diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index edb5f583d7..ba518c1904 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -11,8 +11,8 @@ jobs: fetch-depth: 0 submodules: recursive - - name: Install Flutter - uses: subosito/flutter-action@v2 + - name: Install Flutter + uses: subosito/flutter-action@v2 with: flutter-version: '3.38.1' channel: 'stable' @@ -37,60 +37,34 @@ jobs: - name: Get dependencies run: flutter pub get - - name: Create temp files - id: secret-file1 + - name: Create git_versions.dart stubs run: | - $secretFileExchange = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "lib/external_api_keys.dart"; - $encodedBytes = [System.Convert]::FromBase64String($env:CHANGE_NOW); - Set-Content $secretFileExchange -Value $encodedBytes -AsByteStream; - $secretFileExchangeHash = Get-FileHash $secretFileExchange; - Write-Output "Secret file $secretFileExchange has hash $($secretFileExchangeHash.Hash)"; - - $secretFileBitcoin = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "test/services/coins/bitcoin/bitcoin_wallet_test_parameters.dart"; - $encodedBytes = [System.Convert]::FromBase64String($env:BITCOIN_TEST); - Set-Content $secretFileBitcoin -Value $encodedBytes -AsByteStream; - $secretFileBitcoinHash = Get-FileHash $secretFileBitcoin; - Write-Output "Secret file $secretFileBitcoin has hash $($secretFileBitcoinHash.Hash)"; - - $secretFileDogecoin = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "test/services/coins/dogecoin/dogecoin_wallet_test_parameters.dart"; - $encodedBytes = [System.Convert]::FromBase64String($env:DOGECOIN_TEST); - Set-Content $secretFileDogecoin -Value $encodedBytes -AsByteStream; - $secretFileDogecoinHash = Get-FileHash $secretFileDogecoin; - Write-Output "Secret file $secretFileDogecoin has hash $($secretFileDogecoinHash.Hash)"; - - $secretFileFiro = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "test/services/coins/firo/firo_wallet_test_parameters.dart"; - $encodedBytes = [System.Convert]::FromBase64String($env:FIRO_TEST); - Set-Content $secretFileFiro -Value $encodedBytes -AsByteStream; - $secretFileFiroHash = Get-FileHash $secretFileFiro; - Write-Output "Secret file $secretFileFiro has hash $($secretFileFiroHash.Hash)"; - - $secretFileBitcoinCash = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart"; - $encodedBytes = [System.Convert]::FromBase64String($env:BITCOINCASH_TEST); - Set-Content $secretFileBitcoinCash -Value $encodedBytes -AsByteStream; - $secretFileBitcoinCashHash = Get-FileHash $secretFileBitcoinCash; - Write-Output "Secret file $secretFileBitcoinCash has hash $($secretFileBitcoinCashHash.Hash)"; - - $secretFileNamecoin = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "test/services/coins/namecoin/namecoin_wallet_test_parameters.dart"; - $encodedBytes = [System.Convert]::FromBase64String($env:NAMECOIN_TEST); - Set-Content $secretFileNamecoin -Value $encodedBytes -AsByteStream; - $secretFileNamecoinHash = Get-FileHash $secretFileNamecoin; - Write-Output "Secret file $secretFileNamecoin has hash $($secretFileNamecoinHash.Hash)"; - - $secretFileParticl = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "test/services/coins/particl/particl_wallet_test_parameters.dart"; - $encodedBytes = [System.Convert]::FromBase64String($env:PARTICL_TEST); - Set-Content $secretFileParticl -Value $encodedBytes -AsByteStream; - $secretFileParticlHash = Get-FileHash $secretFileParticl; - Write-Output "Secret file $secretFileParticl has hash $($secretFileParticlHash.Hash)"; - - shell: pwsh + mkdir -p crypto_plugins/flutter_libepiccash/lib + mkdir -p crypto_plugins/flutter_libmwc/lib + + cat > crypto_plugins/flutter_libepiccash/lib/git_versions.dart << 'EOF' + String getPluginVersion() => "stub-for-tests"; + EOF + + cat > crypto_plugins/flutter_libmwc/lib/git_versions.dart << 'EOF' + String getPluginVersion() => "stub-for-tests"; + EOF + + - name: Decode secrets env: CHANGE_NOW: ${{ secrets.CHANGE_NOW }} - BITCOIN_TEST: ${{ secrets.BITCOIN_TEST }} - DOGECOIN_TEST: ${{ secrets.DOGECOIN_TEST }} - FIRO_TEST: ${{ secrets.FIRO_TEST }} - BITCOINCASH_TEST: ${{ secrets.BITCOINCASH_TEST }} - NAMECOIN_TEST: ${{ secrets.NAMECOIN_TEST }} - PARTICL_TEST: ${{ secrets.PARTICL_TEST }} + run: | + echo "$CHANGE_NOW" | base64 --decode > lib/external_api_keys.dart + + - name: Ensure app config for tests + run: bash scripts/ensure_test_app_config.sh + + - name: Create test stubs + run: bash prebuild.sh + working-directory: scripts + + - name: Regenerate mocks + run: dart run build_runner build --delete-conflicting-outputs - name: Check formatting of changed files run: | @@ -110,29 +84,14 @@ jobs: # - name: Analyze # run: flutter analyze - name: Test - run: flutter test --coverage + run: | + bash scripts/ensure_test_app_config.sh + test -s lib/app_config.g.dart + grep -Fq "part of 'app_config.dart';" lib/app_config.g.dart + flutter test --coverage - name: Upload to code coverage uses: codecov/codecov-action@v1.2.2 if: success() || failure() with: token: ${{secrets.CODECOV_TOKEN}} file: coverage/lcov.info - - name: Delete temp files - run: | - $secretFileExchange = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "lib/external_api_keys.dart"; - $secretFileBitcoin = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "test/services/coins/bitcoin/bitcoin_wallet_test_parameters.dart"; - $secretFileDogecoin = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "test/services/coins/dogecoin/dogecoin_wallet_test_parameters.dart"; - $secretFileFiro = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "test/services/coins/firo/firo_wallet_test_parameters.dart"; - $secretFileBitcoinCash = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "test/services/coins/bitcoincash/bitcoincash_wallet_test_parameters.dart"; - $secretFileNamecoin = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "test/services/coins/namecoin/namecoin_wallet_test_parameters.dart"; - $secretFileParticl = Join-Path -Path $env:GITHUB_WORKSPACE -ChildPath "test/services/coins/particl/particl_wallet_test_parameters.dart"; - - Remove-Item -Path $secretFileExchange; - Remove-Item -Path $secretFileBitcoin; - Remove-Item -Path $secretFileDogecoin; - Remove-Item -Path $secretFileFiro; - Remove-Item -Path $secretFileBitcoinCash; - Remove-Item -Path $secretFileNamecoin; - Remove-Item -Path $secretFileParticl; - shell: pwsh - if: always() diff --git a/docs/building.md b/docs/building.md index 924386b7e2..ad2e2550a7 100644 --- a/docs/building.md +++ b/docs/building.md @@ -43,7 +43,7 @@ sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2- ### Build dependencies Install basic dependencies ``` -sudo apt-get install libssl-dev curl unzip automake build-essential file pkg-config git python3 libtool libtinfo6 cmake libgit2-dev clang libncurses5-dev libncursesw5-dev zlib1g-dev llvm g++ gcc gperf libopencv-dev python3-typogrify xsltproc valac gobject-introspection meson +sudo apt-get install libssl-dev curl unzip automake build-essential file pkg-config git python3 libtool libtinfo6 cmake libgit2-dev clang libncurses5-dev libncursesw5-dev zlib1g-dev llvm lld g++ gcc gperf libopencv-dev python3-typogrify xsltproc valac gobject-introspection meson ``` For Ubuntu 20.04, @@ -75,7 +75,7 @@ rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-andro Linux desktop specific dependencies: ``` -sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev meson python3-pip libgirepository1.0-dev valac xsltproc docbook-xsl +sudo apt-get install clang cmake lld ninja-build pkg-config libgtk-3-dev liblzma-dev meson python3-pip libgirepository1.0-dev valac xsltproc docbook-xsl pip3 install --upgrade meson==0.64.1 markdown==3.4.1 markupsafe==2.1.1 jinja2==3.1.2 pygments==2.13.0 toml==0.10.2 typogrify==2.0.7 tomli==2.0.1 ``` diff --git a/lib/db/isar/main_db.dart b/lib/db/isar/main_db.dart index 9e7e0da953..6c6a3e8ba5 100644 --- a/lib/db/isar/main_db.dart +++ b/lib/db/isar/main_db.dart @@ -73,6 +73,7 @@ class MainDB { TokenWalletInfoSchema, FrostWalletInfoSchema, WalletSolanaTokenInfoSchema, + ShopInBitTicketSchema, ], directory: (await StackFileSystem.applicationIsarDirectory()).path, // inspector: kDebugMode, @@ -80,6 +81,14 @@ class MainDB { name: "wallet_data", maxSizeMiB: Platform.isWindows ? 1024 : 512, ); + + // Clear on schema mismatch; tickets are recoverable from the API. + try { + isar.shopInBitTickets.where().findAllSync(); + } catch (_) { + await isar.writeTxn(() async => isar.shopInBitTickets.clear()); + } + return true; } @@ -645,4 +654,34 @@ class MainDB { isar.writeTxn(() async { await isar.solContracts.putAll(tokens); }); + + // ========== ShopInBit tickets =============================================== + + List getShopInBitTickets() { + try { + return isar.shopInBitTickets.where().sortByCreatedAtDesc().findAllSync(); + } catch (_) { + return []; + } + } + + Future putShopInBitTicket(ShopInBitTicket ticket) async { + try { + return await isar.writeTxn(() async { + return await isar.shopInBitTickets.put(ticket); + }); + } catch (e) { + throw MainDBException("failed putShopInBitTicket", e); + } + } + + Future deleteShopInBitTicket(String ticketId) async { + try { + return await isar.writeTxn(() async { + return await isar.shopInBitTickets.deleteByTicketId(ticketId); + }); + } catch (e) { + throw MainDBException("failed deleteShopInBitTicket: $ticketId", e); + } + } } diff --git a/lib/dto/ordinals/inscription_data.dart b/lib/dto/ordinals/inscription_data.dart index 2f12bd670a..19d6ae9a92 100644 --- a/lib/dto/ordinals/inscription_data.dart +++ b/lib/dto/ordinals/inscription_data.dart @@ -51,6 +51,44 @@ class InscriptionData { ); } + /// Parse the response from an ord server's /inscription/{id} endpoint. + /// [contentUrl] should be pre-built as `$baseUrl/content/$inscriptionId`. + factory InscriptionData.fromOrdJson( + Map json, + String contentUrl, + ) { + final inscriptionId = json['inscription_id'] as String; + final satpoint = json['satpoint'] as String? ?? ''; + // satpoint format: "txid:vout:offset" + final satpointParts = satpoint.split(':'); + if (satpointParts.length < 2 || satpointParts[0].isEmpty) { + throw FormatException( + 'Invalid satpoint for inscription $inscriptionId: "$satpoint"', + ); + } + final output = '${satpointParts[0]}:${satpointParts[1]}'; + final offset = satpointParts.length >= 3 + ? int.tryParse(satpointParts[2]) ?? 0 + : 0; + + return InscriptionData( + inscriptionId: inscriptionId, + inscriptionNumber: json['inscription_number'] as int? ?? 0, + address: json['address'] as String? ?? '', + preview: contentUrl, + content: contentUrl, + contentLength: json['content_length'] as int? ?? 0, + contentType: json['content_type'] as String? ?? '', + contentBody: '', + timestamp: json['timestamp'] as int? ?? 0, + genesisTransaction: inscriptionId.split('i').first, + location: satpoint, + output: output, + outputValue: json['output_value'] as int? ?? 0, + offset: offset, + ); + } + @override String toString() { return 'InscriptionData {' diff --git a/lib/hive_registrar.g.dart b/lib/hive_registrar.g.dart new file mode 100644 index 0000000000..1fc044e69b --- /dev/null +++ b/lib/hive_registrar.g.dart @@ -0,0 +1,27 @@ +// Generated by Hive CE +// Do not modify +// Check in to version control + +import 'package:hive_ce/hive.dart'; +import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/models/mwcmqs_config_model.dart'; +import 'package:stackwallet/models/mwcmqs_server_model.dart'; + +extension HiveRegistrar on HiveInterface { + void registerAdapters() { + registerAdapter(ExchangeTransactionStatusAdapter()); + registerAdapter(MwcMqsConfigModelAdapter()); + registerAdapter(MwcMqsServerModelAdapter()); + registerAdapter(TradeAdapter()); + } +} + +extension IsolatedHiveRegistrar on IsolatedHiveInterface { + void registerAdapters() { + registerAdapter(ExchangeTransactionStatusAdapter()); + registerAdapter(MwcMqsConfigModelAdapter()); + registerAdapter(MwcMqsServerModelAdapter()); + registerAdapter(TradeAdapter()); + } +} diff --git a/lib/main.dart b/lib/main.dart index ea6880af6a..3b4083c457 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -41,6 +41,7 @@ import 'models/models.dart'; import 'models/node_model.dart'; import 'models/notification_model.dart'; import 'models/trade_wallet_lookup.dart'; +import 'pages/already_running_view.dart'; import 'pages/campfire_migrate_view.dart'; import 'pages/home_view/home_view.dart'; import 'pages/intro_view.dart'; @@ -178,8 +179,57 @@ void main(List args) async { (await StackFileSystem.applicationHiveDirectory()).path, ); - await DB.instance.hive.openBox(DB.boxNameDBInfo); - await DB.instance.hive.openBox(DB.boxNamePrefs); + try { + await DB.instance.hive.openBox(DB.boxNameDBInfo); + await DB.instance.hive.openBox(DB.boxNamePrefs); + } on FileSystemException catch (e) { + if (e.osError?.errorCode == 11 || e.message.contains('lock failed')) { + // Another instance already holds the Hive database lock. + // Try to bootstrap just enough of the theme system (Isar is independent + // of Hive) so the error screen looks like a real Stack Wallet screen. + Widget errorApp; + try { + await StackFileSystem.initThemesDir(); + await MainDB.instance.initMainDB(); + ThemeService.instance.init(MainDB.instance); + errorApp = const ProviderScope(child: AlreadyRunningApp()); + } catch (_) { + // Isar is also unavailable (e.g., another error). Fall back to a + // minimal but still Inter-font styled screen. + errorApp = MaterialApp( + debugShowCheckedModeBanner: false, + theme: ThemeData(fontFamily: GoogleFonts.inter().fontFamily), + home: Scaffold( + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + AppConfig.appName, + textAlign: TextAlign.center, + style: GoogleFonts.inter( + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 8), + Text( + 'is already running.\n' + 'Close the other window and try again.', + textAlign: TextAlign.center, + style: GoogleFonts.inter(fontSize: 16), + ), + ], + ), + ), + ), + ); + } + runApp(errorApp); + return; + } + rethrow; + } await Prefs.instance.init(); await Logging.instance.initialize( diff --git a/lib/models/isar/models/isar_models.dart b/lib/models/isar/models/isar_models.dart index cf27091bf1..8206fc0f31 100644 --- a/lib/models/isar/models/isar_models.dart +++ b/lib/models/isar/models/isar_models.dart @@ -17,5 +17,6 @@ export 'blockchain_data/utxo.dart'; export 'ethereum/eth_contract.dart'; export 'log.dart'; export 'solana/sol_contract.dart'; +export 'shopinbit_ticket.dart'; export 'transaction_note.dart'; export '../../../wallets/isar/models/wallet_solana_token_info.dart'; diff --git a/lib/models/isar/models/shopinbit_ticket.dart b/lib/models/isar/models/shopinbit_ticket.dart new file mode 100644 index 0000000000..0a2ac53d7b --- /dev/null +++ b/lib/models/isar/models/shopinbit_ticket.dart @@ -0,0 +1,51 @@ +import 'package:isar_community/isar.dart'; + +import '../../shopinbit/shopinbit_order_model.dart'; + +part 'shopinbit_ticket.g.dart'; + +@collection +class ShopInBitTicket { + Id id = Isar.autoIncrement; + + @Index(unique: true, replace: true) + late String ticketId; + + late String displayName; + @enumerated + late ShopInBitCategory category; + @enumerated + late ShopInBitOrderStatus status; + late String requestDescription; + late String deliveryCountry; + late String? offerProductName; + late String? offerPrice; + late String shippingName; + late String shippingStreet; + late String shippingCity; + late String shippingPostalCode; + late String shippingCountry; + late String? paymentMethod; + late List messages; + late DateTime createdAt; + late int apiTicketId; + + // Car research retry support + String? carResearchInvoiceId; + String? feeTicketNumber; + late bool needsCreateRequest; + + // Car research resumable payment state + late bool isPendingPayment; + DateTime? carResearchExpiresAt; + String? carResearchPaymentLinks; +} + +@embedded +class ShopInBitTicketMessage { + late String text; + late DateTime timestamp; + late bool isFromUser; + + ShopInBitTicketMessage(); +} diff --git a/lib/models/isar/models/shopinbit_ticket.g.dart b/lib/models/isar/models/shopinbit_ticket.g.dart new file mode 100644 index 0000000000..ecd600a154 --- /dev/null +++ b/lib/models/isar/models/shopinbit_ticket.g.dart @@ -0,0 +1,4651 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'shopinbit_ticket.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +extension GetShopInBitTicketCollection on Isar { + IsarCollection get shopInBitTickets => this.collection(); +} + +const ShopInBitTicketSchema = CollectionSchema( + name: r'ShopInBitTicket', + id: 1968691807160517649, + properties: { + r'apiTicketId': PropertySchema( + id: 0, + name: r'apiTicketId', + type: IsarType.long, + ), + r'carResearchExpiresAt': PropertySchema( + id: 1, + name: r'carResearchExpiresAt', + type: IsarType.dateTime, + ), + r'carResearchInvoiceId': PropertySchema( + id: 2, + name: r'carResearchInvoiceId', + type: IsarType.string, + ), + r'carResearchPaymentLinks': PropertySchema( + id: 3, + name: r'carResearchPaymentLinks', + type: IsarType.string, + ), + r'category': PropertySchema( + id: 4, + name: r'category', + type: IsarType.byte, + enumMap: _ShopInBitTicketcategoryEnumValueMap, + ), + r'createdAt': PropertySchema( + id: 5, + name: r'createdAt', + type: IsarType.dateTime, + ), + r'deliveryCountry': PropertySchema( + id: 6, + name: r'deliveryCountry', + type: IsarType.string, + ), + r'displayName': PropertySchema( + id: 7, + name: r'displayName', + type: IsarType.string, + ), + r'feeTicketNumber': PropertySchema( + id: 8, + name: r'feeTicketNumber', + type: IsarType.string, + ), + r'isPendingPayment': PropertySchema( + id: 9, + name: r'isPendingPayment', + type: IsarType.bool, + ), + r'messages': PropertySchema( + id: 10, + name: r'messages', + type: IsarType.objectList, + + target: r'ShopInBitTicketMessage', + ), + r'needsCreateRequest': PropertySchema( + id: 11, + name: r'needsCreateRequest', + type: IsarType.bool, + ), + r'offerPrice': PropertySchema( + id: 12, + name: r'offerPrice', + type: IsarType.string, + ), + r'offerProductName': PropertySchema( + id: 13, + name: r'offerProductName', + type: IsarType.string, + ), + r'paymentMethod': PropertySchema( + id: 14, + name: r'paymentMethod', + type: IsarType.string, + ), + r'requestDescription': PropertySchema( + id: 15, + name: r'requestDescription', + type: IsarType.string, + ), + r'shippingCity': PropertySchema( + id: 16, + name: r'shippingCity', + type: IsarType.string, + ), + r'shippingCountry': PropertySchema( + id: 17, + name: r'shippingCountry', + type: IsarType.string, + ), + r'shippingName': PropertySchema( + id: 18, + name: r'shippingName', + type: IsarType.string, + ), + r'shippingPostalCode': PropertySchema( + id: 19, + name: r'shippingPostalCode', + type: IsarType.string, + ), + r'shippingStreet': PropertySchema( + id: 20, + name: r'shippingStreet', + type: IsarType.string, + ), + r'status': PropertySchema( + id: 21, + name: r'status', + type: IsarType.byte, + enumMap: _ShopInBitTicketstatusEnumValueMap, + ), + r'ticketId': PropertySchema( + id: 22, + name: r'ticketId', + type: IsarType.string, + ), + }, + + estimateSize: _shopInBitTicketEstimateSize, + serialize: _shopInBitTicketSerialize, + deserialize: _shopInBitTicketDeserialize, + deserializeProp: _shopInBitTicketDeserializeProp, + idName: r'id', + indexes: { + r'ticketId': IndexSchema( + id: -6483959237056329942, + name: r'ticketId', + unique: true, + replace: true, + properties: [ + IndexPropertySchema( + name: r'ticketId', + type: IndexType.hash, + caseSensitive: true, + ), + ], + ), + }, + links: {}, + embeddedSchemas: {r'ShopInBitTicketMessage': ShopInBitTicketMessageSchema}, + + getId: _shopInBitTicketGetId, + getLinks: _shopInBitTicketGetLinks, + attach: _shopInBitTicketAttach, + version: '3.3.0-dev.2', +); + +int _shopInBitTicketEstimateSize( + ShopInBitTicket object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + { + final value = object.carResearchInvoiceId; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.carResearchPaymentLinks; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.deliveryCountry.length * 3; + bytesCount += 3 + object.displayName.length * 3; + { + final value = object.feeTicketNumber; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.messages.length * 3; + { + final offsets = allOffsets[ShopInBitTicketMessage]!; + for (var i = 0; i < object.messages.length; i++) { + final value = object.messages[i]; + bytesCount += ShopInBitTicketMessageSchema.estimateSize( + value, + offsets, + allOffsets, + ); + } + } + { + final value = object.offerPrice; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.offerProductName; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.paymentMethod; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.requestDescription.length * 3; + bytesCount += 3 + object.shippingCity.length * 3; + bytesCount += 3 + object.shippingCountry.length * 3; + bytesCount += 3 + object.shippingName.length * 3; + bytesCount += 3 + object.shippingPostalCode.length * 3; + bytesCount += 3 + object.shippingStreet.length * 3; + bytesCount += 3 + object.ticketId.length * 3; + return bytesCount; +} + +void _shopInBitTicketSerialize( + ShopInBitTicket object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeLong(offsets[0], object.apiTicketId); + writer.writeDateTime(offsets[1], object.carResearchExpiresAt); + writer.writeString(offsets[2], object.carResearchInvoiceId); + writer.writeString(offsets[3], object.carResearchPaymentLinks); + writer.writeByte(offsets[4], object.category.index); + writer.writeDateTime(offsets[5], object.createdAt); + writer.writeString(offsets[6], object.deliveryCountry); + writer.writeString(offsets[7], object.displayName); + writer.writeString(offsets[8], object.feeTicketNumber); + writer.writeBool(offsets[9], object.isPendingPayment); + writer.writeObjectList( + offsets[10], + allOffsets, + ShopInBitTicketMessageSchema.serialize, + object.messages, + ); + writer.writeBool(offsets[11], object.needsCreateRequest); + writer.writeString(offsets[12], object.offerPrice); + writer.writeString(offsets[13], object.offerProductName); + writer.writeString(offsets[14], object.paymentMethod); + writer.writeString(offsets[15], object.requestDescription); + writer.writeString(offsets[16], object.shippingCity); + writer.writeString(offsets[17], object.shippingCountry); + writer.writeString(offsets[18], object.shippingName); + writer.writeString(offsets[19], object.shippingPostalCode); + writer.writeString(offsets[20], object.shippingStreet); + writer.writeByte(offsets[21], object.status.index); + writer.writeString(offsets[22], object.ticketId); +} + +ShopInBitTicket _shopInBitTicketDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = ShopInBitTicket(); + object.apiTicketId = reader.readLong(offsets[0]); + object.carResearchExpiresAt = reader.readDateTimeOrNull(offsets[1]); + object.carResearchInvoiceId = reader.readStringOrNull(offsets[2]); + object.carResearchPaymentLinks = reader.readStringOrNull(offsets[3]); + object.category = + _ShopInBitTicketcategoryValueEnumMap[reader.readByteOrNull(offsets[4])] ?? + ShopInBitCategory.concierge; + object.createdAt = reader.readDateTime(offsets[5]); + object.deliveryCountry = reader.readString(offsets[6]); + object.displayName = reader.readString(offsets[7]); + object.feeTicketNumber = reader.readStringOrNull(offsets[8]); + object.id = id; + object.isPendingPayment = reader.readBool(offsets[9]); + object.messages = + reader.readObjectList( + offsets[10], + ShopInBitTicketMessageSchema.deserialize, + allOffsets, + ShopInBitTicketMessage(), + ) ?? + []; + object.needsCreateRequest = reader.readBool(offsets[11]); + object.offerPrice = reader.readStringOrNull(offsets[12]); + object.offerProductName = reader.readStringOrNull(offsets[13]); + object.paymentMethod = reader.readStringOrNull(offsets[14]); + object.requestDescription = reader.readString(offsets[15]); + object.shippingCity = reader.readString(offsets[16]); + object.shippingCountry = reader.readString(offsets[17]); + object.shippingName = reader.readString(offsets[18]); + object.shippingPostalCode = reader.readString(offsets[19]); + object.shippingStreet = reader.readString(offsets[20]); + object.status = + _ShopInBitTicketstatusValueEnumMap[reader.readByteOrNull(offsets[21])] ?? + ShopInBitOrderStatus.pending; + object.ticketId = reader.readString(offsets[22]); + return object; +} + +P _shopInBitTicketDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readLong(offset)) as P; + case 1: + return (reader.readDateTimeOrNull(offset)) as P; + case 2: + return (reader.readStringOrNull(offset)) as P; + case 3: + return (reader.readStringOrNull(offset)) as P; + case 4: + return (_ShopInBitTicketcategoryValueEnumMap[reader.readByteOrNull( + offset, + )] ?? + ShopInBitCategory.concierge) + as P; + case 5: + return (reader.readDateTime(offset)) as P; + case 6: + return (reader.readString(offset)) as P; + case 7: + return (reader.readString(offset)) as P; + case 8: + return (reader.readStringOrNull(offset)) as P; + case 9: + return (reader.readBool(offset)) as P; + case 10: + return (reader.readObjectList( + offset, + ShopInBitTicketMessageSchema.deserialize, + allOffsets, + ShopInBitTicketMessage(), + ) ?? + []) + as P; + case 11: + return (reader.readBool(offset)) as P; + case 12: + return (reader.readStringOrNull(offset)) as P; + case 13: + return (reader.readStringOrNull(offset)) as P; + case 14: + return (reader.readStringOrNull(offset)) as P; + case 15: + return (reader.readString(offset)) as P; + case 16: + return (reader.readString(offset)) as P; + case 17: + return (reader.readString(offset)) as P; + case 18: + return (reader.readString(offset)) as P; + case 19: + return (reader.readString(offset)) as P; + case 20: + return (reader.readString(offset)) as P; + case 21: + return (_ShopInBitTicketstatusValueEnumMap[reader.readByteOrNull( + offset, + )] ?? + ShopInBitOrderStatus.pending) + as P; + case 22: + return (reader.readString(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +const _ShopInBitTicketcategoryEnumValueMap = { + 'concierge': 0, + 'travel': 1, + 'car': 2, +}; +const _ShopInBitTicketcategoryValueEnumMap = { + 0: ShopInBitCategory.concierge, + 1: ShopInBitCategory.travel, + 2: ShopInBitCategory.car, +}; +const _ShopInBitTicketstatusEnumValueMap = { + 'pending': 0, + 'reviewing': 1, + 'offerAvailable': 2, + 'accepted': 3, + 'paymentPending': 4, + 'paid': 5, + 'shipping': 6, + 'delivered': 7, + 'closed': 8, + 'cancelled': 9, + 'refunded': 10, +}; +const _ShopInBitTicketstatusValueEnumMap = { + 0: ShopInBitOrderStatus.pending, + 1: ShopInBitOrderStatus.reviewing, + 2: ShopInBitOrderStatus.offerAvailable, + 3: ShopInBitOrderStatus.accepted, + 4: ShopInBitOrderStatus.paymentPending, + 5: ShopInBitOrderStatus.paid, + 6: ShopInBitOrderStatus.shipping, + 7: ShopInBitOrderStatus.delivered, + 8: ShopInBitOrderStatus.closed, + 9: ShopInBitOrderStatus.cancelled, + 10: ShopInBitOrderStatus.refunded, +}; + +Id _shopInBitTicketGetId(ShopInBitTicket object) { + return object.id; +} + +List> _shopInBitTicketGetLinks(ShopInBitTicket object) { + return []; +} + +void _shopInBitTicketAttach( + IsarCollection col, + Id id, + ShopInBitTicket object, +) { + object.id = id; +} + +extension ShopInBitTicketByIndex on IsarCollection { + Future getByTicketId(String ticketId) { + return getByIndex(r'ticketId', [ticketId]); + } + + ShopInBitTicket? getByTicketIdSync(String ticketId) { + return getByIndexSync(r'ticketId', [ticketId]); + } + + Future deleteByTicketId(String ticketId) { + return deleteByIndex(r'ticketId', [ticketId]); + } + + bool deleteByTicketIdSync(String ticketId) { + return deleteByIndexSync(r'ticketId', [ticketId]); + } + + Future> getAllByTicketId(List ticketIdValues) { + final values = ticketIdValues.map((e) => [e]).toList(); + return getAllByIndex(r'ticketId', values); + } + + List getAllByTicketIdSync(List ticketIdValues) { + final values = ticketIdValues.map((e) => [e]).toList(); + return getAllByIndexSync(r'ticketId', values); + } + + Future deleteAllByTicketId(List ticketIdValues) { + final values = ticketIdValues.map((e) => [e]).toList(); + return deleteAllByIndex(r'ticketId', values); + } + + int deleteAllByTicketIdSync(List ticketIdValues) { + final values = ticketIdValues.map((e) => [e]).toList(); + return deleteAllByIndexSync(r'ticketId', values); + } + + Future putByTicketId(ShopInBitTicket object) { + return putByIndex(r'ticketId', object); + } + + Id putByTicketIdSync(ShopInBitTicket object, {bool saveLinks = true}) { + return putByIndexSync(r'ticketId', object, saveLinks: saveLinks); + } + + Future> putAllByTicketId(List objects) { + return putAllByIndex(r'ticketId', objects); + } + + List putAllByTicketIdSync( + List objects, { + bool saveLinks = true, + }) { + return putAllByIndexSync(r'ticketId', objects, saveLinks: saveLinks); + } +} + +extension ShopInBitTicketQueryWhereSort + on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension ShopInBitTicketQueryWhere + on QueryBuilder { + QueryBuilder idEqualTo( + Id id, + ) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between(lower: id, upper: id)); + }); + } + + QueryBuilder + idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder + idGreaterThan(Id id, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan( + Id id, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder + ticketIdEqualTo(String ticketId) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IndexWhereClause.equalTo(indexName: r'ticketId', value: [ticketId]), + ); + }); + } + + QueryBuilder + ticketIdNotEqualTo(String ticketId) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'ticketId', + lower: [], + upper: [ticketId], + includeUpper: false, + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'ticketId', + lower: [ticketId], + includeLower: false, + upper: [], + ), + ); + } else { + return query + .addWhereClause( + IndexWhereClause.between( + indexName: r'ticketId', + lower: [ticketId], + includeLower: false, + upper: [], + ), + ) + .addWhereClause( + IndexWhereClause.between( + indexName: r'ticketId', + lower: [], + upper: [ticketId], + includeUpper: false, + ), + ); + } + }); + } +} + +extension ShopInBitTicketQueryFilter + on QueryBuilder { + QueryBuilder + apiTicketIdEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'apiTicketId', value: value), + ); + }); + } + + QueryBuilder + apiTicketIdGreaterThan(int value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'apiTicketId', + value: value, + ), + ); + }); + } + + QueryBuilder + apiTicketIdLessThan(int value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'apiTicketId', + value: value, + ), + ); + }); + } + + QueryBuilder + apiTicketIdBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'apiTicketId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder + carResearchExpiresAtIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'carResearchExpiresAt'), + ); + }); + } + + QueryBuilder + carResearchExpiresAtIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'carResearchExpiresAt'), + ); + }); + } + + QueryBuilder + carResearchExpiresAtEqualTo(DateTime? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'carResearchExpiresAt', + value: value, + ), + ); + }); + } + + QueryBuilder + carResearchExpiresAtGreaterThan(DateTime? value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'carResearchExpiresAt', + value: value, + ), + ); + }); + } + + QueryBuilder + carResearchExpiresAtLessThan(DateTime? value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'carResearchExpiresAt', + value: value, + ), + ); + }); + } + + QueryBuilder + carResearchExpiresAtBetween( + DateTime? lower, + DateTime? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'carResearchExpiresAt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder + carResearchInvoiceIdIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'carResearchInvoiceId'), + ); + }); + } + + QueryBuilder + carResearchInvoiceIdIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'carResearchInvoiceId'), + ); + }); + } + + QueryBuilder + carResearchInvoiceIdEqualTo(String? value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'carResearchInvoiceId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + carResearchInvoiceIdGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'carResearchInvoiceId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + carResearchInvoiceIdLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'carResearchInvoiceId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + carResearchInvoiceIdBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'carResearchInvoiceId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + carResearchInvoiceIdStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'carResearchInvoiceId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + carResearchInvoiceIdEndsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'carResearchInvoiceId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + carResearchInvoiceIdContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'carResearchInvoiceId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + carResearchInvoiceIdMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'carResearchInvoiceId', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + carResearchInvoiceIdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'carResearchInvoiceId', value: ''), + ); + }); + } + + QueryBuilder + carResearchInvoiceIdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + property: r'carResearchInvoiceId', + value: '', + ), + ); + }); + } + + QueryBuilder + carResearchPaymentLinksIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'carResearchPaymentLinks'), + ); + }); + } + + QueryBuilder + carResearchPaymentLinksIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'carResearchPaymentLinks'), + ); + }); + } + + QueryBuilder + carResearchPaymentLinksEqualTo(String? value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'carResearchPaymentLinks', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + carResearchPaymentLinksGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'carResearchPaymentLinks', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + carResearchPaymentLinksLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'carResearchPaymentLinks', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + carResearchPaymentLinksBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'carResearchPaymentLinks', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + carResearchPaymentLinksStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'carResearchPaymentLinks', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + carResearchPaymentLinksEndsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'carResearchPaymentLinks', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + carResearchPaymentLinksContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'carResearchPaymentLinks', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + carResearchPaymentLinksMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'carResearchPaymentLinks', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + carResearchPaymentLinksIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'carResearchPaymentLinks', + value: '', + ), + ); + }); + } + + QueryBuilder + carResearchPaymentLinksIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + property: r'carResearchPaymentLinks', + value: '', + ), + ); + }); + } + + QueryBuilder + categoryEqualTo(ShopInBitCategory value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'category', value: value), + ); + }); + } + + QueryBuilder + categoryGreaterThan(ShopInBitCategory value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'category', + value: value, + ), + ); + }); + } + + QueryBuilder + categoryLessThan(ShopInBitCategory value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'category', + value: value, + ), + ); + }); + } + + QueryBuilder + categoryBetween( + ShopInBitCategory lower, + ShopInBitCategory upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'category', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder + createdAtEqualTo(DateTime value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'createdAt', value: value), + ); + }); + } + + QueryBuilder + createdAtGreaterThan(DateTime value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'createdAt', + value: value, + ), + ); + }); + } + + QueryBuilder + createdAtLessThan(DateTime value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'createdAt', + value: value, + ), + ); + }); + } + + QueryBuilder + createdAtBetween( + DateTime lower, + DateTime upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'createdAt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder + deliveryCountryEqualTo(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'deliveryCountry', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + deliveryCountryGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'deliveryCountry', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + deliveryCountryLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'deliveryCountry', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + deliveryCountryBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'deliveryCountry', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + deliveryCountryStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'deliveryCountry', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + deliveryCountryEndsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'deliveryCountry', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + deliveryCountryContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'deliveryCountry', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + deliveryCountryMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'deliveryCountry', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + deliveryCountryIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'deliveryCountry', value: ''), + ); + }); + } + + QueryBuilder + deliveryCountryIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'deliveryCountry', value: ''), + ); + }); + } + + QueryBuilder + displayNameEqualTo(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'displayName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + displayNameGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'displayName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + displayNameLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'displayName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + displayNameBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'displayName', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + displayNameStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'displayName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + displayNameEndsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'displayName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + displayNameContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'displayName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + displayNameMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'displayName', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + displayNameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'displayName', value: ''), + ); + }); + } + + QueryBuilder + displayNameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'displayName', value: ''), + ); + }); + } + + QueryBuilder + feeTicketNumberIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'feeTicketNumber'), + ); + }); + } + + QueryBuilder + feeTicketNumberIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'feeTicketNumber'), + ); + }); + } + + QueryBuilder + feeTicketNumberEqualTo(String? value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'feeTicketNumber', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + feeTicketNumberGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'feeTicketNumber', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + feeTicketNumberLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'feeTicketNumber', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + feeTicketNumberBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'feeTicketNumber', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + feeTicketNumberStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'feeTicketNumber', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + feeTicketNumberEndsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'feeTicketNumber', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + feeTicketNumberContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'feeTicketNumber', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + feeTicketNumberMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'feeTicketNumber', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + feeTicketNumberIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'feeTicketNumber', value: ''), + ); + }); + } + + QueryBuilder + feeTicketNumberIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'feeTicketNumber', value: ''), + ); + }); + } + + QueryBuilder + idEqualTo(Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'id', value: value), + ); + }); + } + + QueryBuilder + idGreaterThan(Id value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + ), + ); + }); + } + + QueryBuilder + idLessThan(Id value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + ), + ); + }); + } + + QueryBuilder + idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder + isPendingPaymentEqualTo(bool value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'isPendingPayment', value: value), + ); + }); + } + + QueryBuilder + messagesLengthEqualTo(int length) { + return QueryBuilder.apply(this, (query) { + return query.listLength(r'messages', length, true, length, true); + }); + } + + QueryBuilder + messagesIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.listLength(r'messages', 0, true, 0, true); + }); + } + + QueryBuilder + messagesIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.listLength(r'messages', 0, false, 999999, true); + }); + } + + QueryBuilder + messagesLengthLessThan(int length, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.listLength(r'messages', 0, true, length, include); + }); + } + + QueryBuilder + messagesLengthGreaterThan(int length, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.listLength(r'messages', length, include, 999999, true); + }); + } + + QueryBuilder + messagesLengthBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'messages', + lower, + includeLower, + upper, + includeUpper, + ); + }); + } + + QueryBuilder + needsCreateRequestEqualTo(bool value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'needsCreateRequest', value: value), + ); + }); + } + + QueryBuilder + offerPriceIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'offerPrice'), + ); + }); + } + + QueryBuilder + offerPriceIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'offerPrice'), + ); + }); + } + + QueryBuilder + offerPriceEqualTo(String? value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'offerPrice', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + offerPriceGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'offerPrice', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + offerPriceLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'offerPrice', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + offerPriceBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'offerPrice', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + offerPriceStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'offerPrice', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + offerPriceEndsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'offerPrice', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + offerPriceContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'offerPrice', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + offerPriceMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'offerPrice', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + offerPriceIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'offerPrice', value: ''), + ); + }); + } + + QueryBuilder + offerPriceIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'offerPrice', value: ''), + ); + }); + } + + QueryBuilder + offerProductNameIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'offerProductName'), + ); + }); + } + + QueryBuilder + offerProductNameIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'offerProductName'), + ); + }); + } + + QueryBuilder + offerProductNameEqualTo(String? value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'offerProductName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + offerProductNameGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'offerProductName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + offerProductNameLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'offerProductName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + offerProductNameBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'offerProductName', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + offerProductNameStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'offerProductName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + offerProductNameEndsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'offerProductName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + offerProductNameContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'offerProductName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + offerProductNameMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'offerProductName', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + offerProductNameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'offerProductName', value: ''), + ); + }); + } + + QueryBuilder + offerProductNameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'offerProductName', value: ''), + ); + }); + } + + QueryBuilder + paymentMethodIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNull(property: r'paymentMethod'), + ); + }); + } + + QueryBuilder + paymentMethodIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + const FilterCondition.isNotNull(property: r'paymentMethod'), + ); + }); + } + + QueryBuilder + paymentMethodEqualTo(String? value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'paymentMethod', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + paymentMethodGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'paymentMethod', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + paymentMethodLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'paymentMethod', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + paymentMethodBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'paymentMethod', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + paymentMethodStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'paymentMethod', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + paymentMethodEndsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'paymentMethod', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + paymentMethodContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'paymentMethod', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + paymentMethodMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'paymentMethod', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + paymentMethodIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'paymentMethod', value: ''), + ); + }); + } + + QueryBuilder + paymentMethodIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'paymentMethod', value: ''), + ); + }); + } + + QueryBuilder + requestDescriptionEqualTo(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'requestDescription', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + requestDescriptionGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'requestDescription', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + requestDescriptionLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'requestDescription', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + requestDescriptionBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'requestDescription', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + requestDescriptionStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'requestDescription', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + requestDescriptionEndsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'requestDescription', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + requestDescriptionContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'requestDescription', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + requestDescriptionMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'requestDescription', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + requestDescriptionIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'requestDescription', value: ''), + ); + }); + } + + QueryBuilder + requestDescriptionIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'requestDescription', value: ''), + ); + }); + } + + QueryBuilder + shippingCityEqualTo(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'shippingCity', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingCityGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'shippingCity', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingCityLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'shippingCity', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingCityBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'shippingCity', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingCityStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'shippingCity', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingCityEndsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'shippingCity', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingCityContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'shippingCity', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingCityMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'shippingCity', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingCityIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'shippingCity', value: ''), + ); + }); + } + + QueryBuilder + shippingCityIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'shippingCity', value: ''), + ); + }); + } + + QueryBuilder + shippingCountryEqualTo(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'shippingCountry', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingCountryGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'shippingCountry', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingCountryLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'shippingCountry', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingCountryBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'shippingCountry', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingCountryStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'shippingCountry', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingCountryEndsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'shippingCountry', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingCountryContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'shippingCountry', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingCountryMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'shippingCountry', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingCountryIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'shippingCountry', value: ''), + ); + }); + } + + QueryBuilder + shippingCountryIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'shippingCountry', value: ''), + ); + }); + } + + QueryBuilder + shippingNameEqualTo(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'shippingName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingNameGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'shippingName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingNameLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'shippingName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingNameBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'shippingName', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingNameStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'shippingName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingNameEndsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'shippingName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingNameContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'shippingName', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingNameMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'shippingName', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingNameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'shippingName', value: ''), + ); + }); + } + + QueryBuilder + shippingNameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'shippingName', value: ''), + ); + }); + } + + QueryBuilder + shippingPostalCodeEqualTo(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'shippingPostalCode', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingPostalCodeGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'shippingPostalCode', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingPostalCodeLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'shippingPostalCode', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingPostalCodeBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'shippingPostalCode', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingPostalCodeStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'shippingPostalCode', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingPostalCodeEndsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'shippingPostalCode', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingPostalCodeContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'shippingPostalCode', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingPostalCodeMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'shippingPostalCode', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingPostalCodeIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'shippingPostalCode', value: ''), + ); + }); + } + + QueryBuilder + shippingPostalCodeIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'shippingPostalCode', value: ''), + ); + }); + } + + QueryBuilder + shippingStreetEqualTo(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'shippingStreet', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingStreetGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'shippingStreet', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingStreetLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'shippingStreet', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingStreetBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'shippingStreet', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingStreetStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'shippingStreet', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingStreetEndsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'shippingStreet', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingStreetContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'shippingStreet', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingStreetMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'shippingStreet', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + shippingStreetIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'shippingStreet', value: ''), + ); + }); + } + + QueryBuilder + shippingStreetIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'shippingStreet', value: ''), + ); + }); + } + + QueryBuilder + statusEqualTo(ShopInBitOrderStatus value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'status', value: value), + ); + }); + } + + QueryBuilder + statusGreaterThan(ShopInBitOrderStatus value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'status', + value: value, + ), + ); + }); + } + + QueryBuilder + statusLessThan(ShopInBitOrderStatus value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'status', + value: value, + ), + ); + }); + } + + QueryBuilder + statusBetween( + ShopInBitOrderStatus lower, + ShopInBitOrderStatus upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'status', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } + + QueryBuilder + ticketIdEqualTo(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'ticketId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + ticketIdGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'ticketId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + ticketIdLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'ticketId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + ticketIdBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'ticketId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + ticketIdStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'ticketId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + ticketIdEndsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'ticketId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + ticketIdContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'ticketId', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + ticketIdMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'ticketId', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder + ticketIdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'ticketId', value: ''), + ); + }); + } + + QueryBuilder + ticketIdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'ticketId', value: ''), + ); + }); + } +} + +extension ShopInBitTicketQueryObject + on QueryBuilder { + QueryBuilder + messagesElement(FilterQuery q) { + return QueryBuilder.apply(this, (query) { + return query.object(q, r'messages'); + }); + } +} + +extension ShopInBitTicketQueryLinks + on QueryBuilder {} + +extension ShopInBitTicketQuerySortBy + on QueryBuilder { + QueryBuilder + sortByApiTicketId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'apiTicketId', Sort.asc); + }); + } + + QueryBuilder + sortByApiTicketIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'apiTicketId', Sort.desc); + }); + } + + QueryBuilder + sortByCarResearchExpiresAt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'carResearchExpiresAt', Sort.asc); + }); + } + + QueryBuilder + sortByCarResearchExpiresAtDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'carResearchExpiresAt', Sort.desc); + }); + } + + QueryBuilder + sortByCarResearchInvoiceId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'carResearchInvoiceId', Sort.asc); + }); + } + + QueryBuilder + sortByCarResearchInvoiceIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'carResearchInvoiceId', Sort.desc); + }); + } + + QueryBuilder + sortByCarResearchPaymentLinks() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'carResearchPaymentLinks', Sort.asc); + }); + } + + QueryBuilder + sortByCarResearchPaymentLinksDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'carResearchPaymentLinks', Sort.desc); + }); + } + + QueryBuilder + sortByCategory() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'category', Sort.asc); + }); + } + + QueryBuilder + sortByCategoryDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'category', Sort.desc); + }); + } + + QueryBuilder + sortByCreatedAt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'createdAt', Sort.asc); + }); + } + + QueryBuilder + sortByCreatedAtDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'createdAt', Sort.desc); + }); + } + + QueryBuilder + sortByDeliveryCountry() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'deliveryCountry', Sort.asc); + }); + } + + QueryBuilder + sortByDeliveryCountryDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'deliveryCountry', Sort.desc); + }); + } + + QueryBuilder + sortByDisplayName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'displayName', Sort.asc); + }); + } + + QueryBuilder + sortByDisplayNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'displayName', Sort.desc); + }); + } + + QueryBuilder + sortByFeeTicketNumber() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'feeTicketNumber', Sort.asc); + }); + } + + QueryBuilder + sortByFeeTicketNumberDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'feeTicketNumber', Sort.desc); + }); + } + + QueryBuilder + sortByIsPendingPayment() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isPendingPayment', Sort.asc); + }); + } + + QueryBuilder + sortByIsPendingPaymentDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isPendingPayment', Sort.desc); + }); + } + + QueryBuilder + sortByNeedsCreateRequest() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'needsCreateRequest', Sort.asc); + }); + } + + QueryBuilder + sortByNeedsCreateRequestDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'needsCreateRequest', Sort.desc); + }); + } + + QueryBuilder + sortByOfferPrice() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'offerPrice', Sort.asc); + }); + } + + QueryBuilder + sortByOfferPriceDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'offerPrice', Sort.desc); + }); + } + + QueryBuilder + sortByOfferProductName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'offerProductName', Sort.asc); + }); + } + + QueryBuilder + sortByOfferProductNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'offerProductName', Sort.desc); + }); + } + + QueryBuilder + sortByPaymentMethod() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'paymentMethod', Sort.asc); + }); + } + + QueryBuilder + sortByPaymentMethodDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'paymentMethod', Sort.desc); + }); + } + + QueryBuilder + sortByRequestDescription() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'requestDescription', Sort.asc); + }); + } + + QueryBuilder + sortByRequestDescriptionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'requestDescription', Sort.desc); + }); + } + + QueryBuilder + sortByShippingCity() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shippingCity', Sort.asc); + }); + } + + QueryBuilder + sortByShippingCityDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shippingCity', Sort.desc); + }); + } + + QueryBuilder + sortByShippingCountry() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shippingCountry', Sort.asc); + }); + } + + QueryBuilder + sortByShippingCountryDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shippingCountry', Sort.desc); + }); + } + + QueryBuilder + sortByShippingName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shippingName', Sort.asc); + }); + } + + QueryBuilder + sortByShippingNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shippingName', Sort.desc); + }); + } + + QueryBuilder + sortByShippingPostalCode() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shippingPostalCode', Sort.asc); + }); + } + + QueryBuilder + sortByShippingPostalCodeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shippingPostalCode', Sort.desc); + }); + } + + QueryBuilder + sortByShippingStreet() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shippingStreet', Sort.asc); + }); + } + + QueryBuilder + sortByShippingStreetDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shippingStreet', Sort.desc); + }); + } + + QueryBuilder sortByStatus() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'status', Sort.asc); + }); + } + + QueryBuilder + sortByStatusDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'status', Sort.desc); + }); + } + + QueryBuilder + sortByTicketId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ticketId', Sort.asc); + }); + } + + QueryBuilder + sortByTicketIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ticketId', Sort.desc); + }); + } +} + +extension ShopInBitTicketQuerySortThenBy + on QueryBuilder { + QueryBuilder + thenByApiTicketId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'apiTicketId', Sort.asc); + }); + } + + QueryBuilder + thenByApiTicketIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'apiTicketId', Sort.desc); + }); + } + + QueryBuilder + thenByCarResearchExpiresAt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'carResearchExpiresAt', Sort.asc); + }); + } + + QueryBuilder + thenByCarResearchExpiresAtDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'carResearchExpiresAt', Sort.desc); + }); + } + + QueryBuilder + thenByCarResearchInvoiceId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'carResearchInvoiceId', Sort.asc); + }); + } + + QueryBuilder + thenByCarResearchInvoiceIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'carResearchInvoiceId', Sort.desc); + }); + } + + QueryBuilder + thenByCarResearchPaymentLinks() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'carResearchPaymentLinks', Sort.asc); + }); + } + + QueryBuilder + thenByCarResearchPaymentLinksDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'carResearchPaymentLinks', Sort.desc); + }); + } + + QueryBuilder + thenByCategory() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'category', Sort.asc); + }); + } + + QueryBuilder + thenByCategoryDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'category', Sort.desc); + }); + } + + QueryBuilder + thenByCreatedAt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'createdAt', Sort.asc); + }); + } + + QueryBuilder + thenByCreatedAtDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'createdAt', Sort.desc); + }); + } + + QueryBuilder + thenByDeliveryCountry() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'deliveryCountry', Sort.asc); + }); + } + + QueryBuilder + thenByDeliveryCountryDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'deliveryCountry', Sort.desc); + }); + } + + QueryBuilder + thenByDisplayName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'displayName', Sort.asc); + }); + } + + QueryBuilder + thenByDisplayNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'displayName', Sort.desc); + }); + } + + QueryBuilder + thenByFeeTicketNumber() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'feeTicketNumber', Sort.asc); + }); + } + + QueryBuilder + thenByFeeTicketNumberDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'feeTicketNumber', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder + thenByIsPendingPayment() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isPendingPayment', Sort.asc); + }); + } + + QueryBuilder + thenByIsPendingPaymentDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isPendingPayment', Sort.desc); + }); + } + + QueryBuilder + thenByNeedsCreateRequest() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'needsCreateRequest', Sort.asc); + }); + } + + QueryBuilder + thenByNeedsCreateRequestDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'needsCreateRequest', Sort.desc); + }); + } + + QueryBuilder + thenByOfferPrice() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'offerPrice', Sort.asc); + }); + } + + QueryBuilder + thenByOfferPriceDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'offerPrice', Sort.desc); + }); + } + + QueryBuilder + thenByOfferProductName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'offerProductName', Sort.asc); + }); + } + + QueryBuilder + thenByOfferProductNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'offerProductName', Sort.desc); + }); + } + + QueryBuilder + thenByPaymentMethod() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'paymentMethod', Sort.asc); + }); + } + + QueryBuilder + thenByPaymentMethodDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'paymentMethod', Sort.desc); + }); + } + + QueryBuilder + thenByRequestDescription() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'requestDescription', Sort.asc); + }); + } + + QueryBuilder + thenByRequestDescriptionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'requestDescription', Sort.desc); + }); + } + + QueryBuilder + thenByShippingCity() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shippingCity', Sort.asc); + }); + } + + QueryBuilder + thenByShippingCityDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shippingCity', Sort.desc); + }); + } + + QueryBuilder + thenByShippingCountry() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shippingCountry', Sort.asc); + }); + } + + QueryBuilder + thenByShippingCountryDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shippingCountry', Sort.desc); + }); + } + + QueryBuilder + thenByShippingName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shippingName', Sort.asc); + }); + } + + QueryBuilder + thenByShippingNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shippingName', Sort.desc); + }); + } + + QueryBuilder + thenByShippingPostalCode() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shippingPostalCode', Sort.asc); + }); + } + + QueryBuilder + thenByShippingPostalCodeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shippingPostalCode', Sort.desc); + }); + } + + QueryBuilder + thenByShippingStreet() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shippingStreet', Sort.asc); + }); + } + + QueryBuilder + thenByShippingStreetDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shippingStreet', Sort.desc); + }); + } + + QueryBuilder thenByStatus() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'status', Sort.asc); + }); + } + + QueryBuilder + thenByStatusDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'status', Sort.desc); + }); + } + + QueryBuilder + thenByTicketId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ticketId', Sort.asc); + }); + } + + QueryBuilder + thenByTicketIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ticketId', Sort.desc); + }); + } +} + +extension ShopInBitTicketQueryWhereDistinct + on QueryBuilder { + QueryBuilder + distinctByApiTicketId() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'apiTicketId'); + }); + } + + QueryBuilder + distinctByCarResearchExpiresAt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'carResearchExpiresAt'); + }); + } + + QueryBuilder + distinctByCarResearchInvoiceId({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy( + r'carResearchInvoiceId', + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder + distinctByCarResearchPaymentLinks({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy( + r'carResearchPaymentLinks', + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder + distinctByCategory() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'category'); + }); + } + + QueryBuilder + distinctByCreatedAt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'createdAt'); + }); + } + + QueryBuilder + distinctByDeliveryCountry({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy( + r'deliveryCountry', + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder + distinctByDisplayName({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'displayName', caseSensitive: caseSensitive); + }); + } + + QueryBuilder + distinctByFeeTicketNumber({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy( + r'feeTicketNumber', + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder + distinctByIsPendingPayment() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isPendingPayment'); + }); + } + + QueryBuilder + distinctByNeedsCreateRequest() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'needsCreateRequest'); + }); + } + + QueryBuilder + distinctByOfferPrice({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'offerPrice', caseSensitive: caseSensitive); + }); + } + + QueryBuilder + distinctByOfferProductName({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy( + r'offerProductName', + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder + distinctByPaymentMethod({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy( + r'paymentMethod', + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder + distinctByRequestDescription({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy( + r'requestDescription', + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder + distinctByShippingCity({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'shippingCity', caseSensitive: caseSensitive); + }); + } + + QueryBuilder + distinctByShippingCountry({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy( + r'shippingCountry', + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder + distinctByShippingName({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'shippingName', caseSensitive: caseSensitive); + }); + } + + QueryBuilder + distinctByShippingPostalCode({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy( + r'shippingPostalCode', + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder + distinctByShippingStreet({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy( + r'shippingStreet', + caseSensitive: caseSensitive, + ); + }); + } + + QueryBuilder distinctByStatus() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'status'); + }); + } + + QueryBuilder distinctByTicketId({ + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'ticketId', caseSensitive: caseSensitive); + }); + } +} + +extension ShopInBitTicketQueryProperty + on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder apiTicketIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'apiTicketId'); + }); + } + + QueryBuilder + carResearchExpiresAtProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'carResearchExpiresAt'); + }); + } + + QueryBuilder + carResearchInvoiceIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'carResearchInvoiceId'); + }); + } + + QueryBuilder + carResearchPaymentLinksProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'carResearchPaymentLinks'); + }); + } + + QueryBuilder + categoryProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'category'); + }); + } + + QueryBuilder + createdAtProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'createdAt'); + }); + } + + QueryBuilder + deliveryCountryProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'deliveryCountry'); + }); + } + + QueryBuilder + displayNameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'displayName'); + }); + } + + QueryBuilder + feeTicketNumberProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'feeTicketNumber'); + }); + } + + QueryBuilder + isPendingPaymentProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isPendingPayment'); + }); + } + + QueryBuilder, QQueryOperations> + messagesProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'messages'); + }); + } + + QueryBuilder + needsCreateRequestProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'needsCreateRequest'); + }); + } + + QueryBuilder + offerPriceProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'offerPrice'); + }); + } + + QueryBuilder + offerProductNameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'offerProductName'); + }); + } + + QueryBuilder + paymentMethodProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'paymentMethod'); + }); + } + + QueryBuilder + requestDescriptionProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'requestDescription'); + }); + } + + QueryBuilder + shippingCityProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'shippingCity'); + }); + } + + QueryBuilder + shippingCountryProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'shippingCountry'); + }); + } + + QueryBuilder + shippingNameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'shippingName'); + }); + } + + QueryBuilder + shippingPostalCodeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'shippingPostalCode'); + }); + } + + QueryBuilder + shippingStreetProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'shippingStreet'); + }); + } + + QueryBuilder + statusProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'status'); + }); + } + + QueryBuilder ticketIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'ticketId'); + }); + } +} + +// ************************************************************************** +// IsarEmbeddedGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types + +const ShopInBitTicketMessageSchema = Schema( + name: r'ShopInBitTicketMessage', + id: -6797752334657665095, + properties: { + r'isFromUser': PropertySchema( + id: 0, + name: r'isFromUser', + type: IsarType.bool, + ), + r'text': PropertySchema(id: 1, name: r'text', type: IsarType.string), + r'timestamp': PropertySchema( + id: 2, + name: r'timestamp', + type: IsarType.dateTime, + ), + }, + + estimateSize: _shopInBitTicketMessageEstimateSize, + serialize: _shopInBitTicketMessageSerialize, + deserialize: _shopInBitTicketMessageDeserialize, + deserializeProp: _shopInBitTicketMessageDeserializeProp, +); + +int _shopInBitTicketMessageEstimateSize( + ShopInBitTicketMessage object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.text.length * 3; + return bytesCount; +} + +void _shopInBitTicketMessageSerialize( + ShopInBitTicketMessage object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeBool(offsets[0], object.isFromUser); + writer.writeString(offsets[1], object.text); + writer.writeDateTime(offsets[2], object.timestamp); +} + +ShopInBitTicketMessage _shopInBitTicketMessageDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = ShopInBitTicketMessage(); + object.isFromUser = reader.readBool(offsets[0]); + object.text = reader.readString(offsets[1]); + object.timestamp = reader.readDateTime(offsets[2]); + return object; +} + +P _shopInBitTicketMessageDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readBool(offset)) as P; + case 1: + return (reader.readString(offset)) as P; + case 2: + return (reader.readDateTime(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +extension ShopInBitTicketMessageQueryFilter + on + QueryBuilder< + ShopInBitTicketMessage, + ShopInBitTicketMessage, + QFilterCondition + > { + QueryBuilder< + ShopInBitTicketMessage, + ShopInBitTicketMessage, + QAfterFilterCondition + > + isFromUserEqualTo(bool value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'isFromUser', value: value), + ); + }); + } + + QueryBuilder< + ShopInBitTicketMessage, + ShopInBitTicketMessage, + QAfterFilterCondition + > + textEqualTo(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo( + property: r'text', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder< + ShopInBitTicketMessage, + ShopInBitTicketMessage, + QAfterFilterCondition + > + textGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'text', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder< + ShopInBitTicketMessage, + ShopInBitTicketMessage, + QAfterFilterCondition + > + textLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'text', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder< + ShopInBitTicketMessage, + ShopInBitTicketMessage, + QAfterFilterCondition + > + textBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'text', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder< + ShopInBitTicketMessage, + ShopInBitTicketMessage, + QAfterFilterCondition + > + textStartsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.startsWith( + property: r'text', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder< + ShopInBitTicketMessage, + ShopInBitTicketMessage, + QAfterFilterCondition + > + textEndsWith(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.endsWith( + property: r'text', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder< + ShopInBitTicketMessage, + ShopInBitTicketMessage, + QAfterFilterCondition + > + textContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.contains( + property: r'text', + value: value, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder< + ShopInBitTicketMessage, + ShopInBitTicketMessage, + QAfterFilterCondition + > + textMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.matches( + property: r'text', + wildcard: pattern, + caseSensitive: caseSensitive, + ), + ); + }); + } + + QueryBuilder< + ShopInBitTicketMessage, + ShopInBitTicketMessage, + QAfterFilterCondition + > + textIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'text', value: ''), + ); + }); + } + + QueryBuilder< + ShopInBitTicketMessage, + ShopInBitTicketMessage, + QAfterFilterCondition + > + textIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan(property: r'text', value: ''), + ); + }); + } + + QueryBuilder< + ShopInBitTicketMessage, + ShopInBitTicketMessage, + QAfterFilterCondition + > + timestampEqualTo(DateTime value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.equalTo(property: r'timestamp', value: value), + ); + }); + } + + QueryBuilder< + ShopInBitTicketMessage, + ShopInBitTicketMessage, + QAfterFilterCondition + > + timestampGreaterThan(DateTime value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.greaterThan( + include: include, + property: r'timestamp', + value: value, + ), + ); + }); + } + + QueryBuilder< + ShopInBitTicketMessage, + ShopInBitTicketMessage, + QAfterFilterCondition + > + timestampLessThan(DateTime value, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.lessThan( + include: include, + property: r'timestamp', + value: value, + ), + ); + }); + } + + QueryBuilder< + ShopInBitTicketMessage, + ShopInBitTicketMessage, + QAfterFilterCondition + > + timestampBetween( + DateTime lower, + DateTime upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition( + FilterCondition.between( + property: r'timestamp', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + ), + ); + }); + } +} + +extension ShopInBitTicketMessageQueryObject + on + QueryBuilder< + ShopInBitTicketMessage, + ShopInBitTicketMessage, + QFilterCondition + > {} diff --git a/lib/models/paynym/paynym_account.dart b/lib/models/paynym/paynym_account.dart index 23a29b8043..9950912235 100644 --- a/lib/models/paynym/paynym_account.dart +++ b/lib/models/paynym/paynym_account.dart @@ -37,24 +37,24 @@ class PaynymAccount { ); PaynymAccount.fromMap(Map map) - : nymID = map["nymID"] as String, - nymName = map["nymName"] as String, - segwit = map["segwit"] as bool, - codes = (map["codes"] as List) - .map((e) => PaynymCode.fromMap(Map.from(e as Map))) - .toList(), - followers = (map["followers"] as List) - .map( - (e) => PaynymAccountLite.fromMap( - Map.from(e as Map)), - ) - .toList(), - following = (map["following"] as List) - .map( - (e) => PaynymAccountLite.fromMap( - Map.from(e as Map)), - ) - .toList(); + : nymID = map["nymID"] as String, + nymName = map["nymName"] as String, + segwit = map["segwit"] as bool, + codes = (map["codes"] as List) + .map((e) => PaynymCode.fromMap(Map.from(e as Map))) + .toList(), + followers = (map["followers"] as List) + .map( + (e) => + PaynymAccountLite.fromMap(Map.from(e as Map)), + ) + .toList(), + following = (map["following"] as List) + .map( + (e) => + PaynymAccountLite.fromMap(Map.from(e as Map)), + ) + .toList(); PaynymAccount copyWith({ String? nymID, @@ -75,13 +75,13 @@ class PaynymAccount { } Map toMap() => { - "nymID": nymID, - "nymName": nymName, - "segwit": segwit, - "codes": codes.map((e) => e.toMap()), - "followers": followers.map((e) => e.toMap()), - "following": following.map((e) => e.toMap()), - }; + "nymID": nymID, + "nymName": nymName, + "segwit": segwit, + "codes": codes.map((e) => e.toMap()), + "followers": followers.map((e) => e.toMap()), + "following": following.map((e) => e.toMap()), + }; @override String toString() { diff --git a/lib/models/shopinbit/shopinbit_order_model.dart b/lib/models/shopinbit/shopinbit_order_model.dart new file mode 100644 index 0000000000..f41aa49e39 --- /dev/null +++ b/lib/models/shopinbit/shopinbit_order_model.dart @@ -0,0 +1,328 @@ +import 'package:flutter/foundation.dart'; + +import '../../services/shopinbit/src/models/ticket.dart'; +import '../isar/models/shopinbit_ticket.dart'; + +enum ShopInBitCategory { concierge, travel, car } + +enum ShopInBitOrderStatus { + pending, + reviewing, + offerAvailable, + accepted, + paymentPending, + paid, + shipping, + delivered, + closed, + cancelled, + refunded, +} + +class ShopInBitMessage { + final String text; + final DateTime timestamp; + final bool isFromUser; + + const ShopInBitMessage({ + required this.text, + required this.timestamp, + required this.isFromUser, + }); +} + +class ShopInBitOrderModel extends ChangeNotifier { + String _displayName = ""; + String get displayName => _displayName; + set displayName(String value) { + if (_displayName != value) { + _displayName = value; + notifyListeners(); + } + } + + bool _privacyAccepted = false; + bool get privacyAccepted => _privacyAccepted; + set privacyAccepted(bool value) { + if (_privacyAccepted != value) { + _privacyAccepted = value; + notifyListeners(); + } + } + + ShopInBitCategory? _category; + ShopInBitCategory? get category => _category; + set category(ShopInBitCategory? value) { + if (_category != value) { + _category = value; + notifyListeners(); + } + } + + bool _guidelinesAccepted = false; + bool get guidelinesAccepted => _guidelinesAccepted; + set guidelinesAccepted(bool value) { + if (_guidelinesAccepted != value) { + _guidelinesAccepted = value; + notifyListeners(); + } + } + + String _requestDescription = ""; + String get requestDescription => _requestDescription; + set requestDescription(String value) { + if (_requestDescription != value) { + _requestDescription = value; + notifyListeners(); + } + } + + String _deliveryCountry = ""; + String get deliveryCountry => _deliveryCountry; + set deliveryCountry(String value) { + if (_deliveryCountry != value) { + _deliveryCountry = value; + notifyListeners(); + } + } + + int _apiTicketId = 0; + int get apiTicketId => _apiTicketId; + set apiTicketId(int value) { + if (_apiTicketId != value) { + _apiTicketId = value; + notifyListeners(); + } + } + + String? _ticketId; + String? get ticketId => _ticketId; + set ticketId(String? value) { + if (_ticketId != value) { + _ticketId = value; + notifyListeners(); + } + } + + ShopInBitOrderStatus _status = ShopInBitOrderStatus.pending; + ShopInBitOrderStatus get status => _status; + set status(ShopInBitOrderStatus value) { + if (_status != value) { + _status = value; + notifyListeners(); + } + } + + String? _offerProductName; + String? get offerProductName => _offerProductName; + + String? _offerPrice; + String? get offerPrice => _offerPrice; + + void setOffer({required String productName, required String price}) { + _offerProductName = productName; + _offerPrice = price; + _status = ShopInBitOrderStatus.offerAvailable; + notifyListeners(); + } + + String _shippingName = ""; + String get shippingName => _shippingName; + + String _shippingStreet = ""; + String get shippingStreet => _shippingStreet; + + String _shippingCity = ""; + String get shippingCity => _shippingCity; + + String _shippingPostalCode = ""; + String get shippingPostalCode => _shippingPostalCode; + + String _shippingCountry = ""; + String get shippingCountry => _shippingCountry; + + void setShippingAddress({ + required String name, + required String street, + required String city, + required String postalCode, + required String country, + }) { + _shippingName = name; + _shippingStreet = street; + _shippingCity = city; + _shippingPostalCode = postalCode; + _shippingCountry = country; + notifyListeners(); + } + + String? _paymentMethod; + String? get paymentMethod => _paymentMethod; + set paymentMethod(String? value) { + if (_paymentMethod != value) { + _paymentMethod = value; + notifyListeners(); + } + } + + String? _carResearchInvoiceId; + String? get carResearchInvoiceId => _carResearchInvoiceId; + set carResearchInvoiceId(String? value) { + if (_carResearchInvoiceId != value) { + _carResearchInvoiceId = value; + notifyListeners(); + } + } + + String? _feeTicketNumber; + String? get feeTicketNumber => _feeTicketNumber; + set feeTicketNumber(String? value) { + if (_feeTicketNumber != value) { + _feeTicketNumber = value; + notifyListeners(); + } + } + + bool _needsCreateRequest = false; + bool get needsCreateRequest => _needsCreateRequest; + set needsCreateRequest(bool value) { + if (_needsCreateRequest != value) { + _needsCreateRequest = value; + notifyListeners(); + } + } + + bool _isPendingPayment = false; + bool get isPendingPayment => _isPendingPayment; + set isPendingPayment(bool value) { + if (_isPendingPayment != value) { + _isPendingPayment = value; + notifyListeners(); + } + } + + DateTime? _carResearchExpiresAt; + DateTime? get carResearchExpiresAt => _carResearchExpiresAt; + set carResearchExpiresAt(DateTime? value) { + if (_carResearchExpiresAt != value) { + _carResearchExpiresAt = value; + notifyListeners(); + } + } + + String? _carResearchPaymentLinks; + String? get carResearchPaymentLinks => _carResearchPaymentLinks; + set carResearchPaymentLinks(String? value) { + if (_carResearchPaymentLinks != value) { + _carResearchPaymentLinks = value; + notifyListeners(); + } + } + + List _messages = []; + List get messages => List.unmodifiable(_messages); + void addMessage(ShopInBitMessage message) { + _messages.add(message); + notifyListeners(); + } + + void clearMessages() { + _messages.clear(); + } + + ShopInBitTicket toIsarTicket() { + return ShopInBitTicket() + ..ticketId = _ticketId ?? "" + ..displayName = _displayName + ..category = _category ?? ShopInBitCategory.concierge + ..status = _status + ..requestDescription = _requestDescription + ..deliveryCountry = _deliveryCountry + ..offerProductName = _offerProductName + ..offerPrice = _offerPrice + ..shippingName = _shippingName + ..shippingStreet = _shippingStreet + ..shippingCity = _shippingCity + ..shippingPostalCode = _shippingPostalCode + ..shippingCountry = _shippingCountry + ..paymentMethod = _paymentMethod + ..apiTicketId = _apiTicketId + ..carResearchInvoiceId = _carResearchInvoiceId + ..feeTicketNumber = _feeTicketNumber + ..needsCreateRequest = _needsCreateRequest + ..isPendingPayment = _isPendingPayment + ..carResearchExpiresAt = _carResearchExpiresAt + ..carResearchPaymentLinks = _carResearchPaymentLinks + ..messages = _messages + .map( + (m) => ShopInBitTicketMessage() + ..text = m.text + ..timestamp = m.timestamp + ..isFromUser = m.isFromUser, + ) + .toList() + ..createdAt = DateTime.now(); + } + + static ShopInBitOrderModel fromIsarTicket(ShopInBitTicket ticket) { + return ShopInBitOrderModel() + .._displayName = ticket.displayName + .._category = ticket.category + .._apiTicketId = ticket.apiTicketId + .._ticketId = ticket.ticketId + .._status = ticket.status + .._requestDescription = ticket.requestDescription + .._deliveryCountry = ticket.deliveryCountry + .._offerProductName = ticket.offerProductName + .._offerPrice = ticket.offerPrice + .._shippingName = ticket.shippingName + .._shippingStreet = ticket.shippingStreet + .._shippingCity = ticket.shippingCity + .._shippingPostalCode = ticket.shippingPostalCode + .._shippingCountry = ticket.shippingCountry + .._paymentMethod = ticket.paymentMethod + .._carResearchInvoiceId = ticket.carResearchInvoiceId + .._feeTicketNumber = ticket.feeTicketNumber + .._needsCreateRequest = ticket.needsCreateRequest + .._isPendingPayment = ticket.isPendingPayment + .._carResearchExpiresAt = ticket.carResearchExpiresAt + .._carResearchPaymentLinks = ticket.carResearchPaymentLinks + .._messages = ticket.messages + .map( + (m) => ShopInBitMessage( + text: m.text, + timestamp: m.timestamp, + isFromUser: m.isFromUser, + ), + ) + .toList(); + } + + static ShopInBitOrderStatus statusFromTicketState(TicketState state) { + switch (state) { + case TicketState.newTicket: + return ShopInBitOrderStatus.pending; + case TicketState.checking: + case TicketState.inProgress: + case TicketState.replyNeeded: + return ShopInBitOrderStatus.reviewing; + case TicketState.offerAvailable: + return ShopInBitOrderStatus.offerAvailable; + case TicketState.clearing: + return ShopInBitOrderStatus.accepted; + case TicketState.pendingClose: + return ShopInBitOrderStatus.paymentPending; + case TicketState.shipped: + return ShopInBitOrderStatus.shipping; + case TicketState.fulfilled: + return ShopInBitOrderStatus.delivered; + case TicketState.closed: + case TicketState.merged: + return ShopInBitOrderStatus.closed; + case TicketState.closedCancelled: + return ShopInBitOrderStatus.cancelled; + case TicketState.refunded: + return ShopInBitOrderStatus.refunded; + } + } +} diff --git a/lib/networking/http.dart b/lib/networking/http.dart index 4771a10acc..efa997e64a 100644 --- a/lib/networking/http.dart +++ b/lib/networking/http.dart @@ -87,6 +87,65 @@ class HTTP { } } + Future patch({ + required Uri url, + Map? headers, + Object? body, + required ({InternetAddress host, int port})? proxyInfo, + }) async { + final httpClient = HttpClient(); + try { + if (proxyInfo != null) { + SocksTCPClient.assignToHttpClient(httpClient, [ + ProxySettings(proxyInfo.host, proxyInfo.port), + ]); + } + final HttpClientRequest request = await httpClient.patchUrl(url); + + if (headers != null) { + headers.forEach((key, value) => request.headers.add(key, value)); + } + + request.write(body); + + final response = await request.close(); + return Response(await _bodyBytes(response), response.statusCode); + } catch (e, s) { + Logging.instance.w("HTTP.patch() rethrew: ", error: e, stackTrace: s); + rethrow; + } finally { + httpClient.close(force: true); + } + } + + Future delete({ + required Uri url, + Map? headers, + required ({InternetAddress host, int port})? proxyInfo, + }) async { + final httpClient = HttpClient(); + try { + if (proxyInfo != null) { + SocksTCPClient.assignToHttpClient(httpClient, [ + ProxySettings(proxyInfo.host, proxyInfo.port), + ]); + } + final HttpClientRequest request = await httpClient.deleteUrl(url); + + if (headers != null) { + headers.forEach((key, value) => request.headers.add(key, value)); + } + + final response = await request.close(); + return Response(await _bodyBytes(response), response.statusCode); + } catch (e, s) { + Logging.instance.w("HTTP.delete() rethrew: ", error: e, stackTrace: s); + rethrow; + } finally { + httpClient.close(force: true); + } + } + Future _bodyBytes(HttpClientResponse response) { final completer = Completer(); final List bytes = []; diff --git a/lib/notifications/show_flush_bar.dart b/lib/notifications/show_flush_bar.dart index b955a41ec2..8bbd88cc20 100644 --- a/lib/notifications/show_flush_bar.dart +++ b/lib/notifications/show_flush_bar.dart @@ -8,8 +8,9 @@ * */ +import 'dart:async'; + import 'package:another_flushbar/flushbar.dart'; -import 'package:another_flushbar/flushbar_route.dart' as flushRoute; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; @@ -26,6 +27,9 @@ Future showFloatingFlushBar({ required BuildContext context, Duration? duration = const Duration(milliseconds: 1500), FlushbarPosition flushbarPosition = FlushbarPosition.TOP, + @Deprecated( + 'onTap is non-functional -- toasts are fully passive with IgnorePointer', + ) VoidCallback? onTap, }) { Color bg; @@ -45,34 +49,126 @@ Future showFloatingFlushBar({ break; } final bar = Flushbar( - onTap: (_) { - onTap?.call(); - }, + onTap: null, + isDismissible: false, icon: iconAsset != null - ? SvgPicture.asset( - iconAsset, - height: 16, - width: 16, - color: fg, - ) + ? SvgPicture.asset(iconAsset, height: 16, width: 16, color: fg) : null, message: message, messageColor: fg, flushbarPosition: flushbarPosition, backgroundColor: bg, - duration: duration, + duration: null, flushbarStyle: FlushbarStyle.FLOATING, - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), + borderRadius: BorderRadius.circular(Constants.size.circularBorderRadius), margin: const EdgeInsets.all(20), maxWidth: 550, ); - final _route = flushRoute.showFlushbar( - context: context, - flushbar: bar, + final completer = Completer(); + final overlay = Overlay.of(context, rootOverlay: true); + late final OverlayEntry entry; + entry = OverlayEntry( + builder: (context) => _OverlayFlushbar( + animationDuration: const Duration(seconds: 1), + displayDuration: duration, + forwardCurve: Curves.easeOutCirc, + reverseCurve: Curves.easeOutCirc, + initialAlignment: const Alignment(-1.0, -2.0), + endAlignment: const Alignment(-1.0, -1.0), + onDismiss: () { + entry.remove(); + if (!completer.isCompleted) { + completer.complete(); + } + }, + child: SafeArea( + child: Container(margin: const EdgeInsets.all(20), child: bar), + ), + ), ); + overlay.insert(entry); + return completer.future; +} + +class _OverlayFlushbar extends StatefulWidget { + const _OverlayFlushbar({ + required this.child, + required this.animationDuration, + required this.forwardCurve, + required this.reverseCurve, + required this.initialAlignment, + required this.endAlignment, + required this.onDismiss, + this.displayDuration, + }); + + final Widget child; + final Duration animationDuration; + final Duration? displayDuration; + final Curve forwardCurve; + final Curve reverseCurve; + final Alignment initialAlignment; + final Alignment endAlignment; + final VoidCallback onDismiss; + + @override + State<_OverlayFlushbar> createState() => _OverlayFlushbarState(); +} - return Navigator.of(context, rootNavigator: true).push(_route); +class _OverlayFlushbarState extends State<_OverlayFlushbar> + with SingleTickerProviderStateMixin { + late final AnimationController _controller; + late final Animation _animation; + Timer? _timer; + bool _dismissed = false; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: widget.animationDuration, + vsync: this, + ); + _animation = + AlignmentTween( + begin: widget.initialAlignment, + end: widget.endAlignment, + ).animate( + CurvedAnimation( + parent: _controller, + curve: widget.forwardCurve, + reverseCurve: widget.reverseCurve, + ), + ); + _controller.forward(); + if (widget.displayDuration != null) { + _timer = Timer(widget.displayDuration!, _dismiss); + } + } + + void _dismiss() { + if (_dismissed) return; + _dismissed = true; + _controller.reverse().then((_) { + if (mounted) { + widget.onDismiss(); + } + }); + } + + @override + void dispose() { + _timer?.cancel(); + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AlignTransition( + alignment: _animation, + child: IgnorePointer(child: widget.child), + ); + } } diff --git a/lib/pages/already_running_view.dart b/lib/pages/already_running_view.dart new file mode 100644 index 0000000000..1678276e55 --- /dev/null +++ b/lib/pages/already_running_view.dart @@ -0,0 +1,191 @@ +/* + * This file is part of Stack Wallet. + * + * Copyright (c) 2023 Cypher Stack + * All Rights Reserved. + * The code is distributed under GPLv3 license, see LICENSE file for details. + * Generated by Cypher Stack on 2023-05-26 + * + */ + +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:google_fonts/google_fonts.dart'; + +import '../app_config.dart'; +import '../themes/stack_colors.dart'; +import '../themes/theme_providers.dart'; +import '../themes/theme_service.dart'; +import '../utilities/stack_file_system.dart'; +import '../utilities/text_styles.dart'; +import '../utilities/util.dart'; +import '../widgets/app_icon.dart'; +import '../widgets/background.dart'; + +/// Root app widget for the "already running" error path. +/// +/// Mirrors the theme bootstrap performed by [MaterialAppWithTheme] in main.dart +/// but without touching Hive. Requires Isar + ThemeService to already be +/// initialized before [runApp] is called. +class AlreadyRunningApp extends ConsumerStatefulWidget { + const AlreadyRunningApp({super.key}); + + @override + ConsumerState createState() => _AlreadyRunningAppState(); +} + +class _AlreadyRunningAppState extends ConsumerState { + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(applicationThemesDirectoryPathProvider.notifier).state = + StackFileSystem.themesDir!.path; + // The first instance already verified/installed the light theme, so + // getTheme cannot return null here. + ref.read(themeProvider.state).state = ref + .read(pThemeService) + .getTheme(themeId: "light")!; + }); + } + + @override + Widget build(BuildContext context) { + final colorScheme = ref.watch(colorProvider.state).state; + return MaterialApp( + debugShowCheckedModeBanner: false, + title: AppConfig.appName, + theme: ThemeData( + extensions: [colorScheme], + fontFamily: GoogleFonts.inter().fontFamily, + splashColor: Colors.transparent, + ), + home: const AlreadyRunningView(), + ); + } +} + +/// Error screen shown when this is a second instance of the app. +/// +/// Mirrors [IntroView]'s layout: themed background, logo, app name heading, +/// short description subtitle, then the error message (in label style, smaller +/// than the subtitle) in place of the action buttons. +class AlreadyRunningView extends ConsumerWidget { + const AlreadyRunningView({super.key}); + + static const _errorMessage = + "${AppConfig.appName} is already running. " + "Close the other window and try again."; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final isDesktop = Util.isDesktop; + final colors = Theme.of(context).extension()!; + final stack = ref.watch( + themeProvider.select((value) => value.assets.stack), + ); + + return Background( + child: Scaffold( + backgroundColor: colors.background, + body: SafeArea( + child: Center( + child: !isDesktop + ? Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Spacer(flex: 2), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 300), + child: SizedBox( + width: 266, + height: 266, + child: stack.endsWith(".png") + ? Image.file(File(stack)) + : SvgPicture.file( + File(stack), + width: 266, + height: 266, + ), + ), + ), + ), + const Spacer(flex: 1), + Text( + AppConfig.appName, + textAlign: TextAlign.center, + style: STextStyles.pageTitleH1(context), + ), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 48), + child: Text( + AppConfig.shortDescriptionText, + textAlign: TextAlign.center, + style: STextStyles.subtitle(context), + ), + ), + const Spacer(flex: 4), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 16, + ), + child: Text( + _errorMessage, + textAlign: TextAlign.center, + style: STextStyles.label(context), + ), + ), + ], + ) + : SizedBox( + width: 350, + height: 540, + child: Column( + children: [ + const Spacer(flex: 2), + const SizedBox( + width: 130, + height: 130, + child: AppIcon(), + ), + const Spacer(flex: 42), + Text( + AppConfig.appName, + textAlign: TextAlign.center, + style: STextStyles.pageTitleH1( + context, + ).copyWith(fontSize: 40), + ), + const Spacer(flex: 24), + Text( + AppConfig.shortDescriptionText, + textAlign: TextAlign.center, + style: STextStyles.subtitle( + context, + ).copyWith(fontSize: 24), + ), + const Spacer(flex: 42), + Text( + _errorMessage, + textAlign: TextAlign.center, + style: STextStyles.label( + context, + ).copyWith(fontSize: 18), + ), + const Spacer(flex: 65), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/cakepay/cakepay_card_detail_view.dart b/lib/pages/cakepay/cakepay_card_detail_view.dart new file mode 100644 index 0000000000..7fbd0ebff9 --- /dev/null +++ b/lib/pages/cakepay/cakepay_card_detail_view.dart @@ -0,0 +1,640 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../../services/cakepay/cakepay_service.dart'; +import '../../services/cakepay/src/models/card.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/constants.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../widgets/background.dart'; +import '../../widgets/conditional_parent.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../../widgets/desktop/secondary_button.dart'; +import '../../widgets/rounded_white_container.dart'; +import '../../widgets/stack_dialog.dart'; +import '../../widgets/stack_text_field.dart'; +import 'cakepay_order_view.dart'; + +class CakePayCardDetailView extends StatefulWidget { + const CakePayCardDetailView({super.key, required this.card}); + + static const String routeName = "/cakePayCardDetail"; + + final CakePayCard card; + + @override + State createState() => _CakePayCardDetailViewState(); +} + +class _CakePayCardDetailViewState extends State { + late CakePayCard _card; + bool _purchasing = false; + double? _selectedDenomination; + int _quantity = 1; + bool _termsAccepted = false; + final _customAmountController = TextEditingController(); + final _customAmountFocusNode = FocusNode(); + final _emailController = TextEditingController(); + final _emailFocusNode = FocusNode(); + + @override + void initState() { + super.initState(); + _card = widget.card; + if (_card.isFixedDenomination && _card.denominations.isNotEmpty) { + _selectedDenomination = _card.denominations.first; + } + _emailFocusNode.addListener(() { + setState(() {}); + }); + } + + @override + void dispose() { + _customAmountController.dispose(); + _customAmountFocusNode.dispose(); + _emailController.dispose(); + _emailFocusNode.dispose(); + super.dispose(); + } + + String get _priceString { + if (_card.isFixedDenomination && _selectedDenomination != null) { + return _selectedDenomination!.toStringAsFixed(2); + } + return _customAmountController.text.trim(); + } + + bool get _canPurchase { + if (!_termsAccepted || _purchasing) return false; + if (_emailController.text.trim().isEmpty) return false; + final price = _priceString; + if (price.isEmpty) return false; + final parsed = double.tryParse(price); + if (parsed == null || parsed <= 0) return false; + if (_card.isRangeDenomination) { + if (_card.minValue != null && parsed < _card.minValue!) return false; + if (_card.maxValue != null && parsed > _card.maxValue!) return false; + } + return true; + } + + Future _showOpenBrowserWarning(String url) async { + final uri = Uri.parse(url); + final shouldContinue = await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => Util.isDesktop + ? DesktopDialog( + maxWidth: 550, + maxHeight: 250, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 20, + ), + child: Column( + children: [ + Text("Attention", style: STextStyles.desktopH2(context)), + const SizedBox(height: 16), + Text( + "You are about to open " + "${uri.scheme}://${uri.host} " + "in your browser.", + style: STextStyles.desktopTextSmall(context), + ), + const SizedBox(height: 35), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 200, + buttonHeight: ButtonHeight.l, + label: "Cancel", + onPressed: () { + Navigator.of( + context, + rootNavigator: true, + ).pop(false); + }, + ), + const SizedBox(width: 20), + PrimaryButton( + width: 200, + buttonHeight: ButtonHeight.l, + label: "Continue", + onPressed: () { + Navigator.of( + context, + rootNavigator: true, + ).pop(true); + }, + ), + ], + ), + ], + ), + ), + ) + : StackDialog( + title: "Attention", + message: + "You are about to open " + "${uri.scheme}://${uri.host} " + "in your browser.", + leftButton: TextButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: Text( + "Cancel", + style: STextStyles.button(context).copyWith( + color: Theme.of( + context, + ).extension()!.accentColorDark, + ), + ), + ), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonStyle(context), + onPressed: () { + Navigator.of(context).pop(true); + }, + child: Text("Continue", style: STextStyles.button(context)), + ), + ), + ); + return shouldContinue ?? false; + } + + Future _openTerms() async { + const url = "https://cakepay.com/terms/"; + if (await _showOpenBrowserWarning(url)) { + await launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication); + } + } + + Future _purchase() async { + if (!_canPurchase) return; + setState(() => _purchasing = true); + + final resp = await CakePayService.instance.client.createOrder( + cardId: _card.id, + price: _priceString, + quantity: _quantity > 1 ? _quantity : null, + userEmail: _emailController.text.trim(), + confirmsNoVpn: true, + confirmsVoidedRefund: true, + confirmsTermsAgreed: true, + ); + + if (mounted) { + setState(() => _purchasing = false); + if (!resp.hasError && resp.value != null) { + final order = resp.value!; + + // Track order ID locally so the orders list view can fetch it + // via getOrder() without requiring Knox user auth. + CakePayService.instance.addOrderId(order.orderId); + + if (Util.isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + await showDialog( + context: context, + builder: (_) => CakePayOrderView(orderId: order.orderId), + ); + } else { + await Navigator.of(context).pushReplacementNamed( + CakePayOrderView.routeName, + arguments: order.orderId, + ); + } + } else { + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return StackDialog( + title: "Purchase failed", + message: resp.exception?.message ?? "Failed to create order", + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: Theme.of( + context, + ).extension()!.buttonTextSecondary, + ), + ), + onPressed: () => Navigator.of(context).pop(), + ), + ); + }, + ); + } + } + } + + @override + Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + final card = _card; + + final denominationSelector = card.isFixedDenomination + ? Wrap( + spacing: 8, + runSpacing: 8, + children: card.denominations.map((d) { + final selected = d == _selectedDenomination; + return ChoiceChip( + label: Text( + "${d.toStringAsFixed(0)} ${card.currencyCode ?? ''}", + style: + (isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context)) + .copyWith( + color: selected + ? Theme.of( + context, + ).extension()!.textDark + : null, + ), + ), + selected: selected, + onSelected: (val) { + if (val) setState(() => _selectedDenomination = d); + }, + ); + }).toList(), + ) + : card.isRangeDenomination + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Enter amount (${card.minValue?.toStringAsFixed(0) ?? '?'} - " + "${card.maxValue?.toStringAsFixed(0) ?? '?'} " + "${card.currencyCode ?? ''})", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + const SizedBox(height: 8), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + controller: _customAmountController, + focusNode: _customAmountFocusNode, + keyboardType: const TextInputType.numberWithOptions( + decimal: true, + ), + onChanged: (_) => setState(() {}), + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + ), + decoration: + standardInputDecoration( + "Amount", + _customAmountFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + filled: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + ), + ), + ], + ) + : const SizedBox.shrink(); + + final quantityRow = Row( + children: [ + Text( + "Quantity", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + const Spacer(), + IconButton( + icon: const Icon(Icons.remove_circle_outline, size: 20), + onPressed: _quantity > 1 ? () => setState(() => _quantity--) : null, + ), + Text( + "$_quantity", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + IconButton( + icon: const Icon(Icons.add_circle_outline, size: 20), + onPressed: () => setState(() => _quantity++), + ), + ], + ); + + final termsCheckbox = GestureDetector( + onTap: () => setState(() => _termsAccepted = !_termsAccepted), + child: Container( + color: Colors.transparent, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20, + height: 26, + child: IgnorePointer( + child: Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: _termsAccepted, + onChanged: (_) {}, + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: RichText( + text: TextSpan( + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.w500_14(context), + children: [ + const TextSpan(text: "I agree to the "), + TextSpan( + text: "terms and conditions", + style: STextStyles.richLink( + context, + ).copyWith(fontSize: isDesktop ? null : 14), + recognizer: TapGestureRecognizer()..onTap = _openTerms, + ), + const TextSpan( + text: + ", confirm I am not using a VPN, " + "and understand refunds are voided. " + "I understand that the gift card " + "will be delivered to the listed " + "email.", + ), + ], + ), + ), + ), + ], + ), + ), + ); + + final content = SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (card.cardImageUrl != null) + Center( + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.network( + card.cardImageUrl!, + width: isDesktop ? 200 : 150, + fit: BoxFit.contain, + errorBuilder: (_, __, ___) => + Icon(Icons.card_giftcard, size: isDesktop ? 80 : 60), + ), + ), + ), + SizedBox(height: isDesktop ? 16 : 12), + Text( + card.name, + style: isDesktop + ? STextStyles.desktopH2(context) + : STextStyles.pageTitleH1(context), + ), + if (card.description != null && card.description!.isNotEmpty) ...[ + SizedBox(height: isDesktop ? 16 : 12), + RoundedWhiteContainer( + child: Text( + card.description!, + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + ), + ], + if (card.howToUse != null && card.howToUse!.isNotEmpty) ...[ + SizedBox(height: isDesktop ? 16 : 12), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "How to use", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + const SizedBox(height: 8), + Text( + card.howToUse!, + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + ], + if (card.termsAndConditions != null && + card.termsAndConditions!.isNotEmpty) ...[ + SizedBox(height: isDesktop ? 16 : 12), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Terms & conditions", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + const SizedBox(height: 8), + Text( + card.termsAndConditions!, + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + ], + if (card.expiryAndValidity != null && + card.expiryAndValidity!.isNotEmpty) ...[ + SizedBox(height: isDesktop ? 16 : 12), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Expiry & validity", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + const SizedBox(height: 8), + Text( + card.expiryAndValidity!, + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + ], + SizedBox(height: isDesktop ? 24 : 16), + denominationSelector, + SizedBox(height: isDesktop ? 16 : 12), + quantityRow, + SizedBox(height: isDesktop ? 16 : 12), + termsCheckbox, + SizedBox(height: isDesktop ? 16 : 12), + Text( + "Email for receipt and delivery", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + const SizedBox(height: 8), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + controller: _emailController, + focusNode: _emailFocusNode, + autocorrect: false, + enableSuggestions: false, + keyboardType: TextInputType.emailAddress, + onChanged: (_) => setState(() {}), + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + ), + decoration: + standardInputDecoration( + "Email", + _emailFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + filled: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + ), + ), + SizedBox(height: isDesktop ? 24 : 16), + PrimaryButton( + label: _purchasing ? "Processing..." : "Purchase", + enabled: _canPurchase, + onPressed: _canPurchase ? _purchase : null, + ), + ], + ), + ); + + return _scaffold(isDesktop: isDesktop, child: content); + } + + Widget _scaffold({required bool isDesktop, required Widget child}) { + return ConditionalParent( + condition: isDesktop, + builder: (child) => DesktopDialog( + maxWidth: 580, + maxHeight: 700, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Gift Card", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 8, + ), + child: child, + ), + ), + ], + ), + ), + child: ConditionalParent( + condition: !isDesktop, + builder: (child) => Background( + child: Scaffold( + backgroundColor: Theme.of( + context, + ).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () => Navigator.of(context).pop(), + ), + title: Text("Gift Card", style: STextStyles.navBarTitle(context)), + ), + body: SafeArea( + child: Padding(padding: const EdgeInsets.all(16), child: child), + ), + ), + ), + child: child, + ), + ); + } +} diff --git a/lib/pages/cakepay/cakepay_confirm_send_view.dart b/lib/pages/cakepay/cakepay_confirm_send_view.dart new file mode 100644 index 0000000000..41ea7a14bf --- /dev/null +++ b/lib/pages/cakepay/cakepay_confirm_send_view.dart @@ -0,0 +1,625 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../models/isar/models/isar_models.dart'; +import '../../notifications/show_flush_bar.dart'; +import '../../pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart'; +import '../../providers/providers.dart'; +import '../../route_generator.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/amount/amount_formatter.dart'; +import '../../utilities/constants.dart'; +import '../../utilities/logger.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../wallets/models/tx_data.dart'; +import '../../widgets/background.dart'; +import '../../widgets/conditional_parent.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../../widgets/desktop/secondary_button.dart'; +import '../../widgets/rounded_container.dart'; +import '../../widgets/rounded_white_container.dart'; +import '../../widgets/stack_dialog.dart'; +import '../pinpad_views/lock_screen_view.dart'; +import '../send_view/sub_widgets/sending_transaction_dialog.dart'; +import '../wallet_view/wallet_view.dart'; + +class CakePayConfirmSendView extends ConsumerStatefulWidget { + const CakePayConfirmSendView({ + super.key, + required this.txData, + required this.walletId, + this.routeOnSuccessName = WalletView.routeName, + required this.orderId, + }); + + static const String routeName = "/cakePayConfirmSend"; + + final TxData txData; + final String walletId; + final String routeOnSuccessName; + final String orderId; + + @override + ConsumerState createState() => + _CakePayConfirmSendViewState(); +} + +class _CakePayConfirmSendViewState + extends ConsumerState { + late final String walletId; + late final String routeOnSuccessName; + + final isDesktop = Util.isDesktop; + + Future _attemptSend(BuildContext context) async { + final parentWallet = ref.read(pWallets).getWallet(walletId); + final coin = parentWallet.info.coin; + + final sendProgressController = ProgressAndSuccessController(); + + unawaited( + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return SendingTransactionDialog( + coin: coin, + controller: sendProgressController, + ); + }, + ), + ); + + final time = Future.delayed(const Duration(milliseconds: 2500)); + + late String txid; + final String note = widget.txData.note ?? ""; + + try { + final txidFuture = parentWallet.confirmSend(txData: widget.txData); + + unawaited(parentWallet.refresh()); + + final results = await Future.wait([txidFuture, time]); + + sendProgressController.triggerSuccess?.call(); + await Future.delayed(const Duration(seconds: 5)); + + txid = (results.first as TxData).txid!; + + await ref + .read(mainDBProvider) + .putTransactionNote( + TransactionNote(walletId: walletId, txid: txid, value: note), + ); + + if (context.mounted) { + // pop sending dialog (pushed via showDialog which uses root navigator) + Navigator.of(context, rootNavigator: true).pop(); + + if (Util.isDesktop) { + // pop the confirm send desktop dialog + Navigator.of(context, rootNavigator: true).pop(); + } + + Navigator.of(context).popUntil(ModalRoute.withName(routeOnSuccessName)); + + if (context.mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "Payment sent! Check order status for updates.", + context: context, + ), + ); + } + } + } catch (e, s) { + Logging.instance.e( + "Broadcast transaction failed: ", + error: e, + stackTrace: s, + ); + + if (context.mounted) { + Navigator.of(context, rootNavigator: true).pop(); + + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return StackDialog( + title: "Broadcast transaction failed", + message: e.toString(), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: Theme.of( + context, + ).extension()!.buttonTextSecondary, + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ); + }, + ); + } + } + } + + Future _confirmSend() async { + final dynamic unlocked; + + final coin = ref.read(pWalletCoin(walletId)); + + if (Util.isDesktop) { + unlocked = await showDialog( + context: context, + builder: (context) => DesktopDialog( + maxWidth: 580, + maxHeight: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [DesktopDialogCloseButton()], + ), + Padding( + padding: const EdgeInsets.only(left: 32, right: 32, bottom: 32), + child: DesktopAuthSend(coin: coin), + ), + ], + ), + ), + ); + } else { + unlocked = await Navigator.push( + context, + RouteGenerator.getRoute( + shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, + builder: (_) => const LockscreenView( + showBackButton: true, + popOnSuccess: true, + routeOnSuccessArguments: true, + routeOnSuccess: "", + biometricsCancelButtonString: "CANCEL", + biometricsLocalizedReason: "Authenticate to send transaction", + biometricsAuthenticationTitle: "Confirm Transaction", + ), + settings: const RouteSettings(name: "/confirmsendlockscreen"), + ), + ); + } + + if (unlocked is bool && mounted) { + if (unlocked) { + await _attemptSend(context); + } else { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Invalid passphrase", + context: context, + ), + ); + } + } + } + + @override + void initState() { + walletId = widget.walletId; + routeOnSuccessName = widget.routeOnSuccessName; + super.initState(); + } + + @override + Widget build(BuildContext context) { + final coin = ref.watch(pWalletCoin(walletId)); + + return ConditionalParent( + condition: !isDesktop, + builder: (child) { + return Background( + child: Scaffold( + backgroundColor: Theme.of( + context, + ).extension()!.background, + appBar: AppBar( + backgroundColor: Theme.of( + context, + ).extension()!.backgroundAppBar, + leading: AppBarBackButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Confirm transaction", + style: STextStyles.navBarTitle(context), + ), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (builderContext, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, + ), + ), + ), + ), + ); + }, + ), + ), + ), + ); + }, + child: ConditionalParent( + condition: isDesktop, + builder: (child) => DesktopDialog( + maxHeight: double.infinity, + maxWidth: 580, + child: Column( + children: [ + Row( + children: [ + const SizedBox(width: 6), + const AppBarBackButton(isCompact: true, iconSize: 23), + const SizedBox(width: 12), + Text( + "Confirm ${coin.ticker} transaction", + style: STextStyles.desktopH3(context), + ), + ], + ), + Padding( + padding: const EdgeInsets.only(left: 32, right: 32, bottom: 32), + child: Column( + children: [ + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + borderColor: Theme.of( + context, + ).extension()!.background, + child: child, + ), + const SizedBox(height: 16), + Row( + children: [ + Text( + "Transaction fee", + style: STextStyles.desktopTextExtraExtraSmall( + context, + ), + ), + ], + ), + const SizedBox(height: 10), + RoundedContainer( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + ref + .watch(pAmountFormatter(coin)) + .format(widget.txData.fee!), + style: + STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: Theme.of( + context, + ).extension()!.textDark, + ), + ), + ], + ), + ), + const SizedBox(height: 16), + RoundedContainer( + color: Theme.of( + context, + ).extension()!.snackBarBackSuccess, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Total amount", + style: STextStyles.titleBold12(context).copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ), + ), + Builder( + builder: (context) { + final fee = widget.txData.fee!; + final amount = widget.txData.amountWithoutChange!; + final total = amount + fee; + return Text( + ref.watch(pAmountFormatter(coin)).format(total), + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ), + textAlign: TextAlign.right, + ); + }, + ), + ], + ), + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox(width: 16), + Expanded( + child: PrimaryButton( + label: "Send", + buttonHeight: isDesktop ? ButtonHeight.l : null, + onPressed: _confirmSend, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ConditionalParent( + condition: isDesktop, + builder: (child) => Container( + decoration: BoxDecoration( + color: Theme.of(context).extension()!.background, + borderRadius: BorderRadius.vertical( + top: Radius.circular(Constants.size.circularBorderRadius), + ), + ), + child: Padding( + padding: const EdgeInsets.all(12), + child: Row(children: [child]), + ), + ), + child: Text( + "Send ${coin.ticker}", + style: isDesktop + ? STextStyles.desktopTextMedium(context) + : STextStyles.pageTitleH1(context), + ), + ), + isDesktop + ? Container( + color: Theme.of( + context, + ).extension()!.background, + height: 1, + ) + : const SizedBox(height: 12), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text("Send from", style: STextStyles.smallMed12(context)), + const SizedBox(height: 4), + Text( + ref.watch(pWalletName(walletId)), + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + isDesktop + ? Container( + color: Theme.of( + context, + ).extension()!.background, + height: 1, + ) + : const SizedBox(height: 12), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "CakePay address", + style: STextStyles.smallMed12(context), + ), + const SizedBox(height: 4), + Text( + widget.txData.recipients!.first.address, + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + isDesktop + ? Container( + color: Theme.of( + context, + ).extension()!.background, + height: 1, + ) + : const SizedBox(height: 12), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Amount", style: STextStyles.smallMed12(context)), + Text( + ref + .watch(pAmountFormatter(coin)) + .format(widget.txData.amountWithoutChange!), + style: STextStyles.itemSubtitle12(context), + textAlign: TextAlign.right, + ), + ], + ), + ), + isDesktop + ? Container( + color: Theme.of( + context, + ).extension()!.background, + height: 1, + ) + : const SizedBox(height: 12), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Transaction fee", + style: STextStyles.smallMed12(context), + ), + Text( + ref + .watch(pAmountFormatter(coin)) + .format(widget.txData.fee!), + style: STextStyles.itemSubtitle12(context), + textAlign: TextAlign.right, + ), + ], + ), + ), + isDesktop + ? Container( + color: Theme.of( + context, + ).extension()!.background, + height: 1, + ) + : const SizedBox(height: 12), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text("Note", style: STextStyles.smallMed12(context)), + const SizedBox(height: 4), + Text( + widget.txData.note ?? "", + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + isDesktop + ? Container( + color: Theme.of( + context, + ).extension()!.background, + height: 1, + ) + : const SizedBox(height: 12), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Order ID", style: STextStyles.smallMed12(context)), + Text( + widget.orderId.length > 8 + ? "${widget.orderId.substring(0, 8)}..." + : widget.orderId, + style: STextStyles.itemSubtitle12(context), + textAlign: TextAlign.right, + ), + ], + ), + ), + if (!isDesktop) const SizedBox(height: 12), + if (!isDesktop) + RoundedContainer( + color: Theme.of( + context, + ).extension()!.snackBarBackSuccess, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Total amount", + style: STextStyles.titleBold12(context).copyWith( + color: Theme.of( + context, + ).extension()!.textConfirmTotalAmount, + ), + ), + Builder( + builder: (context) { + final fee = widget.txData.fee!; + final amount = widget.txData.amountWithoutChange!; + final total = amount + fee; + return Text( + ref.watch(pAmountFormatter(coin)).format(total), + style: STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of( + context, + ).extension()!.textConfirmTotalAmount, + ), + textAlign: TextAlign.right, + ); + }, + ), + ], + ), + ), + if (!isDesktop) const SizedBox(height: 16), + if (!isDesktop) const Spacer(), + if (!isDesktop) + PrimaryButton( + label: "Send", + buttonHeight: isDesktop ? ButtonHeight.l : null, + onPressed: _confirmSend, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/cakepay/cakepay_order_view.dart b/lib/pages/cakepay/cakepay_order_view.dart new file mode 100644 index 0000000000..71c9fe89a9 --- /dev/null +++ b/lib/pages/cakepay/cakepay_order_view.dart @@ -0,0 +1,989 @@ +import 'dart:async'; + +import 'package:decimal/decimal.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../app_config.dart'; +import '../../notifications/show_flush_bar.dart'; +import '../../providers/providers.dart'; +import '../../route_generator.dart'; +import '../../services/cakepay/cakepay_service.dart'; +import '../../services/cakepay/src/models/order.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/amount/amount.dart'; +import '../../utilities/assets.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../wallets/crypto_currency/crypto_currency.dart'; +import '../../widgets/background.dart'; +import '../../widgets/conditional_parent.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../../widgets/qr.dart'; +import '../../widgets/rounded_white_container.dart'; +import 'cakepay_send_from_view.dart'; + +class CakePayOrderView extends ConsumerStatefulWidget { + const CakePayOrderView({super.key, required this.orderId}); + + static const String routeName = "/cakePayOrder"; + + final String orderId; + + @override + ConsumerState createState() => _CakePayOrderViewState(); +} + +class _CakePayOrderViewState extends ConsumerState { + CakePayOrder? _order; + bool _loading = true; + Timer? _pollTimer; + Timer? _countdownTimer; + Duration _timeRemaining = Duration.zero; + int _selectedPaymentMethod = 0; + + @override + void initState() { + super.initState(); + _loadOrder(); + _pollTimer = Timer.periodic( + const Duration(seconds: 15), + (_) => _loadOrder(), + ); + } + + @override + void dispose() { + _pollTimer?.cancel(); + _countdownTimer?.cancel(); + super.dispose(); + } + + void _startCountdown() { + _countdownTimer?.cancel(); + _updateTimeRemaining(); + _countdownTimer = Timer.periodic( + const Duration(seconds: 1), + (_) => _updateTimeRemaining(), + ); + } + + void _updateTimeRemaining() { + if (_order?.expirationTime == null) return; + final expiresAt = DateTime.fromMillisecondsSinceEpoch( + _order!.expirationTime!, + ); + final remaining = expiresAt.difference(DateTime.now()); + if (mounted) { + setState(() { + _timeRemaining = remaining.isNegative ? Duration.zero : remaining; + }); + } + if (remaining.isNegative) { + _countdownTimer?.cancel(); + } + } + + String _formatDuration(Duration d) { + if (d.isNegative || d == Duration.zero) return "Expired"; + final minutes = d.inMinutes; + final seconds = d.inSeconds % 60; + if (d.inHours > 0) { + return "${d.inHours}h ${minutes % 60}m ${seconds}s"; + } + return "${minutes}m ${seconds}s"; + } + + void _navigateToSendFrom({ + required CryptoCurrency coin, + required Amount? amount, + required String address, + required String orderId, + }) { + final isDesktop = Util.isDesktop; + if (isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + showDialog( + context: context, + builder: (_) => CakePaySendFromView( + coin: coin, + amount: amount, + address: address, + orderId: orderId, + shouldPopRoot: true, + ), + ); + } else { + Navigator.of(context).push( + RouteGenerator.getRoute( + shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, + builder: (_) => CakePaySendFromView( + coin: coin, + amount: amount, + address: address, + orderId: orderId, + ), + settings: const RouteSettings(name: CakePaySendFromView.routeName), + ), + ); + } + } + + /// Resolve an API ticker (e.g. "LTC_MWEB") to a Stack Wallet coin, + /// falling back to the base ticker before "_" if the full one isn't + /// recognised. + CryptoCurrency? _resolveCoin(String apiTicker) { + final ticker = apiTicker.toUpperCase(); + var coin = AppConfig.getCryptoCurrencyForTicker(ticker); + if (coin == null && ticker.contains('_') && !ticker.endsWith('_LN')) { + coin = AppConfig.getCryptoCurrencyForTicker(ticker.split('_').first); + } + return coin; + } + + /// Pretty-print an API ticker for display. + String _tickerLabel(String apiTicker) { + switch (apiTicker.toUpperCase()) { + case 'BTC_LN': + return 'BTC (LN)'; + case 'LTC_MWEB': + return 'LTC (MWEB)'; + default: + return apiTicker.toUpperCase(); + } + } + + void _payWithOption(CakePayPaymentOption option, String orderId) { + final label = _tickerLabel(option.ticker); + final coin = _resolveCoin(option.ticker); + + if (option.address.trim().isEmpty) { + showFloatingFlushBar( + type: FlushBarType.warning, + message: "No payment address available for $label", + context: context, + ); + return; + } + + if (coin == null) { + showFloatingFlushBar( + type: FlushBarType.warning, + message: "No wallet support for $label", + context: context, + ); + return; + } + + final hasWallet = ref + .read(pWallets) + .wallets + .any((w) => w.info.coin == coin); + + if (!hasWallet) { + showFloatingFlushBar( + type: FlushBarType.warning, + message: "No $label wallet found. Create one first.", + context: context, + ); + return; + } + + Amount? amount; + try { + amount = Amount.fromDecimal( + Decimal.parse(option.amountFrom.toString()), + fractionDigits: coin.fractionDigits, + ); + } catch (_) {} + + _navigateToSendFrom( + coin: coin, + amount: amount, + address: option.address, + orderId: orderId, + ); + } + + Future _loadOrder() async { + final resp = await CakePayService.instance.client.getOrder(widget.orderId); + if (mounted) { + setState(() { + _loading = false; + if (!resp.hasError && resp.value != null) { + var order = resp.value!; + final override = CakePayService.devStatusOverrides[order.orderId]; + if (override != null) { + order = order.copyWith(status: override); + } + _order = order; + if (_isTerminal(_order!.status)) { + _pollTimer?.cancel(); + _countdownTimer?.cancel(); + } else if (_order!.expirationTime != null) { + _startCountdown(); + } + } + }); + } + } + + bool _isTerminal(CakePayOrderStatus status) { + return status == CakePayOrderStatus.complete || + status == CakePayOrderStatus.expired || + status == CakePayOrderStatus.failed || + status == CakePayOrderStatus.refunded; + } + + /// Whether the order has received payment and is being processed or + /// is already complete. Payment UI should be hidden for these. + bool _isPaidOrBeyond(CakePayOrderStatus status) { + return const { + CakePayOrderStatus.paid, + CakePayOrderStatus.pendingPurchase, + CakePayOrderStatus.purchaseProcessing, + CakePayOrderStatus.purchased, + CakePayOrderStatus.pendingEmail, + CakePayOrderStatus.complete, + }.contains(status); + } + + /// Whether payment UI (tabs, QR, address, pay button) should be shown. + bool _showPaymentUI(CakePayOrderStatus status) { + return !_isPaidOrBeyond(status) && + status != CakePayOrderStatus.expired && + status != CakePayOrderStatus.failed && + status != CakePayOrderStatus.pendingRefund && + status != CakePayOrderStatus.refunded; + } + + /// Copyable order ID and created-at timestamp for terminal state banners. + List _orderInfoWidgets(CakePayOrder order, bool isDesktop) { + final subtitleStyle = isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context); + + return [ + // Copyable order ID. + RoundedWhiteContainer( + child: GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: order.orderId)); + showFloatingFlushBar( + type: FlushBarType.info, + message: "Order ID copied", + iconAsset: Assets.svg.copy, + context: context, + ); + }, + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Order ID", style: subtitleStyle), + const SizedBox(height: 4), + Text( + order.orderId, + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + ], + ), + ), + Icon( + Icons.copy, + size: 14, + color: Theme.of( + context, + ).extension()!.accentColorBlue, + ), + ], + ), + ), + ), + // Created-at timestamp. + if (order.createdAt != null) ...[ + SizedBox(height: isDesktop ? 8 : 6), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Created", style: subtitleStyle), + Text(order.createdAt!, style: subtitleStyle), + ], + ), + ), + ], + ]; + } + + String _statusLabel(CakePayOrderStatus status) { + switch (status) { + case CakePayOrderStatus.new_: + return "New"; + case CakePayOrderStatus.expiredButStillPending: + return "Expired (pending)"; + case CakePayOrderStatus.expired: + return "Expired"; + case CakePayOrderStatus.failed: + return "Failed"; + case CakePayOrderStatus.paid: + return "Paid"; + case CakePayOrderStatus.paidPartial: + return "Partially paid"; + case CakePayOrderStatus.pendingPurchase: + return "Pending purchase"; + case CakePayOrderStatus.purchaseProcessing: + return "Processing"; + case CakePayOrderStatus.purchased: + return "Purchased"; + case CakePayOrderStatus.pendingEmail: + return "Pending email"; + case CakePayOrderStatus.complete: + return "Complete"; + case CakePayOrderStatus.pendingRefund: + return "Pending refund"; + case CakePayOrderStatus.refunded: + return "Refunded"; + } + } + + Color _statusColor(BuildContext context, CakePayOrderStatus status) { + final colors = Theme.of(context).extension()!; + switch (status) { + case CakePayOrderStatus.complete: + case CakePayOrderStatus.purchased: + return colors.accentColorGreen; + case CakePayOrderStatus.new_: + case CakePayOrderStatus.paid: + case CakePayOrderStatus.paidPartial: + return colors.accentColorBlue; + case CakePayOrderStatus.pendingPurchase: + case CakePayOrderStatus.purchaseProcessing: + case CakePayOrderStatus.pendingEmail: + case CakePayOrderStatus.expiredButStillPending: + return colors.accentColorYellow; + case CakePayOrderStatus.expired: + case CakePayOrderStatus.failed: + case CakePayOrderStatus.pendingRefund: + case CakePayOrderStatus.refunded: + return colors.textSubtitle1; + } + } + + @override + Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + + if (_loading) { + return _scaffold( + isDesktop: isDesktop, + child: const Center( + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ), + ); + } + + if (_order == null) { + return _scaffold( + isDesktop: isDesktop, + child: Center( + child: Text( + "Failed to load order", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.itemSubtitle(context), + ), + ), + ); + } + + final order = _order!; + final paymentOptions = order.paymentOptions; + + final statusBadge = Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: _statusColor(context, order.status).withValues(alpha: 0.2), + ), + child: Text( + _statusLabel(order.status), + style: + (isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context)) + .copyWith(color: _statusColor(context, order.status)), + ), + ); + + final details = [ + Row(mainAxisAlignment: MainAxisAlignment.end, children: [statusBadge]), + SizedBox(height: isDesktop ? 8 : 6), + RoundedWhiteContainer( + child: GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: order.orderId)); + showFloatingFlushBar( + type: FlushBarType.info, + message: "Order ID copied", + iconAsset: Assets.svg.copy, + context: context, + ); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Order ID", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + SelectableText( + order.orderId, + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + const SizedBox(width: 6), + Icon( + Icons.copy, + size: 14, + color: Theme.of( + context, + ).extension()!.accentColorBlue, + ), + ], + ), + ], + ), + ), + ), + SizedBox(height: isDesktop ? 16 : 12), + ]; + + if (order.amountUsd != null) { + details.add( + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Amount", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + Text( + "\$${order.amountUsd} USD", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + ], + ), + ), + ); + details.add(SizedBox(height: isDesktop ? 16 : 12)); + } + + if (order.cards != null && order.cards!.isNotEmpty) { + for (final item in order.cards!) { + details.add( + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.name ?? "Gift Card", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + if (item.priceValue != null) ...[ + const SizedBox(height: 4), + Text( + "${item.priceValue} ${item.currencyCode ?? ''}".trim(), + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + ], + if (item.priceUsd != null) ...[ + const SizedBox(height: 2), + Text( + item.priceUsd!, + style: + (isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle12(context)) + .copyWith( + color: Theme.of( + context, + ).extension()!.textSubtitle1, + ), + ), + ], + ], + ), + ), + ); + details.add(SizedBox(height: isDesktop ? 8 : 6)); + } + } + + // Commission / markup info. + if (order.commission != null || order.markupPercent != null) { + details.add( + RoundedWhiteContainer( + child: Column( + children: [ + if (order.commission != null) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Commission", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + Text( + order.commission!, + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + ], + ), + if (order.commission != null && order.markupPercent != null) + const SizedBox(height: 4), + if (order.markupPercent != null) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Markup", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + Text( + "${order.markupPercent!.toStringAsFixed(2)}%", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + ], + ), + ], + ), + ), + ); + details.add(SizedBox(height: isDesktop ? 8 : 6)); + } + + // Expiration countdown. + if (order.expirationTime != null) { + final isExpired = _timeRemaining == Duration.zero; + details.add( + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Time remaining", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + Text( + _formatDuration(_timeRemaining), + style: + (isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context)) + .copyWith( + color: isExpired + ? Theme.of( + context, + ).extension()!.accentColorRed + : _timeRemaining.inMinutes < 5 + ? Theme.of( + context, + ).extension()!.accentColorOrange + : null, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ); + details.add(SizedBox(height: isDesktop ? 8 : 6)); + } + + // --- Status-dependent payment section --- + final status = order.status; + + // Banner for paid / processing states. + if (_isPaidOrBeyond(status)) { + details.add(SizedBox(height: isDesktop ? 16 : 12)); + details.add( + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.check_circle, + size: 20, + color: Theme.of( + context, + ).extension()!.accentColorGreen, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + status == CakePayOrderStatus.complete + ? "Order complete." + : "Payment received.", + style: + (isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle12(context)) + .copyWith( + color: Theme.of( + context, + ).extension()!.accentColorGreen, + ), + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + "Your gift card details will be sent to " + "the email address provided when creating " + "the order.", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + ); + details.add(SizedBox(height: isDesktop ? 8 : 6)); + details.addAll(_orderInfoWidgets(order, isDesktop)); + details.add(SizedBox(height: isDesktop ? 8 : 6)); + details.add( + const PrimaryButton( + label: "ORDER PAID", + enabled: false, + onPressed: null, + ), + ); + details.add(SizedBox(height: isDesktop ? 8 : 6)); + } + + // Banner for expired / failed / refund states. + if (status == CakePayOrderStatus.expired || + status == CakePayOrderStatus.failed || + status == CakePayOrderStatus.pendingRefund || + status == CakePayOrderStatus.refunded) { + details.add(SizedBox(height: isDesktop ? 16 : 12)); + details.add( + RoundedWhiteContainer( + child: Row( + children: [ + Icon( + Icons.cancel, + size: 20, + color: Theme.of( + context, + ).extension()!.textSubtitle1, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + _statusLabel(status), + style: + (isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context)) + .copyWith( + color: Theme.of( + context, + ).extension()!.textSubtitle1, + ), + ), + ), + ], + ), + ), + ); + details.add(SizedBox(height: isDesktop ? 8 : 6)); + details.addAll(_orderInfoWidgets(order, isDesktop)); + details.add(SizedBox(height: isDesktop ? 8 : 6)); + } + + // Payment UI: tabs + QR + address + pay button. + // Only shown for states that still accept payment. + if (_showPaymentUI(status) && + paymentOptions != null && + paymentOptions.isNotEmpty) { + // Sort so BTC_LN always appears last. + final options = paymentOptions.values.toList() + ..sort((a, b) { + final aLn = a.ticker.toUpperCase() == 'BTC_LN'; + final bLn = b.ticker.toUpperCase() == 'BTC_LN'; + if (aLn && !bLn) return 1; + if (!aLn && bLn) return -1; + return 0; + }); + if (_selectedPaymentMethod >= options.length) { + _selectedPaymentMethod = 0; + } + final selected = options[_selectedPaymentMethod]; + final label = _tickerLabel(selected.ticker); + final coin = _resolveCoin(selected.ticker); + final bool hasWallet = + coin != null && + ref.watch(pWallets).wallets.any((w) => w.info.coin == coin); + + details.add(SizedBox(height: isDesktop ? 8 : 4)); + details.add( + Text( + "Pay with", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + ); + details.add(SizedBox(height: isDesktop ? 8 : 6)); + + // Tab selector. + details.add( + Row( + children: List.generate(options.length, (index) { + final isSelected = _selectedPaymentMethod == index; + return Expanded( + child: GestureDetector( + onTap: () => setState(() => _selectedPaymentMethod = index), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 10), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: isSelected + ? Theme.of( + context, + ).extension()!.accentColorBlue + : Colors.transparent, + width: 2, + ), + ), + ), + child: Text( + _tickerLabel(options[index].ticker), + textAlign: TextAlign.center, + style: + (isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle12(context)) + .copyWith( + color: isSelected + ? Theme.of( + context, + ).extension()!.accentColorBlue + : null, + fontWeight: isSelected ? FontWeight.w600 : null, + ), + ), + ), + ), + ); + }), + ), + ); + + details.add(SizedBox(height: isDesktop ? 16 : 12)); + + // QR code for the selected payment address. + if (selected.address.isNotEmpty) { + details.add( + Center( + child: QR(data: selected.address, size: isDesktop ? 200 : 180), + ), + ); + details.add(SizedBox(height: isDesktop ? 16 : 12)); + } + + // Selected method details. + details.add( + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Amount", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + Text( + "${selected.amountFrom} $label", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + ], + ), + const SizedBox(height: 8), + GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: selected.address)); + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + "$label address", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + const Spacer(), + Icon( + Icons.copy, + size: 14, + color: Theme.of( + context, + ).extension()!.accentColorBlue, + ), + const SizedBox(width: 4), + Text("Copy", style: STextStyles.link2(context)), + ], + ), + const SizedBox(height: 4), + Text( + selected.address, + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + const SizedBox(height: 12), + PrimaryButton( + label: hasWallet ? "Pay with $label" : "$label (no wallet)", + enabled: hasWallet, + onPressed: hasWallet + ? () => _payWithOption(selected, order.orderId) + : null, + ), + ], + ), + ), + ); + details.add(SizedBox(height: isDesktop ? 8 : 6)); + } + + final content = SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: details, + ), + ); + + return _scaffold(isDesktop: isDesktop, child: content); + } + + Widget _scaffold({required bool isDesktop, required Widget child}) { + return ConditionalParent( + condition: isDesktop, + builder: (child) => DesktopDialog( + maxWidth: 580, + maxHeight: 650, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text("Order", style: STextStyles.desktopH3(context)), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 8, + ), + child: child, + ), + ), + ], + ), + ), + child: ConditionalParent( + condition: !isDesktop, + builder: (child) => Background( + child: Scaffold( + backgroundColor: Theme.of( + context, + ).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () => Navigator.of(context).pop(), + ), + title: Text("Order", style: STextStyles.navBarTitle(context)), + ), + body: SafeArea( + child: Padding(padding: const EdgeInsets.all(16), child: child), + ), + ), + ), + child: child, + ), + ); + } +} diff --git a/lib/pages/cakepay/cakepay_orders_view.dart b/lib/pages/cakepay/cakepay_orders_view.dart new file mode 100644 index 0000000000..139db34df7 --- /dev/null +++ b/lib/pages/cakepay/cakepay_orders_view.dart @@ -0,0 +1,311 @@ +import 'package:flutter/material.dart'; + +import '../../services/cakepay/cakepay_service.dart'; +import '../../services/cakepay/src/models/order.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../widgets/background.dart'; +import '../../widgets/conditional_parent.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/rounded_white_container.dart'; +import 'cakepay_order_view.dart'; + +class CakePayOrdersView extends StatefulWidget { + const CakePayOrdersView({super.key}); + + static const String routeName = "/cakePayOrders"; + + @override + State createState() => _CakePayOrdersViewState(); +} + +class _CakePayOrdersViewState extends State { + List _orders = []; + bool _syncing = false; + + @override + void initState() { + super.initState(); + _syncFromApi(); + } + + /// Fetch each locally-tracked order ID individually via getOrder() + /// (which works with the seller API key, unlike getMyOrders()). + /// Mirrors ShopInBit's _syncFromApi() pattern. + Future _syncFromApi() async { + setState(() => _syncing = true); + try { + final orderIds = CakePayService.instance.getOrderIds(); + final results = []; + + for (final id in orderIds) { + final resp = await CakePayService.instance.client.getOrder(id); + if (!resp.hasError && resp.value != null) { + var order = resp.value!; + final override = CakePayService.devStatusOverrides[order.orderId]; + if (override != null) { + order = order.copyWith(status: override); + } + results.add(order); + } + } + + if (mounted) { + setState(() { + _orders = results; + }); + } + } catch (_) { + // Fall back to empty list — no local cache to fall back on + } finally { + if (mounted) { + setState(() => _syncing = false); + } + } + } + + String _statusLabel(CakePayOrderStatus status) { + switch (status) { + case CakePayOrderStatus.new_: + return "New"; + case CakePayOrderStatus.expiredButStillPending: + return "Expired (pending)"; + case CakePayOrderStatus.expired: + return "Expired"; + case CakePayOrderStatus.failed: + return "Failed"; + case CakePayOrderStatus.paid: + return "Paid"; + case CakePayOrderStatus.paidPartial: + return "Partially paid"; + case CakePayOrderStatus.pendingPurchase: + return "Pending purchase"; + case CakePayOrderStatus.purchaseProcessing: + return "Processing"; + case CakePayOrderStatus.purchased: + return "Purchased"; + case CakePayOrderStatus.pendingEmail: + return "Pending email"; + case CakePayOrderStatus.complete: + return "Complete"; + case CakePayOrderStatus.pendingRefund: + return "Pending refund"; + case CakePayOrderStatus.refunded: + return "Refunded"; + } + } + + Color _statusColor(BuildContext context, CakePayOrderStatus status) { + final colors = Theme.of(context).extension()!; + switch (status) { + case CakePayOrderStatus.complete: + case CakePayOrderStatus.purchased: + return colors.accentColorGreen; + case CakePayOrderStatus.new_: + case CakePayOrderStatus.paid: + case CakePayOrderStatus.paidPartial: + return colors.accentColorBlue; + case CakePayOrderStatus.pendingPurchase: + case CakePayOrderStatus.purchaseProcessing: + case CakePayOrderStatus.pendingEmail: + case CakePayOrderStatus.expiredButStillPending: + return colors.accentColorYellow; + case CakePayOrderStatus.expired: + case CakePayOrderStatus.failed: + case CakePayOrderStatus.pendingRefund: + case CakePayOrderStatus.refunded: + return colors.textSubtitle1; + } + } + + @override + Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + + final list = _orders.isEmpty + ? Center( + child: Text( + _syncing ? "Loading orders..." : "No orders yet", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.itemSubtitle(context), + ), + ) + : ListView.separated( + shrinkWrap: isDesktop, + primary: isDesktop ? false : null, + itemCount: _orders.length, + separatorBuilder: (_, __) => SizedBox(height: isDesktop ? 16 : 12), + itemBuilder: (context, index) { + final order = _orders[index]; + return GestureDetector( + onTap: () { + if (isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + showDialog( + context: context, + builder: (_) => CakePayOrderView(orderId: order.orderId), + ); + } else { + Navigator.of(context).pushNamed( + CakePayOrderView.routeName, + arguments: order.orderId, + ); + } + }, + child: RoundedWhiteContainer( + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + order.orderId.length > 8 + ? "${order.orderId.substring(0, 8)}..." + : order.orderId, + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 2, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: _statusColor( + context, + order.status, + ).withValues(alpha: 0.2), + ), + child: Text( + _statusLabel(order.status), + style: + (isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle12( + context, + )) + .copyWith( + color: _statusColor( + context, + order.status, + ), + ), + ), + ), + ], + ), + if (order.amountUsd != null) ...[ + const SizedBox(height: 4), + Text( + "\$${order.amountUsd} USD", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle12( + context, + ).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ], + ), + ), + SizedBox(width: isDesktop ? 16 : 8), + Icon( + Icons.chevron_right, + color: Theme.of( + context, + ).extension()!.textSubtitle1, + ), + ], + ), + ), + ); + }, + ); + + final content = Stack( + children: [ + list, + if (_syncing) + const Center( + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ), + ], + ); + + return ConditionalParent( + condition: isDesktop, + builder: (child) => DesktopDialog( + maxWidth: 580, + maxHeight: 550, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "My Orders", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), + child: child, + ), + ), + ], + ), + ), + child: ConditionalParent( + condition: !isDesktop, + builder: (child) => Background( + child: Scaffold( + backgroundColor: Theme.of( + context, + ).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () => Navigator.of(context).pop(), + ), + title: Text("My Orders", style: STextStyles.navBarTitle(context)), + ), + body: SafeArea( + child: Padding(padding: const EdgeInsets.all(16), child: child), + ), + ), + ), + child: content, + ), + ); + } +} diff --git a/lib/pages/cakepay/cakepay_send_from_view.dart b/lib/pages/cakepay/cakepay_send_from_view.dart new file mode 100644 index 0000000000..4213625d36 --- /dev/null +++ b/lib/pages/cakepay/cakepay_send_from_view.dart @@ -0,0 +1,408 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../app_config.dart'; +import '../../models/isar/models/blockchain_data/address.dart'; +import '../../providers/providers.dart'; +import '../../route_generator.dart'; +import '../../themes/coin_icon_provider.dart'; +import '../../themes/stack_colors.dart'; +import '../../themes/theme_providers.dart'; +import '../../utilities/amount/amount.dart'; +import '../../utilities/amount/amount_formatter.dart'; +import '../../utilities/constants.dart'; +import '../../utilities/enums/fee_rate_type_enum.dart'; +import '../../utilities/logger.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../wallets/crypto_currency/crypto_currency.dart'; +import '../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../wallets/models/tx_data.dart'; +import '../../wallets/wallet/intermediate/external_wallet.dart'; +import '../../widgets/background.dart'; +import '../../widgets/conditional_parent.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/rounded_white_container.dart'; +import '../../widgets/stack_dialog.dart'; +import '../../pages_desktop_specific/desktop_home_view.dart'; +import '../home_view/home_view.dart'; +import '../send_view/sub_widgets/building_transaction_dialog.dart'; +import 'cakepay_confirm_send_view.dart'; + +class CakePaySendFromView extends ConsumerStatefulWidget { + const CakePaySendFromView({ + super.key, + this.coin, + this.amount, + required this.address, + required this.orderId, + this.shouldPopRoot = false, + }); + + static const String routeName = "/cakePaySendFrom"; + + final CryptoCurrency? coin; + final Amount? amount; + final String address; + final String orderId; + final bool shouldPopRoot; + + @override + ConsumerState createState() => + _CakePaySendFromViewState(); +} + +class _CakePaySendFromViewState extends ConsumerState { + @override + Widget build(BuildContext context) { + final List walletIds; + if (widget.coin != null) { + walletIds = ref + .watch(pWallets) + .wallets + .where((e) => e.info.coin == widget.coin) + .map((e) => e.walletId) + .toList(); + } else { + walletIds = ref.watch(pWallets).wallets.map((e) => e.walletId).toList(); + } + + final isDesktop = Util.isDesktop; + + return ConditionalParent( + condition: !isDesktop, + builder: (child) { + return Background( + child: Scaffold( + backgroundColor: Theme.of( + context, + ).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () => Navigator.of(context).pop(), + ), + title: Text("Send from", style: STextStyles.navBarTitle(context)), + ), + body: SafeArea( + child: Padding(padding: const EdgeInsets.all(16), child: child), + ), + ), + ); + }, + child: ConditionalParent( + condition: isDesktop, + builder: (child) => DesktopDialog( + maxHeight: double.infinity, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Send from ${AppConfig.prefix}", + style: STextStyles.desktopH3(context), + ), + ), + DesktopDialogCloseButton( + onPressedOverride: Navigator.of( + context, + rootNavigator: widget.shouldPopRoot, + ).pop, + ), + ], + ), + Padding( + padding: const EdgeInsets.only(left: 32, right: 32, bottom: 32), + child: child, + ), + ], + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + children: [ + Text( + widget.amount != null && widget.coin != null + ? "You need to send ${ref.watch(pAmountFormatter(widget.coin!)).format(widget.amount!)}" + : "Select a wallet to pay", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle(context), + ), + ], + ), + const SizedBox(height: 16), + ConditionalParent( + condition: !isDesktop, + builder: (child) => Expanded(child: child), + child: ListView.builder( + primary: isDesktop ? false : null, + shrinkWrap: isDesktop, + itemCount: walletIds.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: _CakePaySendFromCard( + walletId: walletIds[index], + amount: widget.amount, + address: widget.address, + orderId: widget.orderId, + ), + ); + }, + ), + ), + ], + ), + ), + ); + } +} + +class _CakePaySendFromCard extends ConsumerStatefulWidget { + const _CakePaySendFromCard({ + required this.walletId, + this.amount, + required this.address, + required this.orderId, + }); + + final String walletId; + final Amount? amount; + final String address; + final String orderId; + + @override + ConsumerState<_CakePaySendFromCard> createState() => + _CakePaySendFromCardState(); +} + +class _CakePaySendFromCardState extends ConsumerState<_CakePaySendFromCard> { + Future _send() async { + final coin = ref.read(pWalletCoin(widget.walletId)); + final Amount? sendAmount = widget.amount; + + if (sendAmount == null) { + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return StackDialog( + title: "Transaction failed", + message: "Payment amount not available yet", + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: Theme.of( + context, + ).extension()!.buttonTextSecondary, + ), + ), + onPressed: () => Navigator.of(context).pop(), + ), + ); + }, + ); + return; + } + + bool wasCancelled = false; + + try { + final wallet = ref.read(pWallets).getWallet(widget.walletId); + + unawaited( + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return ConditionalParent( + condition: Util.isDesktop, + builder: (child) => DesktopDialog( + maxWidth: 400, + maxHeight: double.infinity, + child: Padding(padding: const EdgeInsets.all(32), child: child), + ), + child: BuildingTransactionDialog( + coin: coin, + isSpark: false, + onCancel: () { + wasCancelled = true; + Navigator.of(context).pop(); + }, + ), + ); + }, + ), + ); + + if (wallet is ExternalWallet) { + await wallet.init(); + await wallet.open(); + } + + final time = Future.delayed(const Duration(milliseconds: 2500)); + + final addressType = + wallet.cryptoCurrency.getAddressType(widget.address) ?? + AddressType.unknown; + + final recipient = TxRecipient( + address: widget.address, + amount: sendAmount, + isChange: false, + addressType: addressType, + ); + + final txDataFuture = wallet.prepareSend( + txData: TxData( + recipients: [recipient], + feeRateType: FeeRateType.average, + ), + ); + + final results = await Future.wait([txDataFuture, time]); + + final txData = (results.first as TxData).copyWith( + note: "CakePay payment", + ); + + if (!wasCancelled) { + if (mounted) { + Navigator.of(context, rootNavigator: true).pop(); + } + + if (mounted) { + await Navigator.of(context).push( + RouteGenerator.getRoute( + shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, + builder: (_) => CakePayConfirmSendView( + txData: txData, + walletId: widget.walletId, + routeOnSuccessName: Util.isDesktop + ? DesktopHomeView.routeName + : HomeView.routeName, + orderId: widget.orderId, + ), + settings: const RouteSettings( + name: CakePayConfirmSendView.routeName, + ), + ), + ); + } + } + } catch (e, s) { + Logging.instance.e("$e\n$s", error: e, stackTrace: s); + if (mounted && !wasCancelled) { + Navigator.of(context, rootNavigator: true).pop(); + + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return StackDialog( + title: "Transaction failed", + message: e.toString(), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: Theme.of( + context, + ).extension()!.buttonTextSecondary, + ), + ), + onPressed: () => Navigator.of(context).pop(), + ), + ); + }, + ); + } + } + } + + @override + Widget build(BuildContext context) { + final coin = ref.watch(pWalletCoin(widget.walletId)); + + return RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: MaterialButton( + splashColor: Theme.of(context).extension()!.highlight, + key: Key("cakePayWalletKey_${widget.walletId}"), + padding: const EdgeInsets.all(8), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () async { + if (mounted) unawaited(_send()); + }, + child: Row( + children: [ + Container( + decoration: BoxDecoration( + color: ref.watch(pCoinColor(coin)).withValues(alpha: 0.5), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: Padding( + padding: const EdgeInsets.all(6), + child: SvgPicture.file( + File(ref.watch(coinIconProvider(coin))), + width: 24, + height: 24, + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + ref.watch(pWalletName(widget.walletId)), + style: STextStyles.titleBold12(context), + ), + const SizedBox(height: 2), + Text( + ref + .watch(pAmountFormatter(coin)) + .format( + ref.watch(pWalletBalance(widget.walletId)).spendable, + ), + style: STextStyles.itemSubtitle(context), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/cakepay/cakepay_vendors_view.dart b/lib/pages/cakepay/cakepay_vendors_view.dart new file mode 100644 index 0000000000..0e7f1b261b --- /dev/null +++ b/lib/pages/cakepay/cakepay_vendors_view.dart @@ -0,0 +1,443 @@ +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import '../../services/cakepay/cakepay_service.dart'; +import '../../services/cakepay/src/models/card.dart'; +import '../../services/cakepay/src/models/vendor.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/constants.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../widgets/background.dart'; +import '../../widgets/conditional_parent.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/rounded_white_container.dart'; +import '../../widgets/stack_text_field.dart'; +import '../../utilities/assets.dart'; +import 'cakepay_card_detail_view.dart'; + +class CakePayVendorsView extends StatefulWidget { + const CakePayVendorsView({super.key}); + + static const String routeName = "/cakePayVendors"; + + @override + State createState() => _CakePayVendorsViewState(); +} + +class _CakePayVendorsViewState extends State { + List _vendors = []; + List _countryNames = []; + String? _selectedCountry; + bool _loading = true; + String? _error; + final _searchController = TextEditingController(); + final _searchFocusNode = FocusNode(); + final _countrySearchController = TextEditingController(); + + @override + void initState() { + super.initState(); + _loadVendors(); + } + + @override + void dispose() { + _searchController.dispose(); + _searchFocusNode.dispose(); + _countrySearchController.dispose(); + super.dispose(); + } + + /// Derive a country list from the loaded vendors so we don't need the + /// broken /marketplace/countries/ endpoint. + void _deriveCountries() { + final seen = {}; + final countries = []; + for (final v in _vendors) { + final c = v.country; + if (c != null && c.isNotEmpty && seen.add(c)) { + countries.add(c); + } + } + countries.sort(); + _countryNames = countries; + } + + Future _loadVendors() async { + setState(() { + _loading = true; + _error = null; + }); + final resp = await CakePayService.instance.client.getVendors( + country: _selectedCountry, + search: _searchController.text.trim().isNotEmpty + ? _searchController.text.trim() + : null, + ); + if (mounted) { + setState(() { + _loading = false; + if (!resp.hasError && resp.value != null) { + _vendors = resp.value!; + _deriveCountries(); + } else { + _error = resp.exception?.message ?? "Failed to load gift cards"; + } + }); + } + } + + List get _allCards { + final cards = []; + for (final vendor in _vendors) { + cards.addAll(vendor.cards.where((c) => c.available)); + } + return cards; + } + + @override + Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + final cards = _allCards; + + final searchField = ClipRRect( + borderRadius: BorderRadius.circular(Constants.size.circularBorderRadius), + child: TextField( + controller: _searchController, + focusNode: _searchFocusNode, + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + ) + : STextStyles.field(context), + decoration: + standardInputDecoration( + "Search gift cards", + _searchFocusNode, + context, + ).copyWith( + prefixIcon: const Padding( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 12), + child: Icon(Icons.search, size: 20), + ), + ), + onSubmitted: (_) => _loadVendors(), + ), + ); + + final countryDropdown = _countryNames.isEmpty + ? const SizedBox.shrink() + : Padding( + padding: const EdgeInsets.only(top: 12), + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: DropdownButtonHideUnderline( + child: DropdownButton2( + value: _selectedCountry, + isExpanded: true, + hint: Text( + "All countries", + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldDefaultSearchIconLeft, + ) + : STextStyles.fieldLabel(context), + ), + items: [ + DropdownMenuItem( + value: null, + child: Text( + "All countries", + style: isDesktop + ? STextStyles.desktopTextExtraSmall( + context, + ).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + ) + : STextStyles.w500_14(context), + ), + ), + ..._countryNames.map( + (name) => DropdownMenuItem( + value: name, + child: Text( + name, + style: isDesktop + ? STextStyles.desktopTextExtraSmall( + context, + ).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + ) + : STextStyles.w500_14(context), + ), + ), + ), + ], + onMenuStateChange: (isOpen) { + if (!isOpen) { + _countrySearchController.clear(); + } + }, + onChanged: (value) { + setState(() => _selectedCountry = value); + _loadVendors(); + }, + buttonStyleData: ButtonStyleData( + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + iconStyleData: IconStyleData( + icon: Padding( + padding: const EdgeInsets.only(right: 10), + child: SvgPicture.asset( + Assets.svg.chevronDown, + width: 12, + height: 6, + colorFilter: ColorFilter.mode( + Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, + BlendMode.srcIn, + ), + ), + ), + ), + dropdownStyleData: DropdownStyleData( + offset: const Offset(0, -10), + elevation: 0, + maxHeight: 300, + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + dropdownSearchData: DropdownSearchData( + searchController: _countrySearchController, + searchInnerWidgetHeight: 48, + searchInnerWidget: TextFormField( + controller: _countrySearchController, + decoration: InputDecoration( + isDense: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 14, + ), + hintText: "Search...", + hintStyle: STextStyles.fieldLabel(context), + border: InputBorder.none, + ), + ), + searchMatchFn: (item, searchValue) { + if (item.value == null) { + return "all countries".contains( + searchValue.toLowerCase(), + ); + } + return item.value!.toLowerCase().contains( + searchValue.toLowerCase(), + ); + }, + ), + menuItemStyleData: const MenuItemStyleData( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + ), + ), + ), + ), + ); + + final cardsList = _loading + ? const Center( + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ) + : cards.isEmpty + ? Center( + child: Text( + _error ?? "No gift cards found", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.itemSubtitle(context), + ), + ) + : ListView.separated( + shrinkWrap: isDesktop, + primary: isDesktop ? false : null, + itemCount: cards.length, + separatorBuilder: (_, __) => SizedBox(height: isDesktop ? 16 : 12), + itemBuilder: (context, index) { + final card = cards[index]; + return GestureDetector( + onTap: () { + if (isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + showDialog( + context: context, + builder: (_) => CakePayCardDetailView(card: card), + ); + } else { + Navigator.of(context).pushNamed( + CakePayCardDetailView.routeName, + arguments: card, + ); + } + }, + child: RoundedWhiteContainer( + child: Row( + children: [ + if (card.cardImageUrl != null) + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: Image.network( + card.cardImageUrl!, + width: isDesktop ? 60 : 48, + height: isDesktop ? 40 : 32, + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => Icon( + Icons.card_giftcard, + size: isDesktop ? 40 : 32, + ), + ), + ) + else + Icon(Icons.card_giftcard, size: isDesktop ? 40 : 32), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + card.name, + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 2), + Text( + card.denominationRange.isNotEmpty + ? "${card.denominationRange} ${card.currencyCode ?? ''}" + : card.currencyCode ?? '', + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle12( + context, + ).copyWith( + color: Theme.of( + context, + ).extension()!.textSubtitle1, + ), + ), + ], + ), + ), + Icon( + Icons.chevron_right, + color: Theme.of( + context, + ).extension()!.textSubtitle1, + ), + ], + ), + ), + ); + }, + ); + + final body = Column( + children: [ + searchField, + countryDropdown, + SizedBox(height: isDesktop ? 16 : 12), + Expanded(child: cardsList), + ], + ); + + return ConditionalParent( + condition: isDesktop, + builder: (child) => DesktopDialog( + maxWidth: 580, + maxHeight: 650, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Gift Cards", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 8, + ), + child: child, + ), + ), + ], + ), + ), + child: ConditionalParent( + condition: !isDesktop, + builder: (child) => Background( + child: Scaffold( + backgroundColor: Theme.of( + context, + ).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () => Navigator.of(context).pop(), + ), + title: Text( + "Gift Cards", + style: STextStyles.navBarTitle(context), + ), + ), + body: SafeArea( + child: Padding(padding: const EdgeInsets.all(16), child: child), + ), + ), + ), + child: body, + ), + ); + } +} diff --git a/lib/pages/more_view/gift_cards_view.dart b/lib/pages/more_view/gift_cards_view.dart new file mode 100644 index 0000000000..9fcf82bbca --- /dev/null +++ b/lib/pages/more_view/gift_cards_view.dart @@ -0,0 +1,153 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../app_config.dart'; +import '../../services/event_bus/events/global/tor_connection_status_changed_event.dart'; +import '../../services/tor_service.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/assets.dart'; +import '../../utilities/text_styles.dart'; +import '../../widgets/background.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../../widgets/desktop/secondary_button.dart'; +import '../../widgets/rounded_white_container.dart'; +import '../../widgets/tor_subscription.dart'; +import '../cakepay/cakepay_orders_view.dart'; +import '../cakepay/cakepay_vendors_view.dart'; + +class GiftCardsView extends ConsumerStatefulWidget { + const GiftCardsView({super.key}); + + static const String routeName = "/giftCardsView"; + + @override + ConsumerState createState() => _GiftCardsViewState(); +} + +class _GiftCardsViewState extends ConsumerState { + late bool _torEnabled; + + @override + void initState() { + _torEnabled = AppConfig.hasFeature(AppFeature.tor) + ? ref.read(pTorService).status != TorConnectionStatus.disconnected + : false; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return TorSubscription( + onTorStatusChanged: (status) { + setState(() { + _torEnabled = status != TorConnectionStatus.disconnected; + }); + }, + child: Background( + child: Scaffold( + backgroundColor: Theme.of( + context, + ).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text("Gift cards", style: STextStyles.navBarTitle(context)), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + SvgPicture.asset( + Assets.svg.creditCard, + width: 32, + height: 32, + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "CakePay", + style: STextStyles.titleBold12(context), + ), + const SizedBox(height: 2), + Text( + "Purchase gift cards with cryptocurrency", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ), + ), + ], + ), + const SizedBox(height: 16), + if (_torEnabled) + Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Text( + "CakePay is not available while Tor is enabled", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of( + context, + ).extension()!.textSubtitle1, + ), + ), + ), + Row( + children: [ + Expanded( + child: PrimaryButton( + label: "Browse", + enabled: !_torEnabled, + onPressed: () { + Navigator.of( + context, + ).pushNamed(CakePayVendorsView.routeName); + }, + ), + ), + const SizedBox(width: 16), + Expanded( + child: SecondaryButton( + label: "My Orders", + onPressed: () { + Navigator.of( + context, + ).pushNamed(CakePayOrdersView.routeName); + }, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/more_view/services_view.dart b/lib/pages/more_view/services_view.dart new file mode 100644 index 0000000000..aa4d7acdaa --- /dev/null +++ b/lib/pages/more_view/services_view.dart @@ -0,0 +1,336 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../../db/isar/main_db.dart'; +import '../../models/shopinbit/shopinbit_order_model.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/assets.dart'; +import '../../utilities/text_styles.dart'; +import '../../widgets/background.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../../widgets/desktop/secondary_button.dart'; +import '../../widgets/rounded_white_container.dart'; +import '../../widgets/stack_dialog.dart'; +import '../../services/shopinbit/shopinbit_service.dart'; +import '../shopinbit/shopinbit_settings_view.dart'; +import '../shopinbit/shopinbit_setup_view.dart'; +import '../shopinbit/shopinbit_step_1.dart'; +import '../shopinbit/shopinbit_step_2.dart'; +import '../shopinbit/shopinbit_tickets_view.dart'; + +class ServicesView extends StatefulWidget { + const ServicesView({super.key}); + + static const String routeName = "/servicesView"; + + @override + State createState() => _ServicesViewState(); +} + +class _ServicesViewState extends State { + Future _showOpenBrowserWarning(BuildContext context, String url) async { + final uri = Uri.parse(url); + final shouldContinue = await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => StackDialog( + title: "Attention", + message: + "You are about to open " + "${uri.scheme}://${uri.host} " + "in your browser.", + leftButton: TextButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: Text( + "Cancel", + style: STextStyles.button(context).copyWith( + color: Theme.of( + context, + ).extension()!.accentColorDark, + ), + ), + ), + rightButton: TextButton( + style: Theme.of( + context, + ).extension()!.getPrimaryEnabledButtonStyle(context), + onPressed: () { + Navigator.of(context).pop(true); + }, + child: Text("Continue", style: STextStyles.button(context)), + ), + ), + ); + return shouldContinue ?? false; + } + + void _showShopDialog(BuildContext context) { + showDialog( + context: context, + barrierDismissible: true, + builder: (dialogContext) => StackDialogBase( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("ShopinBit", style: STextStyles.pageTitleH2(dialogContext)), + const SizedBox(height: 8), + RichText( + text: TextSpan( + style: STextStyles.smallMed14(dialogContext), + children: [ + const TextSpan( + text: + "Please note the following before proceeding:" + "\n\n\u2022 Minimum order amount: 1,000 EUR" + "\n\u2022 Service fee: 10% of the order total" + "\n\nBy continuing, you agree to the ShopinBit ", + ), + TextSpan( + text: "Privacy Policy", + style: STextStyles.richLink( + dialogContext, + ).copyWith(fontSize: 16), + recognizer: TapGestureRecognizer() + ..onTap = () async { + const url = + "https://api.shopinbit.com/static/policy/privacy.html"; + final shouldOpen = await _showOpenBrowserWarning( + dialogContext, + url, + ); + if (shouldOpen) { + await launchUrl( + Uri.parse(url), + mode: LaunchMode.externalApplication, + ); + } + }, + ), + const TextSpan(text: "."), + ], + ), + ), + const SizedBox(height: 20), + Row( + children: [ + Expanded( + child: TextButton( + onPressed: () { + Navigator.of(dialogContext).pop(); + }, + child: Text( + "Cancel", + style: STextStyles.button(dialogContext).copyWith( + color: Theme.of( + dialogContext, + ).extension()!.accentColorDark, + ), + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: TextButton( + style: Theme.of(dialogContext) + .extension()! + .getPrimaryEnabledButtonStyle(dialogContext), + onPressed: () async { + Navigator.of(dialogContext).pop(); + final model = ShopInBitOrderModel(); + final service = ShopInBitService.instance; + + if (service.loadSetupComplete()) { + // Returning user: pre-load display name, + // skip Step 1, go to Step 2 + final savedName = service.loadDisplayName(); + if (savedName != null && savedName.isNotEmpty) { + model.displayName = savedName; + } + await Navigator.of( + context, + ).pushNamed(ShopInBitStep2.routeName, arguments: model); + } else { + // First-time user: show setup flow + await Navigator.of(context).pushNamed( + ShopInBitSetupView.routeName, + arguments: model, + ); + } + if (mounted) setState(() {}); + }, + child: Text( + "Continue", + style: STextStyles.button(dialogContext), + ), + ), + ), + ], + ), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text("Services", style: STextStyles.navBarTitle(context)), + ), + body: SafeArea( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16), + child: RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + SvgPicture.asset( + Assets.svg.circleSliders, + width: 32, + height: 32, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + "ShopinBit", + style: STextStyles.titleBold12(context), + ), + ), + GestureDetector( + onTap: () { + Navigator.of( + context, + ).pushNamed(ShopInBitSettingsView.routeName); + }, + child: SvgPicture.asset( + Assets.svg.gear, + width: 20, + height: 20, + color: Theme.of( + context, + ).extension()!.textDark3, + ), + ), + ], + ), + const SizedBox(height: 12), + Text( + "Turn your crypto into Electronics, Flights, Hotel, " + "Cars or any other legal product or service... " + "ShopinBit is a concierge shopping service that helps " + "you 'live the good life with crypto'...", + style: STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of( + context, + ).extension()!.textSubtitle1, + ), + ), + const SizedBox(height: 12), + RichText( + text: TextSpan( + style: STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of( + context, + ).extension()!.textSubtitle1, + ), + children: [ + const TextSpan( + text: + "Minimum order value of 1,000 EUR. " + "A 10% service fee applies to all orders.\n\n" + "By using ShopinBit, you agree to their ", + ), + TextSpan( + text: "Terms & Conditions", + style: STextStyles.richLink( + context, + ).copyWith(fontSize: 14), + recognizer: TapGestureRecognizer() + ..onTap = () async { + const url = + "https://api.shopinbit.com/static/policy/terms.html"; + final shouldOpen = + await _showOpenBrowserWarning(context, url); + if (shouldOpen) { + await launchUrl( + Uri.parse(url), + mode: LaunchMode.externalApplication, + ); + } + }, + ), + const TextSpan(text: " and "), + TextSpan( + text: "Privacy Policy", + style: STextStyles.richLink( + context, + ).copyWith(fontSize: 14), + recognizer: TapGestureRecognizer() + ..onTap = () async { + const url = + "https://api.shopinbit.com/static/policy/privacy.html"; + final shouldOpen = + await _showOpenBrowserWarning(context, url); + if (shouldOpen) { + await launchUrl( + Uri.parse(url), + mode: LaunchMode.externalApplication, + ); + } + }, + ), + const TextSpan(text: "."), + ], + ), + ), + const SizedBox(height: 16), + PrimaryButton( + label: "Shop with ShopinBit", + enabled: true, + onPressed: () => _showShopDialog(context), + ), + const SizedBox(height: 12), + Builder( + builder: (context) { + final count = MainDB.instance + .getShopInBitTickets() + .length; + return SecondaryButton( + label: count > 0 + ? "My requests ($count)" + : "My requests", + onPressed: () async { + await Navigator.of( + context, + ).pushNamed(ShopInBitTicketsView.routeName); + if (mounted) setState(() {}); + }, + ); + }, + ), + ], + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/ordinals/ordinal_details_view.dart b/lib/pages/ordinals/ordinal_details_view.dart index 7ea7c2d342..958aa8f37d 100644 --- a/lib/pages/ordinals/ordinal_details_view.dart +++ b/lib/pages/ordinals/ordinal_details_view.dart @@ -15,8 +15,11 @@ import '../../models/isar/models/blockchain_data/utxo.dart'; import '../../models/isar/ordinal.dart'; import '../../networking/http.dart'; import '../../notifications/show_flush_bar.dart'; +import '../../pages/send_view/confirm_transaction_view.dart'; import '../../providers/db/main_db_provider.dart'; import '../../providers/global/prefs_provider.dart'; +import '../../providers/global/wallets_provider.dart'; +import '../../route_generator.dart'; import '../../services/tor_service.dart'; import '../../themes/stack_colors.dart'; import '../../utilities/amount/amount.dart'; @@ -27,10 +30,14 @@ import '../../utilities/fs.dart'; import '../../utilities/show_loading.dart'; import '../../utilities/text_styles.dart'; import '../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart'; import '../../widgets/background.dart'; import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/primary_button.dart'; import '../../widgets/desktop/secondary_button.dart'; +import '../../widgets/ordinal_image.dart'; import '../../widgets/rounded_white_container.dart'; +import 'widgets/dialogs.dart'; class OrdinalDetailsView extends ConsumerStatefulWidget { const OrdinalDetailsView({ @@ -298,12 +305,7 @@ class _OrdinalImageGroup extends ConsumerWidget { aspectRatio: 1, child: Container( color: Colors.transparent, - child: Image.network( - ordinal.content, // Use the preview URL as the image source - fit: BoxFit.cover, - filterQuality: - FilterQuality.none, // Set the filter mode to nearest - ), + child: OrdinalImage(url: ordinal.content), ), ), ), @@ -354,33 +356,129 @@ class _OrdinalImageGroup extends ConsumerWidget { }, ), ), - // const SizedBox( - // width: _spacing, - // ), - // Expanded( - // child: PrimaryButton( - // label: "Send", - // icon: SvgPicture.asset( - // Assets.svg.send, - // width: 10, - // height: 10, - // color: Theme.of(context) - // .extension()! - // .buttonTextPrimary, - // ), - // buttonHeight: ButtonHeight.l, - // iconSpacing: 4, - // onPressed: () async { - // final response = await showDialog( - // context: context, - // builder: (_) => const SendOrdinalUnfreezeDialog(), - // ); - // if (response == "unfreeze") { - // // TODO: unfreeze and go to send ord screen - // } - // }, - // ), - // ), + const SizedBox(width: _spacing), + Expanded( + child: PrimaryButton( + label: "Send", + icon: SvgPicture.asset( + Assets.svg.send, + width: 10, + height: 10, + color: Theme.of( + context, + ).extension()!.buttonTextPrimary, + ), + buttonHeight: ButtonHeight.l, + iconSpacing: 4, + onPressed: () async { + final utxo = ordinal.getUTXO(ref.read(mainDBProvider)); + if (utxo == null) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Could not find ordinal UTXO", + context: context, + ), + ); + return; + } + + // Step 1: Confirm unfreeze + if (utxo.isBlocked) { + final unfreezeResponse = await showDialog( + context: context, + builder: (_) => const SendOrdinalUnfreezeDialog(), + ); + if (unfreezeResponse != "unfreeze") return; + } + + if (!context.mounted) return; + + // Step 2: Get recipient address + final address = await showDialog( + context: context, + builder: (_) => OrdinalRecipientAddressDialog( + inscriptionNumber: ordinal.inscriptionNumber, + ), + ); + if (address == null || address.isEmpty) return; + + // Validate address + final wallet = ref.read(pWallets).getWallet(walletId); + if (!wallet.cryptoCurrency.validateAddress(address)) { + if (context.mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Invalid address", + context: context, + ), + ); + } + return; + } + + if (!context.mounted) return; + + // Step 3: Prepare the transaction + final OrdinalsInterface? ordinalsWallet = + wallet is OrdinalsInterface ? wallet : null; + if (ordinalsWallet == null) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Wallet does not support ordinals", + context: context, + ), + ); + return; + } + + bool didError = false; + final txData = await showLoading( + whileFuture: ordinalsWallet.prepareOrdinalSend( + ordinalUtxo: utxo, + recipientAddress: address, + ), + context: context, + rootNavigator: true, + message: "Preparing transaction...", + onException: (e) { + didError = true; + String msg = e.toString(); + while (msg.isNotEmpty && msg.startsWith("Exception:")) { + msg = msg.substring(10).trim(); + } + if (context.mounted) { + showFloatingFlushBar( + type: FlushBarType.warning, + message: msg, + context: context, + ); + } + }, + ); + + if (didError || txData == null || !context.mounted) return; + + // Step 4: Navigate to confirm transaction view + await Navigator.of(context).push( + RouteGenerator.getRoute( + shouldUseMaterialRoute: + RouteGenerator.useMaterialPageRoute, + builder: (_) => ConfirmTransactionView( + walletId: walletId, + txData: txData, + onSuccess: () {}, + ), + settings: const RouteSettings( + name: ConfirmTransactionView.routeName, + ), + ), + ); + }, + ), + ), ], ), ], diff --git a/lib/pages/ordinals/widgets/dialogs.dart b/lib/pages/ordinals/widgets/dialogs.dart index fca607961d..cb51fca1a8 100644 --- a/lib/pages/ordinals/widgets/dialogs.dart +++ b/lib/pages/ordinals/widgets/dialogs.dart @@ -1,7 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; + import '../../../themes/stack_colors.dart'; import '../../../utilities/assets.dart'; +import '../../../utilities/text_styles.dart'; +import '../../../utilities/util.dart'; +import '../../../widgets/desktop/desktop_dialog.dart'; import '../../../widgets/desktop/primary_button.dart'; import '../../../widgets/desktop/secondary_button.dart'; import '../../../widgets/stack_dialog.dart'; @@ -11,6 +16,61 @@ class SendOrdinalUnfreezeDialog extends StatelessWidget { @override Widget build(BuildContext context) { + if (Util.isDesktop) { + return DesktopDialog( + maxWidth: 450, + maxHeight: 220, + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "This ordinal is frozen", + style: STextStyles.desktopH3(context), + ), + SvgPicture.asset( + Assets.svg.coinControl.blocked, + width: 24, + height: 24, + color: Theme.of(context).extension()!.textDark, + ), + ], + ), + const SizedBox(height: 12), + Text( + "To send this ordinal, you must unfreeze it first.", + style: STextStyles.desktopTextMedium(context), + ), + const Spacer(), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox(width: 16), + Expanded( + child: PrimaryButton( + label: "Unfreeze", + onPressed: () { + Navigator.of(context).pop("unfreeze"); + }, + ), + ), + ], + ), + ], + ), + ), + ); + } + return StackDialog( title: "This ordinal is frozen", icon: SvgPicture.asset( @@ -39,6 +99,56 @@ class UnfreezeOrdinalDialog extends StatelessWidget { @override Widget build(BuildContext context) { + if (Util.isDesktop) { + return DesktopDialog( + maxWidth: 450, + maxHeight: 200, + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Unfreeze ordinal?", + style: STextStyles.desktopH3(context), + ), + SvgPicture.asset( + Assets.svg.coinControl.blocked, + width: 24, + height: 24, + color: Theme.of(context).extension()!.textDark, + ), + ], + ), + const Spacer(), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox(width: 16), + Expanded( + child: PrimaryButton( + label: "Unfreeze", + onPressed: () { + Navigator.of(context).pop("unfreeze"); + }, + ), + ), + ], + ), + ], + ), + ), + ); + } + return StackDialog( title: "Are you sure you want to unfreeze this ordinal?", icon: SvgPicture.asset( @@ -60,3 +170,158 @@ class UnfreezeOrdinalDialog extends StatelessWidget { ); } } + +class OrdinalRecipientAddressDialog extends StatefulWidget { + const OrdinalRecipientAddressDialog({ + super.key, + required this.inscriptionNumber, + }); + + final int inscriptionNumber; + + @override + State createState() => + _OrdinalRecipientAddressDialogState(); +} + +class _OrdinalRecipientAddressDialogState + extends State { + late final TextEditingController _controller; + + @override + void initState() { + _controller = TextEditingController(); + super.initState(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + Widget _buildTextField(BuildContext context) { + return TextField( + controller: _controller, + decoration: InputDecoration( + hintText: "Paste address", + hintStyle: STextStyles.fieldLabel(context), + suffixIcon: IconButton( + icon: SvgPicture.asset( + Assets.svg.clipboard, + width: 20, + height: 20, + color: Theme.of( + context, + ).extension()!.textFieldDefaultSearchIconLeft, + ), + onPressed: () async { + final data = await Clipboard.getData("text/plain"); + if (data?.text != null) { + _controller.text = data!.text!; + setState(() {}); + } + }, + ), + ), + style: STextStyles.field(context), + autofocus: true, + ); + } + + @override + Widget build(BuildContext context) { + if (Util.isDesktop) { + return DesktopDialog( + maxWidth: 500, + maxHeight: 300, + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Send ordinal #${widget.inscriptionNumber}", + style: STextStyles.desktopH3(context), + ), + const SizedBox(height: 12), + Text( + "Enter the recipient address", + style: STextStyles.desktopTextMedium(context), + ), + const SizedBox(height: 8), + _buildTextField(context), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox(width: 16), + Expanded( + child: PrimaryButton( + label: "Continue", + onPressed: () { + final address = _controller.text.trim(); + if (address.isNotEmpty) { + Navigator.of(context).pop(address); + } + }, + ), + ), + ], + ), + ], + ), + ), + ); + } + + return StackDialogBase( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Send ordinal #${widget.inscriptionNumber}", + style: STextStyles.pageTitleH2(context), + ), + const SizedBox(height: 12), + Text( + "Enter the recipient address", + style: STextStyles.smallMed12(context), + ), + const SizedBox(height: 8), + _buildTextField(context), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox(width: 16), + Expanded( + child: PrimaryButton( + label: "Continue", + onPressed: () { + final address = _controller.text.trim(); + if (address.isNotEmpty) { + Navigator.of(context).pop(address); + } + }, + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages/ordinals/widgets/ordinal_card.dart b/lib/pages/ordinals/widgets/ordinal_card.dart index 31eeb57337..8662e7dddf 100644 --- a/lib/pages/ordinals/widgets/ordinal_card.dart +++ b/lib/pages/ordinals/widgets/ordinal_card.dart @@ -5,14 +5,11 @@ import '../../../pages_desktop_specific/ordinals/desktop_ordinal_details_view.da import '../../../utilities/constants.dart'; import '../../../utilities/text_styles.dart'; import '../../../utilities/util.dart'; +import '../../../widgets/ordinal_image.dart'; import '../../../widgets/rounded_white_container.dart'; class OrdinalCard extends StatelessWidget { - const OrdinalCard({ - super.key, - required this.walletId, - required this.ordinal, - }); + const OrdinalCard({super.key, required this.walletId, required this.ordinal}); final String walletId; final Ordinal ordinal; @@ -38,12 +35,7 @@ class OrdinalCard extends StatelessWidget { borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, ), - child: Image.network( - ordinal.content, // Use the preview URL as the image source - fit: BoxFit.cover, - filterQuality: - FilterQuality.none, // Set the filter mode to nearest - ), + child: OrdinalImage(url: ordinal.content), ), ), const Spacer(), diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart index dfd6c98bd0..84af619745 100644 --- a/lib/pages/send_view/confirm_transaction_view.dart +++ b/lib/pages/send_view/confirm_transaction_view.dart @@ -14,10 +14,13 @@ import 'dart:io'; import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; +import 'package:isar_community/isar.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import '../../models/input.dart'; import '../../models/isar/models/transaction_note.dart'; +import '../../models/isar/ordinal.dart'; import '../../notifications/show_flush_bar.dart'; import '../../pages_desktop_specific/coin_control/desktop_coin_control_use_dialog.dart'; import '../../pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart'; @@ -1418,6 +1421,71 @@ class _ConfirmTransactionViewState ), ), ), + // Ordinal UTXO spend warning + Builder( + builder: (context) { + final usedUtxos = widget.txData.usedUTXOs; + if (usedUtxos == null || usedUtxos.isEmpty) { + return const SizedBox.shrink(); + } + + final db = ref.read(mainDBProvider); + bool hasOrdinal = false; + for (final input in usedUtxos) { + if (input is StandardInput) { + final ordinal = db.isar.ordinals + .where() + .filter() + .walletIdEqualTo(walletId) + .and() + .utxoTXIDEqualTo(input.utxo.txid) + .and() + .utxoVOUTEqualTo(input.utxo.vout) + .findFirstSync(); + if (ordinal != null) { + hasOrdinal = true; + break; + } + } + } + + if (!hasOrdinal) return const SizedBox.shrink(); + + return Padding( + padding: isDesktop + ? const EdgeInsets.symmetric(horizontal: 32, vertical: 8) + : const EdgeInsets.symmetric(vertical: 8), + child: RoundedContainer( + color: Theme.of( + context, + ).extension()!.warningBackground, + child: Row( + children: [ + Icon( + Icons.warning_amber_rounded, + color: Theme.of( + context, + ).extension()!.warningForeground, + size: 20, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + "This transaction spends a UTXO containing " + "an ordinal inscription.", + style: STextStyles.smallMed12(context).copyWith( + color: Theme.of( + context, + ).extension()!.warningForeground, + ), + ), + ), + ], + ), + ), + ); + }, + ), SizedBox(height: isDesktop ? 28 : 16), Padding( padding: isDesktop @@ -1446,7 +1514,10 @@ class _ConfirmTransactionViewState right: 32, bottom: 32, ), - child: DesktopAuthSend(coin: coin), + child: DesktopAuthSend( + coin: coin, + tokenTicker: widget.isTokenTx ? unit : null, + ), ), ], ), diff --git a/lib/pages/settings_views/global_settings_view/global_settings_view.dart b/lib/pages/settings_views/global_settings_view/global_settings_view.dart index 5dc6d4101f..2232d198d4 100644 --- a/lib/pages/settings_views/global_settings_view/global_settings_view.dart +++ b/lib/pages/settings_views/global_settings_view/global_settings_view.dart @@ -11,8 +11,10 @@ import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../app_config.dart'; +import '../../../providers/providers.dart'; import '../../../route_generator.dart'; import '../../../themes/stack_colors.dart'; import '../../../utilities/assets.dart'; @@ -22,6 +24,7 @@ import '../../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../../widgets/rounded_white_container.dart'; import '../../address_book_views/address_book_view.dart'; import '../../pinpad_views/lock_screen_view.dart'; +import '../../shopinbit/shopinbit_settings_view.dart'; import '../sub_widgets/settings_list_button.dart'; import 'about_view.dart'; import 'advanced_views/advanced_settings_view.dart'; @@ -96,21 +99,19 @@ class GlobalSettingsView extends StatelessWidget { Navigator.push( context, RouteGenerator.getRoute( - shouldUseMaterialRoute: - RouteGenerator - .useMaterialPageRoute, - builder: - (_) => const LockscreenView( - showBackButton: true, - routeOnSuccess: - StackBackupView.routeName, - biometricsCancelButtonString: - "CANCEL", - biometricsLocalizedReason: - "Authenticate to access ${AppConfig.prefix} backup & restore settings", - biometricsAuthenticationTitle: - "${AppConfig.prefix} backup", - ), + shouldUseMaterialRoute: RouteGenerator + .useMaterialPageRoute, + builder: (_) => const LockscreenView( + showBackButton: true, + routeOnSuccess: + StackBackupView.routeName, + biometricsCancelButtonString: + "CANCEL", + biometricsLocalizedReason: + "Authenticate to access ${AppConfig.prefix} backup & restore settings", + biometricsAuthenticationTitle: + "${AppConfig.prefix} backup", + ), settings: const RouteSettings( name: "/swblockscreen", ), @@ -247,6 +248,34 @@ class GlobalSettingsView extends StatelessWidget { }, ), const SizedBox(height: 8), + Consumer( + builder: (_, ref, __) { + final familiarity = ref.watch( + prefsChangeNotifierProvider.select( + (v) => v.familiarity, + ), + ); + if (familiarity < 6) { + return const SizedBox.shrink(); + } + return Column( + children: [ + const SizedBox(height: 8), + SettingsListButton( + iconAssetName: Assets.svg.key, + iconSize: 16, + title: "ShopinBit", + onPressed: () { + Navigator.of(context).pushNamed( + ShopInBitSettingsView.routeName, + ); + }, + ), + ], + ); + }, + ), + const SizedBox(height: 8), SettingsListButton( iconAssetName: Assets.svg.questionMessage, iconSize: 16, diff --git a/lib/pages/settings_views/global_settings_view/hidden_settings.dart b/lib/pages/settings_views/global_settings_view/hidden_settings.dart index 52b78cbcf0..ca102c4c59 100644 --- a/lib/pages/settings_views/global_settings_view/hidden_settings.dart +++ b/lib/pages/settings_views/global_settings_view/hidden_settings.dart @@ -14,8 +14,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import '../../../db/isar/main_db.dart'; import '../../../notifications/show_flush_bar.dart'; import '../../../providers/providers.dart'; +import '../../../services/cakepay/cakepay_service.dart'; +import '../../../services/cakepay/src/models/order.dart'; import '../../../themes/stack_colors.dart'; import '../../../utilities/assets.dart'; import '../../../utilities/constants.dart'; @@ -41,19 +44,17 @@ class HiddenSettings extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: AppBarIconButton( size: 32, - color: - Theme.of( - context, - ).extension()!.textFieldDefaultBG, + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, shadows: const [], icon: SvgPicture.asset( Assets.svg.arrowLeft, width: 18, height: 18, - color: - Theme.of( - context, - ).extension()!.topNavIconPrimary, + color: Theme.of( + context, + ).extension()!.topNavIconPrimary, ), onPressed: Navigator.of(context).pop, ), @@ -81,8 +82,8 @@ class HiddenSettings extends StatelessWidget { ref .read(prefsChangeNotifierProvider) .advancedFiroFeatures = !ref - .read(prefsChangeNotifierProvider) - .advancedFiroFeatures; + .read(prefsChangeNotifierProvider) + .advancedFiroFeatures; }, child: RoundedWhiteContainer( child: Text( @@ -94,10 +95,9 @@ class HiddenSettings extends StatelessWidget { ? "Hide advanced Firo features" : "Show advanced Firo features", style: STextStyles.button(context).copyWith( - color: - Theme.of(context) - .extension()! - .accentColorDark, + color: Theme.of(context) + .extension()! + .accentColorDark, ), ), ), @@ -109,10 +109,9 @@ class HiddenSettings extends StatelessWidget { builder: (_, ref, __) { return GestureDetector( onTap: () async { - final notifs = - ref - .read(notificationsProvider) - .notifications; + final notifs = ref + .read(notificationsProvider) + .notifications; for (final n in notifs) { await ref @@ -137,10 +136,9 @@ class HiddenSettings extends StatelessWidget { child: Text( "Delete notifications", style: STextStyles.button(context).copyWith( - color: - Theme.of(context) - .extension()! - .accentColorDark, + color: Theme.of(context) + .extension()! + .accentColorDark, ), ), ), @@ -153,17 +151,17 @@ class HiddenSettings extends StatelessWidget { return GestureDetector( onTap: () async { ref - .read(prefsChangeNotifierProvider) - .logsPath = null; + .read(prefsChangeNotifierProvider) + .logsPath = + null; }, child: RoundedWhiteContainer( child: Text( "Reset log location", style: STextStyles.button(context).copyWith( - color: - Theme.of(context) - .extension()! - .accentColorDark, + color: Theme.of(context) + .extension()! + .accentColorDark, ), ), ), @@ -285,14 +283,14 @@ class HiddenSettings extends StatelessWidget { 6) { return GestureDetector( onTap: () async { - final familiarity = - ref - .read(prefsChangeNotifierProvider) - .familiarity; + final familiarity = ref + .read(prefsChangeNotifierProvider) + .familiarity; if (familiarity < 6) { ref - .read(prefsChangeNotifierProvider) - .familiarity = 6; + .read(prefsChangeNotifierProvider) + .familiarity = + 6; Constants.exchangeForExperiencedUsers(6); } @@ -300,14 +298,12 @@ class HiddenSettings extends StatelessWidget { child: RoundedWhiteContainer( child: Text( "Enable exchange", - style: STextStyles.button( - context, - ).copyWith( - color: - Theme.of(context) + style: STextStyles.button(context) + .copyWith( + color: Theme.of(context) .extension()! .accentColorDark, - ), + ), ), ), ); @@ -317,34 +313,81 @@ class HiddenSettings extends StatelessWidget { }, ), const SizedBox(height: 12), + GestureDetector( + onTap: () async { + final tickets = MainDB.instance + .getShopInBitTickets(); + for (final t in tickets) { + await MainDB.instance.deleteShopInBitTicket( + t.ticketId, + ); + } + if (context.mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: + "Deleted ${tickets.length} ShopinBit request(s)", + context: context, + ), + ); + } + }, + child: RoundedWhiteContainer( + child: Text( + "Delete all ShopinBit requests", + style: STextStyles.button(context).copyWith( + color: Theme.of( + context, + ).extension()!.accentColorDark, + ), + ), + ), + ), + const SizedBox(height: 12), Consumer( builder: (_, ref, __) { return GestureDetector( onTap: () async { await showDialog( context: context, - builder: - (_) => TorWarningDialog( - coin: Stellar( - CryptoCurrencyNetwork.main, - ), - ), + builder: (_) => TorWarningDialog( + coin: Stellar(CryptoCurrencyNetwork.main), + ), ); }, child: RoundedWhiteContainer( child: Text( "Show Tor warning popup", style: STextStyles.button(context).copyWith( - color: - Theme.of(context) - .extension()! - .accentColorDark, + color: Theme.of(context) + .extension()! + .accentColorDark, ), ), ), ); }, ), + const SizedBox(height: 12), + GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (_) => const _CakePayDevStatusDialog(), + ); + }, + child: RoundedWhiteContainer( + child: Text( + "CakePay status overrides", + style: STextStyles.button(context).copyWith( + color: Theme.of( + context, + ).extension()!.accentColorDark, + ), + ), + ), + ), // const SizedBox( // height: 12, // ), @@ -385,3 +428,124 @@ class HiddenSettings extends StatelessWidget { ); } } + +class _CakePayDevStatusDialog extends StatefulWidget { + const _CakePayDevStatusDialog(); + + @override + State<_CakePayDevStatusDialog> createState() => + _CakePayDevStatusDialogState(); +} + +class _CakePayDevStatusDialogState extends State<_CakePayDevStatusDialog> { + late final List _orderIds; + + @override + void initState() { + super.initState(); + _orderIds = CakePayService.instance.getOrderIds(); + } + + @override + Widget build(BuildContext context) { + final colors = Theme.of(context).extension()!; + + return AlertDialog( + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "CakePay Status Overrides", + style: STextStyles.pageTitleH2(context), + ), + if (CakePayService.devStatusOverrides.isNotEmpty) + TextButton( + onPressed: () { + setState(() { + CakePayService.devStatusOverrides.clear(); + }); + }, + child: Text("Clear all", style: STextStyles.link2(context)), + ), + ], + ), + content: SizedBox( + width: 400, + child: _orderIds.isEmpty + ? Text( + "No tracked CakePay orders.\n" + "Create an order first, then come back here to override " + "its status.", + style: STextStyles.itemSubtitle(context), + ) + : ListView.separated( + shrinkWrap: true, + itemCount: _orderIds.length, + separatorBuilder: (_, __) => const Divider(height: 16), + itemBuilder: (context, index) { + final id = _orderIds[index]; + final current = CakePayService.devStatusOverrides[id]; + + return Row( + children: [ + Expanded( + child: Text( + id.length > 12 ? "${id.substring(0, 12)}..." : id, + style: STextStyles.itemSubtitle12(context), + ), + ), + const SizedBox(width: 8), + DropdownButton( + value: current, + hint: Text( + "API default", + style: STextStyles.itemSubtitle12( + context, + ).copyWith(color: colors.textSubtitle2), + ), + underline: const SizedBox(), + isDense: true, + items: [ + DropdownMenuItem( + value: null, + child: Text( + "API default", + style: STextStyles.itemSubtitle12( + context, + ).copyWith(color: colors.textSubtitle2), + ), + ), + ...CakePayOrderStatus.values.map( + (s) => DropdownMenuItem( + value: s, + child: Text( + s.value, + style: STextStyles.itemSubtitle12(context), + ), + ), + ), + ], + onChanged: (value) { + setState(() { + if (value == null) { + CakePayService.devStatusOverrides.remove(id); + } else { + CakePayService.devStatusOverrides[id] = value; + } + }); + }, + ), + ], + ); + }, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text("Close", style: STextStyles.button(context)), + ), + ], + ); + } +} diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index 75411e9cbc..b873664535 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -97,12 +97,11 @@ class _AddEditNodeViewState extends ConsumerState { } Future attemptSave() async { - final canConnect = await testNodeConnection( + final canConnect = await ref.read(testNodeConnectionProvider)( context: context, onSuccess: _onTestSuccess, cryptoCurrency: coin, nodeFormData: ref.read(nodeFormDataProvider), - ref: ref, ); bool? shouldSave; @@ -688,13 +687,13 @@ class _AddEditNodeViewState extends ConsumerState { buttonHeight: isDesktop ? ButtonHeight.l : null, onPressed: testConnectionEnabled ? () async { - final testPassed = await testNodeConnection( - context: context, - onSuccess: _onTestSuccess, - cryptoCurrency: coin, - nodeFormData: ref.read(nodeFormDataProvider), - ref: ref, - ); + final testPassed = + await ref.read(testNodeConnectionProvider)( + context: context, + onSuccess: _onTestSuccess, + cryptoCurrency: coin, + nodeFormData: ref.read(nodeFormDataProvider), + ); if (context.mounted) { if (testPassed) { unawaited( diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart index 481c3906d5..233ba0c6b9 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart @@ -127,125 +127,113 @@ class _NodeDetailsViewState extends ConsumerState { return ConditionalParent( condition: !isDesktop, - builder: - (child) => Background( - child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 75), - ); - } - if (context.mounted) { - Navigator.of(context).pop(); - } - }, - ), - title: Text( - "Node details", - style: STextStyles.navBarTitle(context), - ), - actions: [ - // if (!nodeId.startsWith(DefaultNodes.defaultNodeIdPrefix)) - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, + builder: (child) => Background( + child: Scaffold( + backgroundColor: Theme.of( + context, + ).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (context.mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Node details", + style: STextStyles.navBarTitle(context), + ), + actions: [ + // if (!nodeId.startsWith(DefaultNodes.defaultNodeIdPrefix)) + Padding( + padding: const EdgeInsets.only(top: 10, bottom: 10, right: 10), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("nodeDetailsEditNodeAppBarButtonKey"), + size: 36, + shadows: const [], + color: Theme.of( + context, + ).extension()!.background, + icon: SvgPicture.asset( + Assets.svg.pencil, + color: Theme.of( + context, + ).extension()!.accentColorDark, + width: 20, + height: 20, ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("nodeDetailsEditNodeAppBarButtonKey"), - size: 36, - shadows: const [], - color: - Theme.of( - context, - ).extension()!.background, - icon: SvgPicture.asset( - Assets.svg.pencil, - color: - Theme.of( - context, - ).extension()!.accentColorDark, - width: 20, - height: 20, - ), - onPressed: () { - Navigator.of(context).pushNamed( - AddEditNodeView.routeName, - arguments: Tuple4( - AddEditNodeViewType.edit, - coin, - nodeId, - popRouteName, - ), - ); - }, - ), - ), - ), - ], - ), - body: SafeArea( - child: Padding( - padding: const EdgeInsets.only(top: 12, left: 12, right: 12), - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(4), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 8, - ), - child: IntrinsicHeight(child: child), - ), + onPressed: () { + Navigator.of(context).pushNamed( + AddEditNodeView.routeName, + arguments: Tuple4( + AddEditNodeViewType.edit, + coin, + nodeId, + popRouteName, ), ); }, ), ), ), + ], + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.only(top: 12, left: 12, right: 12), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(4), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 8, + ), + child: IntrinsicHeight(child: child), + ), + ), + ); + }, + ), ), ), + ), + ), child: ConditionalParent( condition: isDesktop, - builder: - (child) => DesktopDialog( - maxWidth: 580, - maxHeight: double.infinity, - child: Column( - mainAxisSize: MainAxisSize.min, + builder: (child) => DesktopDialog( + maxWidth: 580, + maxHeight: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( children: [ - Row( - children: [ - const SizedBox(width: 8), - const AppBarBackButton(iconSize: 24, size: 40), - Text( - "Node details", - style: STextStyles.desktopH3(context), - ), - ], - ), - Padding( - padding: const EdgeInsets.only( - left: 32, - right: 32, - top: 16, - bottom: 32, - ), - child: child, - ), + const SizedBox(width: 8), + const AppBarBackButton(iconSize: 24, size: 40), + Text("Node details", style: STextStyles.desktopH3(context)), ], ), - ), + Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + top: 16, + bottom: 32, + ), + child: child, + ), + ], + ), + ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -260,28 +248,27 @@ class _NodeDetailsViewState extends ConsumerState { if (isDesktop && canDelete) SizedBox( height: 56, - child: - _desktopReadOnly - ? null - : Row( - children: [ - Expanded( - child: DeleteButton( - label: "Delete node", - desktopMed: true, - onPressed: () async { - Navigator.of(context).pop(); + child: _desktopReadOnly + ? null + : Row( + children: [ + Expanded( + child: DeleteButton( + label: "Delete node", + desktopMed: true, + onPressed: () async { + Navigator.of(context).pop(); - await ref - .read(nodeServiceChangeNotifierProvider) - .delete(node!.id, true); - }, - ), + await ref + .read(nodeServiceChangeNotifierProvider) + .delete(node!.id, true); + }, ), - const SizedBox(width: 16), - const Spacer(), - ], - ), + ), + const SizedBox(width: 16), + const Spacer(), + ], + ), ), if (isDesktop && !_desktopReadOnly && canDelete) const SizedBox(height: 45), @@ -292,10 +279,9 @@ class _NodeDetailsViewState extends ConsumerState { label: "Test connection", buttonHeight: isDesktop ? ButtonHeight.l : null, onPressed: () async { - final node = - ref - .read(nodeServiceChangeNotifierProvider) - .getNodeById(id: nodeId)!; + final node = ref + .read(nodeServiceChangeNotifierProvider) + .getNodeById(id: nodeId)!; final TorPlainNetworkOption netOption; if (ref.read(nodeFormDataProvider).netOption != null) { @@ -307,28 +293,27 @@ class _NodeDetailsViewState extends ConsumerState { ); } - final nodeFormData = - NodeFormData() - ..useSSL = node.useSSL - ..trusted = node.trusted - ..name = node.name - ..host = node.host - ..login = node.loginName - ..port = node.port - ..isFailover = node.isFailover - ..netOption = netOption - ..forceNoTor = node.forceNoTor; + final nodeFormData = NodeFormData() + ..useSSL = node.useSSL + ..trusted = node.trusted + ..name = node.name + ..host = node.host + ..login = node.loginName + ..port = node.port + ..isFailover = node.isFailover + ..netOption = netOption + ..forceNoTor = node.forceNoTor; nodeFormData.password = await node.getPassword( ref.read(secureStoreProvider), ); if (context.mounted) { - final testPassed = await testNodeConnection( - context: context, - nodeFormData: nodeFormData, - cryptoCurrency: coin, - ref: ref, - ); + final testPassed = + await ref.read(testNodeConnectionProvider)( + context: context, + nodeFormData: nodeFormData, + cryptoCurrency: coin, + ); if (testPassed) { if (context.mounted) { @@ -359,52 +344,54 @@ class _NodeDetailsViewState extends ConsumerState { if (isDesktop) Expanded( child: - // !nodeId.startsWith(DefaultNodes.defaultNodeIdPrefix) - // ? - PrimaryButton( - label: _desktopReadOnly ? "Edit" : "Save", - buttonHeight: ButtonHeight.l, - onPressed: () async { - final shouldSave = _desktopReadOnly == false; - setState(() { - _desktopReadOnly = !_desktopReadOnly; - }); + // !nodeId.startsWith(DefaultNodes.defaultNodeIdPrefix) + // ? + PrimaryButton( + label: _desktopReadOnly ? "Edit" : "Save", + buttonHeight: ButtonHeight.l, + onPressed: () async { + final shouldSave = _desktopReadOnly == false; + setState(() { + _desktopReadOnly = !_desktopReadOnly; + }); - if (shouldSave) { - final editedNode = node!.copyWith( - host: ref.read(nodeFormDataProvider).host, - port: ref.read(nodeFormDataProvider).port, - name: ref.read(nodeFormDataProvider).name, - useSSL: ref.read(nodeFormDataProvider).useSSL, - trusted: ref.read(nodeFormDataProvider).trusted, - loginName: ref.read(nodeFormDataProvider).login, - isFailover: - ref.read(nodeFormDataProvider).isFailover, - torEnabled: - ref.read(nodeFormDataProvider).netOption == - TorPlainNetworkOption.tor || - ref.read(nodeFormDataProvider).netOption == - TorPlainNetworkOption.both, - clearnetEnabled: - ref.read(nodeFormDataProvider).netOption == - TorPlainNetworkOption.clear || - ref.read(nodeFormDataProvider).netOption == - TorPlainNetworkOption.both, - forceNoTor: - ref.read(nodeFormDataProvider).forceNoTor, - ); - - await ref - .read(nodeServiceChangeNotifierProvider) - .save( - editedNode, - ref.read(nodeFormDataProvider).password, - true, + if (shouldSave) { + final editedNode = node!.copyWith( + host: ref.read(nodeFormDataProvider).host, + port: ref.read(nodeFormDataProvider).port, + name: ref.read(nodeFormDataProvider).name, + useSSL: ref.read(nodeFormDataProvider).useSSL, + trusted: ref.read(nodeFormDataProvider).trusted, + loginName: ref.read(nodeFormDataProvider).login, + isFailover: ref + .read(nodeFormDataProvider) + .isFailover, + torEnabled: + ref.read(nodeFormDataProvider).netOption == + TorPlainNetworkOption.tor || + ref.read(nodeFormDataProvider).netOption == + TorPlainNetworkOption.both, + clearnetEnabled: + ref.read(nodeFormDataProvider).netOption == + TorPlainNetworkOption.clear || + ref.read(nodeFormDataProvider).netOption == + TorPlainNetworkOption.both, + forceNoTor: ref + .read(nodeFormDataProvider) + .forceNoTor, ); - await _notifyWalletsOfUpdatedNode(); - } - }, - ), + + await ref + .read(nodeServiceChangeNotifierProvider) + .save( + editedNode, + ref.read(nodeFormDataProvider).password, + true, + ); + await _notifyWalletsOfUpdatedNode(); + } + }, + ), // : Container() ), ], diff --git a/lib/pages/shopinbit/shopinbit_car_fee_view.dart b/lib/pages/shopinbit/shopinbit_car_fee_view.dart new file mode 100644 index 0000000000..7f691375d2 --- /dev/null +++ b/lib/pages/shopinbit/shopinbit_car_fee_view.dart @@ -0,0 +1,786 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../db/isar/main_db.dart'; +import '../../models/shopinbit/shopinbit_order_model.dart'; +import '../../notifications/show_flush_bar.dart'; +import '../../services/shopinbit/shopinbit_service.dart'; +import '../../services/shopinbit/src/models/address.dart'; +import '../../services/shopinbit/src/models/car_research.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/assets.dart'; +import '../../utilities/constants.dart'; +import '../../utilities/logger.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../more_view/services_view.dart'; +import '../../widgets/background.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../../widgets/rounded_white_container.dart'; +import '../../widgets/stack_text_field.dart'; +import 'shopinbit_car_research_payment_view.dart'; +import 'shopinbit_step_2.dart'; + +class ShopInBitCarFeeView extends StatefulWidget { + const ShopInBitCarFeeView({super.key, required this.model}); + + static const String routeName = "/shopInBitCarFee"; + + final ShopInBitOrderModel model; + + @override + State createState() => _ShopInBitCarFeeViewState(); +} + +class _ShopInBitCarFeeViewState extends State { + late final TextEditingController _nameController; + late final TextEditingController _streetController; + late final TextEditingController _cityController; + late final TextEditingController _postalCodeController; + late final FocusNode _nameFocusNode; + late final FocusNode _streetFocusNode; + late final FocusNode _cityFocusNode; + late final FocusNode _postalCodeFocusNode; + + List> _countries = []; + String? _selectedCountryIso; + bool _loadingCountries = false; + final TextEditingController _countrySearchController = + TextEditingController(); + + // Billing address (optional, separate from delivery) + bool _differentBilling = false; + late final TextEditingController _billingNameController; + late final TextEditingController _billingStreetController; + late final TextEditingController _billingCityController; + late final TextEditingController _billingPostalCodeController; + late final FocusNode _billingNameFocusNode; + late final FocusNode _billingStreetFocusNode; + late final FocusNode _billingCityFocusNode; + late final FocusNode _billingPostalCodeFocusNode; + String? _selectedBillingCountryIso; + final TextEditingController _billingCountrySearchController = + TextEditingController(); + + String _displayedFee = "223.00 EUR"; + bool _submitting = false; + + bool get _canContinue { + if (_nameController.text.trim().isEmpty || + _streetController.text.trim().isEmpty || + _cityController.text.trim().isEmpty || + _postalCodeController.text.trim().isEmpty || + _selectedCountryIso == null) { + return false; + } + if (_differentBilling) { + if (_billingNameController.text.trim().isEmpty || + _billingStreetController.text.trim().isEmpty || + _billingCityController.text.trim().isEmpty || + _billingPostalCodeController.text.trim().isEmpty || + _selectedBillingCountryIso == null) { + return false; + } + } + return true; + } + + @override + void initState() { + super.initState(); + _nameController = TextEditingController(text: widget.model.shippingName); + _streetController = TextEditingController( + text: widget.model.shippingStreet, + ); + _cityController = TextEditingController(text: widget.model.shippingCity); + _postalCodeController = TextEditingController( + text: widget.model.shippingPostalCode, + ); + _nameFocusNode = FocusNode(); + _streetFocusNode = FocusNode(); + _cityFocusNode = FocusNode(); + _postalCodeFocusNode = FocusNode(); + _billingNameController = TextEditingController(); + _billingStreetController = TextEditingController(); + _billingCityController = TextEditingController(); + _billingPostalCodeController = TextEditingController(); + _billingNameFocusNode = FocusNode(); + _billingStreetFocusNode = FocusNode(); + _billingCityFocusNode = FocusNode(); + _billingPostalCodeFocusNode = FocusNode(); + + for (final node in [ + _nameFocusNode, + _streetFocusNode, + _cityFocusNode, + _postalCodeFocusNode, + _billingNameFocusNode, + _billingStreetFocusNode, + _billingCityFocusNode, + _billingPostalCodeFocusNode, + ]) { + node.addListener(() => setState(() {})); + } + + _fetchCountries(); + + // Pre-select country on resume if model already has a shipping country. + if (widget.model.shippingCountry.isNotEmpty) { + _selectedCountryIso = widget.model.shippingCountry; + } + } + + @override + void dispose() { + _nameController.dispose(); + _streetController.dispose(); + _cityController.dispose(); + _postalCodeController.dispose(); + _nameFocusNode.dispose(); + _streetFocusNode.dispose(); + _cityFocusNode.dispose(); + _postalCodeFocusNode.dispose(); + _billingNameController.dispose(); + _billingStreetController.dispose(); + _billingCityController.dispose(); + _billingPostalCodeController.dispose(); + _billingNameFocusNode.dispose(); + _billingStreetFocusNode.dispose(); + _billingCityFocusNode.dispose(); + _billingPostalCodeFocusNode.dispose(); + _billingCountrySearchController.dispose(); + _countrySearchController.dispose(); + super.dispose(); + } + + void _popToStep2() { + Navigator.of(context).popUntil((route) { + final name = route.settings.name; + if (name == ShopInBitStep2.routeName) { + return true; + } + if (name == ServicesView.routeName) { + return true; + } + if (route.isFirst) { + return true; + } + return false; + }); + } + + Future _fetchCountries() async { + setState(() => _loadingCountries = true); + try { + final resp = await ShopInBitService.instance.client.getCountries(); + if (resp.hasError || resp.value == null) return; + _countries = resp.value!; + if (_selectedCountryIso != null && + !_countries.any((c) => c['iso'] == _selectedCountryIso)) { + _selectedCountryIso = null; + } + } catch (_) { + // leave list empty; user will see no items + } finally { + if (mounted) setState(() => _loadingCountries = false); + } + } + + ({String first, String last}) _splitFullName(String raw) { + final trimmed = raw.trim(); + final idx = trimmed.lastIndexOf(' '); + if (idx >= 0) { + return ( + first: trimmed.substring(0, idx).trim(), + last: trimmed.substring(idx + 1).trim(), + ); + } + return (first: trimmed, last: ""); + } + + Future _createInvoice() async { + if (_submitting) return; + setState(() => _submitting = true); + try { + await ShopInBitService.instance.ensureCustomerKey(); + + // Delivery address (always provided) + final deliveryName = _splitFullName(_nameController.text); + widget.model.setShippingAddress( + name: _nameController.text.trim(), + street: _streetController.text.trim(), + city: _cityController.text.trim(), + postalCode: _postalCodeController.text.trim(), + country: _selectedCountryIso!, + ); + + // Billing address: use separate billing fields if different, else use delivery + final Address billing; + if (_differentBilling) { + final billingName = _splitFullName(_billingNameController.text); + billing = Address( + firstName: billingName.first, + lastName: billingName.last, + street: _billingStreetController.text.trim(), + zip: _billingPostalCodeController.text.trim(), + city: _billingCityController.text.trim(), + country: _selectedBillingCountryIso!, + ); + } else { + billing = Address( + firstName: deliveryName.first, + lastName: deliveryName.last, + street: _streetController.text.trim(), + zip: _postalCodeController.text.trim(), + city: _cityController.text.trim(), + country: _selectedCountryIso!, + ); + } + + final resp = await ShopInBitService.instance.client + .createCarResearchInvoice(billing: billing); + + if (resp.hasError || resp.value == null) { + if (mounted) { + setState(() => _submitting = false); + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: resp.exception?.message ?? "Failed to create invoice", + context: context, + ), + ); + } + return; + } + + final invoice = resp.value!; + + // Persist pending state so the user can resume if they close the dialog. + // Sentinel ticketId; unique-replace index ensures at most one pending record. + widget.model.ticketId = "pending-car-research"; + widget.model.carResearchInvoiceId = invoice.btcpayInvoice; + widget.model.isPendingPayment = true; + widget.model.carResearchExpiresAt = invoice.expiresAt; + widget.model.carResearchPaymentLinks = jsonEncode(invoice.paymentLinks); + await MainDB.instance.putShopInBitTicket(widget.model.toIsarTicket()); + + // Best-effort fee fetch; do not block navigation on fee parse failure. + await _loadFee(invoice); + + if (!mounted) return; + + if (Util.isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + unawaited( + showDialog( + context: context, + builder: (_) => ShopInBitCarResearchPaymentView( + model: widget.model, + invoice: invoice, + ), + ), + ); + } else { + unawaited( + Navigator.of(context).pushNamed( + ShopInBitCarResearchPaymentView.routeName, + arguments: (widget.model, invoice), + ), + ); + } + } catch (e) { + if (mounted) { + setState(() => _submitting = false); + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: e.toString(), + context: context, + ), + ); + } + } + } + + String? _parseBip21Amount(String uri) { + try { + // Parse amount from payment URI query params. + final qIdx = uri.indexOf('?'); + if (qIdx < 0) return null; + final query = uri.substring(qIdx + 1); + final params = Uri.splitQueryString(query); + return params['amount'] ?? params['tx_amount']; + } catch (_) { + return null; + } + } + + Future _loadFee(CarResearchInvoice invoice) async { + // Keep status call for visibility into any future API changes surfacing + // a fee field. Today the endpoint returns only {status, additional}, so + // we source the displayed amount from the BIP21 payment URIs instead. + try { + final resp = await ShopInBitService.instance.client + .getCarResearchInvoiceStatus(invoice.btcpayInvoice); + if (resp.hasError || resp.value == null) { + Logging.instance.i( + "CarResearch status response (car_fee_view): error " + "${resp.exception?.message}", + ); + } else { + Logging.instance.i( + "CarResearch status response (car_fee_view): ${resp.value}", + ); + } + } catch (e) { + Logging.instance.i( + "CarResearch status response (car_fee_view): threw $e", + ); + } + + // Primary fee source: parse BIP21 `amount` query param from paymentLinks. + Logging.instance.i( + "CarResearch paymentLinks (car_fee_view): ${invoice.paymentLinks}", + ); + try { + for (final entry in invoice.paymentLinks.entries) { + final parsed = _parseBip21Amount(entry.value); + if (parsed != null && parsed.isNotEmpty) { + if (mounted) { + setState( + () => _displayedFee = "$parsed ${entry.key.toUpperCase()}", + ); + } + return; + } + } + } catch (_) { + // Leave placeholder in place. + } + // No parse succeeded: leave the existing "223.00 EUR" business-rule + // placeholder in place rather than showing "--". + } + + Widget _buildField({ + required TextEditingController controller, + required FocusNode focusNode, + required String label, + required bool isDesktop, + }) { + return ClipRRect( + borderRadius: BorderRadius.circular(Constants.size.circularBorderRadius), + child: TextField( + controller: controller, + focusNode: focusNode, + autocorrect: false, + enableSuggestions: false, + onChanged: (_) => setState(() {}), + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: + standardInputDecoration( + label, + focusNode, + context, + desktopMed: isDesktop, + ).copyWith( + filled: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + ), + ); + } + + Widget _buildCountryDropdown({ + required String? value, + required ValueChanged onChanged, + required String hint, + required TextEditingController searchController, + required bool isDesktop, + }) { + return ClipRRect( + borderRadius: BorderRadius.circular(Constants.size.circularBorderRadius), + child: DropdownButtonHideUnderline( + child: DropdownButton2( + value: value, + items: _countries + .map( + (c) => DropdownMenuItem( + value: c['iso'] as String, + child: Text( + c['label'] as String, + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + ) + : STextStyles.w500_14(context), + ), + ), + ) + .toList(), + onMenuStateChange: (isOpen) { + if (!isOpen) { + searchController.clear(); + } + }, + onChanged: _loadingCountries ? null : onChanged, + hint: Text( + _loadingCountries ? "Loading countries..." : hint, + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldDefaultSearchIconLeft, + ) + : STextStyles.fieldLabel(context), + ), + isExpanded: true, + buttonStyleData: ButtonStyleData( + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + iconStyleData: IconStyleData( + icon: Padding( + padding: const EdgeInsets.only(right: 10), + child: SvgPicture.asset( + Assets.svg.chevronDown, + width: 12, + height: 6, + color: Theme.of( + context, + ).extension()!.textFieldActiveSearchIconRight, + ), + ), + ), + dropdownStyleData: DropdownStyleData( + offset: const Offset(0, 0), + elevation: 0, + maxHeight: 300, + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + dropdownSearchData: DropdownSearchData( + searchController: searchController, + searchInnerWidgetHeight: 48, + searchInnerWidget: TextFormField( + controller: searchController, + decoration: InputDecoration( + isDense: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 14, + ), + hintText: "Search...", + hintStyle: STextStyles.fieldLabel(context), + border: InputBorder.none, + ), + ), + searchMatchFn: (item, searchValue) { + final label = _countries + .where((c) => c['iso'] == item.value) + .map((c) => c['label'] as String) + .firstOrNull; + return label?.toLowerCase().contains(searchValue.toLowerCase()) ?? + false; + }, + ), + menuItemStyleData: const MenuItemStyleData( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + final spacing = SizedBox(height: isDesktop ? 16 : 12); + + final content = Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Car research fee", + style: isDesktop + ? STextStyles.desktopH2(context) + : STextStyles.pageTitleH1(context), + ), + SizedBox(height: isDesktop ? 16 : 8), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Research fee", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.itemSubtitle(context), + ), + Text( + _displayedFee, + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.itemSubtitle(context), + ), + ], + ), + ), + SizedBox(height: isDesktop ? 24 : 16), + Text( + "Delivery address", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + SizedBox(height: isDesktop ? 16 : 12), + _buildField( + controller: _nameController, + focusNode: _nameFocusNode, + label: "Full name", + isDesktop: isDesktop, + ), + spacing, + _buildField( + controller: _streetController, + focusNode: _streetFocusNode, + label: "Street address", + isDesktop: isDesktop, + ), + spacing, + Row( + children: [ + Expanded( + child: _buildField( + controller: _cityController, + focusNode: _cityFocusNode, + label: "City", + isDesktop: isDesktop, + ), + ), + SizedBox(width: isDesktop ? 16 : 12), + Expanded( + child: _buildField( + controller: _postalCodeController, + focusNode: _postalCodeFocusNode, + label: "Postal code", + isDesktop: isDesktop, + ), + ), + ], + ), + spacing, + _buildCountryDropdown( + value: _selectedCountryIso, + onChanged: (v) => setState(() => _selectedCountryIso = v), + hint: "Country", + searchController: _countrySearchController, + isDesktop: isDesktop, + ), + spacing, + GestureDetector( + onTap: () { + setState(() { + _differentBilling = !_differentBilling; + if (!_differentBilling) { + _billingNameController.clear(); + _billingStreetController.clear(); + _billingCityController.clear(); + _billingPostalCodeController.clear(); + _selectedBillingCountryIso = null; + } + }); + }, + child: Container( + color: Colors.transparent, + child: Row( + children: [ + SizedBox( + width: 20, + height: 20, + child: IgnorePointer( + child: Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: _differentBilling, + onChanged: (_) {}, + ), + ), + ), + const SizedBox(width: 12), + Text( + "Different billing address?", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.w500_14(context), + ), + ], + ), + ), + ), + if (_differentBilling) ...[ + spacing, + Text( + "Billing address", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + SizedBox(height: isDesktop ? 16 : 12), + _buildField( + controller: _billingNameController, + focusNode: _billingNameFocusNode, + label: "Full name", + isDesktop: isDesktop, + ), + spacing, + _buildField( + controller: _billingStreetController, + focusNode: _billingStreetFocusNode, + label: "Street address", + isDesktop: isDesktop, + ), + spacing, + Row( + children: [ + Expanded( + child: _buildField( + controller: _billingCityController, + focusNode: _billingCityFocusNode, + label: "City", + isDesktop: isDesktop, + ), + ), + SizedBox(width: isDesktop ? 16 : 12), + Expanded( + child: _buildField( + controller: _billingPostalCodeController, + focusNode: _billingPostalCodeFocusNode, + label: "Postal code", + isDesktop: isDesktop, + ), + ), + ], + ), + spacing, + _buildCountryDropdown( + value: _selectedBillingCountryIso, + onChanged: (v) => setState(() => _selectedBillingCountryIso = v), + hint: "Billing country", + searchController: _billingCountrySearchController, + isDesktop: isDesktop, + ), + ], + if (!isDesktop) const Spacer(), + if (isDesktop) const SizedBox(height: 24), + PrimaryButton( + label: "Pay research fee", + enabled: _canContinue && !_submitting, + onPressed: (_canContinue && !_submitting) + ? () => unawaited(_createInvoice()) + : null, + ), + ], + ); + + if (isDesktop) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 750, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "ShopinBit", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), + child: SingleChildScrollView(child: content), + ), + ), + ], + ), + ); + } + + return Background( + child: PopScope( + canPop: false, + onPopInvokedWithResult: (bool didPop, dynamic result) { + if (!didPop) { + _popToStep2(); + } + }, + child: Scaffold( + backgroundColor: Theme.of( + context, + ).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton(onPressed: _popToStep2), + title: Text("ShopinBit", style: STextStyles.navBarTitle(context)), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.all(16), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 32, + ), + child: IntrinsicHeight(child: content), + ), + ), + ); + }, + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/shopinbit/shopinbit_car_research_payment_view.dart b/lib/pages/shopinbit/shopinbit_car_research_payment_view.dart new file mode 100644 index 0000000000..0073ee831e --- /dev/null +++ b/lib/pages/shopinbit/shopinbit_car_research_payment_view.dart @@ -0,0 +1,1028 @@ +import 'dart:async'; + +import 'package:decimal/decimal.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../app_config.dart'; +import '../../db/isar/main_db.dart'; +import '../../models/isar/models/ethereum/eth_contract.dart'; +import '../../models/shopinbit/shopinbit_order_model.dart'; +import '../../notifications/show_flush_bar.dart'; +import '../../providers/providers.dart'; +import '../../route_generator.dart'; +import '../../services/shopinbit/shopinbit_service.dart'; +import '../../services/shopinbit/src/models/car_research.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/address_utils.dart'; +import '../../utilities/amount/amount.dart'; +import '../../utilities/assets.dart'; +import '../../utilities/logger.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../wallets/crypto_currency/crypto_currency.dart'; +import '../more_view/services_view.dart'; +import '../../widgets/background.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../../widgets/desktop/secondary_button.dart'; +import '../../widgets/stack_dialog.dart'; +import '../../widgets/qr.dart'; +import '../../widgets/rounded_white_container.dart'; +import 'shopinbit_order_created.dart'; +import 'shopinbit_send_from_view.dart'; +import 'shopinbit_tickets_view.dart'; + +enum _PaymentFlowState { + idle, + polling, + loggingPayment, + creatingRequest, + complete, + error, +} + +class ShopInBitCarResearchPaymentView extends ConsumerStatefulWidget { + const ShopInBitCarResearchPaymentView({ + super.key, + required this.model, + required this.invoice, + }); + + static const String routeName = "/shopInBitCarResearchPayment"; + + final ShopInBitOrderModel model; + final CarResearchInvoice invoice; + + @override + ConsumerState createState() => + _ShopInBitCarResearchPaymentViewState(); +} + +class _ShopInBitCarResearchPaymentViewState + extends ConsumerState { + static const Set _terminalStates = { + // concierge heritage + "paid", + "paid_over", + "paid_late", + "payment_processing", + // BTCPay / car research likely + "settled", + "confirmed", + "complete", + "completed", + "finalized", + }; + + Timer? _pollTimer; + Map? _status; + _PaymentFlowState _flowState = _PaymentFlowState.idle; + String _statusString = "ready_to_pay"; + List _methods = []; + List _addresses = []; + int _selectedMethod = 0; + + String get _currentAddress => + _selectedMethod < _addresses.length ? _addresses[_selectedMethod] : ""; + + bool get _isTerminal { + final s = _statusString.toLowerCase().trim(); + return _terminalStates.contains(s); + } + + bool get _payNowEnabled => + !_isTerminal && _flowState == _PaymentFlowState.idle; + + void _confirmPayment() { + // Keep polling while the user is in the send flow. + final method = _methods[_selectedMethod]; + final ticker = method.toUpperCase(); + + final coin = AppConfig.getCryptoCurrencyForTicker(ticker); + + String address = ""; + Amount? amount; + EthContract? tokenContract; + + if (_currentAddress.isNotEmpty) { + final parsed = AddressUtils.parsePaymentUri(_currentAddress); + + if (parsed?.address != null && parsed!.address.isNotEmpty) { + address = parsed.address; + } else { + final raw = _currentAddress; + final colonIdx = raw.indexOf(':'); + if (colonIdx != -1) { + final afterScheme = raw.substring(colonIdx + 1); + final qIdx = afterScheme.indexOf('?'); + address = qIdx != -1 ? afterScheme.substring(0, qIdx) : afterScheme; + } else { + address = raw; + } + } + + String? amountStr = parsed?.amount; + if (amountStr == null || amountStr.isEmpty) { + final uri = Uri.tryParse(_currentAddress); + if (uri != null) { + amountStr = uri.queryParameters['amount']; + } + } + // Car research flow has no concierge PaymentInfo.due fallback. + + final int fractionDigits; + if (coin != null) { + fractionDigits = coin.fractionDigits; + } else if (ticker == "USDT") { + fractionDigits = 6; + } else { + fractionDigits = 8; + } + + if (amountStr != null && amountStr.isNotEmpty) { + try { + amount = Amount.fromDecimal( + Decimal.parse(amountStr), + fractionDigits: fractionDigits, + ); + } catch (_) {} + } + } + + if (coin != null && address.isNotEmpty) { + _navigateToSendFrom(coin: coin, amount: amount, address: address); + return; + } + + if (ticker == "USDT" && address.isNotEmpty) { + const usdtAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7"; + tokenContract = ref.read(mainDBProvider).getEthContractSync(usdtAddress); + if (tokenContract != null) { + final ethCoin = AppConfig.getCryptoCurrencyForTicker("ETH"); + if (ethCoin != null) { + _navigateToSendFrom( + coin: ethCoin, + amount: amount, + address: address, + tokenContract: tokenContract, + ); + return; + } + } + } + + // No compatible wallet coin found: surface an info flushbar and keep + // the user on this screen so they can pay externally and then use the + // "CHECK FOR PAYMENT" button. + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: + "No compatible wallet for $method. " + "Pay externally, then tap CHECK FOR PAYMENT.", + context: context, + ), + ); + } + + void _navigateToSendFrom({ + required CryptoCurrency coin, + required Amount? amount, + required String address, + EthContract? tokenContract, + }) { + if (Util.isDesktop) { + // Show send-from on top of the payment dialog, not instead of it. + unawaited( + showDialog( + context: context, + builder: (_) => ShopInBitSendFromView( + coin: coin, + amount: amount, + address: address, + model: widget.model, + shouldPopRoot: true, + tokenContract: tokenContract, + ), + ), + ); + } else { + Navigator.of(context).push( + RouteGenerator.getRoute( + shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, + builder: (_) => ShopInBitSendFromView( + coin: coin, + amount: amount, + address: address, + model: widget.model, + tokenContract: tokenContract, + // After wallet send, pop back to this view to continue polling. + routeOnSuccessName: ShopInBitCarResearchPaymentView.routeName, + ), + settings: const RouteSettings(name: ShopInBitSendFromView.routeName), + ), + ); + } + } + + Future _checkForPayment() async { + if (_flowState != _PaymentFlowState.idle) return; + setState(() => _flowState = _PaymentFlowState.polling); + try { + await _pollStatus(); + if (!mounted) return; + if (!_isTerminal && _flowState != _PaymentFlowState.loggingPayment) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: + "Payment not yet confirmed. Please wait a moment and try again.", + context: context, + ), + ); + } + } finally { + if (mounted && _flowState == _PaymentFlowState.polling) { + setState(() => _flowState = _PaymentFlowState.idle); + } + } + } + + String? _parseBip21Amount(String uri) { + try { + // Parse amount from payment URI query params. + final qIdx = uri.indexOf('?'); + if (qIdx < 0) return null; + final query = uri.substring(qIdx + 1); + final params = Uri.splitQueryString(query); + return params['amount'] ?? params['tx_amount']; + } catch (_) { + return null; + } + } + + String get _displayedFee { + // API status endpoint does not expose a fee field (confirmed: returns + // only {status, additional}). Parse the amount from the BIP21 payment + // URI for the currently-selected method, fall back to the 223.00 EUR + // business-rule value if no parse succeeds. + final links = widget.invoice.paymentLinks; + if (_selectedMethod < _methods.length) { + final methodKey = _methods[_selectedMethod]; + // _methods holds upper-cased keys; links map may be case-sensitive. + String? uri = links[methodKey]; + if (uri == null) { + for (final entry in links.entries) { + if (entry.key.toUpperCase() == methodKey) { + uri = entry.value; + break; + } + } + } + if (uri != null) { + final parsed = _parseBip21Amount(uri); + if (parsed != null && parsed.isNotEmpty) { + return "$parsed $methodKey"; + } + } + } + return "223.00 EUR"; + } + + String get _statusLabel { + switch (_statusString) { + case "payment_processing": + return "Confirming..."; + case "paid": + case "paid_over": + case "paid_late": + return "Paid ✓"; + case "ready_to_pay": + default: + return "Waiting for payment"; + } + } + + @override + void initState() { + super.initState(); + final links = widget.invoice.paymentLinks; + _methods = links.keys.map((k) => k.toUpperCase()).toList(); + _addresses = links.values.toList(); + // Kick off an immediate poll then start periodic polling. + unawaited(_pollStatus()); + _pollTimer = Timer.periodic( + const Duration(seconds: 15), + (_) => unawaited(_pollStatus()), + ); + } + + @override + void dispose() { + _pollTimer?.cancel(); + super.dispose(); + } + + void _popToTickets() { + Navigator.of(context).popUntil((route) { + final name = route.settings.name; + if (name == ShopInBitTicketsView.routeName) { + return true; + } + if (name == ServicesView.routeName) { + return true; + } + if (route.isFirst) { + return true; + } + return false; + }); + } + + Future _pollStatus() async { + try { + final resp = await ShopInBitService.instance.client + .getCarResearchInvoiceStatus(widget.invoice.btcpayInvoice); + if (resp.hasError || resp.value == null) { + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: + resp.exception?.message ?? "Failed to fetch invoice status", + context: context, + ), + ); + } + return; + } + if (!mounted) return; + Logging.instance.i( + "CarResearch status response (payment_view): ${resp.value}", + ); + Logging.instance.i( + "CarResearch paymentLinks (payment_view): " + "${widget.invoice.paymentLinks}", + ); + setState(() { + _status = resp.value!; + _statusString = _status!["status"]?.toString() ?? _statusString; + }); + if (_isTerminal) { + _pollTimer?.cancel(); + await _processPaymentAndRequest(); + } + } catch (e) { + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: e.toString(), + context: context, + ), + ); + } + } + } + + Future _processPaymentAndRequest() async { + // Guard: only one entry allowed + if (_flowState == _PaymentFlowState.loggingPayment || + _flowState == _PaymentFlowState.creatingRequest || + _flowState == _PaymentFlowState.complete || + _flowState == _PaymentFlowState.error) + return; + + // Skip logCarResearchPayment if the fee was already logged. + final existingFeeTicket = widget.model.feeTicketNumber; + if (existingFeeTicket != null) { + if (!widget.model.needsCreateRequest) { + // Both steps already done: navigate to success directly. + if (!mounted) return; + setState(() => _flowState = _PaymentFlowState.complete); + if (Util.isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + unawaited( + showDialog( + context: context, + builder: (_) => ShopInBitOrderCreated(model: widget.model), + ), + ); + } else { + unawaited( + Navigator.of(context).pushNamed( + ShopInBitOrderCreated.routeName, + arguments: widget.model, + ), + ); + } + return; + } + // Fee logged; skip to createRequest. + setState(() => _flowState = _PaymentFlowState.creatingRequest); + _pollTimer?.cancel(); + try { + final customerKey = await ShopInBitService.instance.ensureCustomerKey(); + final comment = + "${widget.model.requestDescription}\n\n" + "The Client paid the car research fee (#$existingFeeTicket)"; + final reqResp = await ShopInBitService.instance.client.createRequest( + customerPseudonym: widget.model.displayName, + externalCustomerKey: customerKey, + serviceType: "car", + comment: comment, + deliveryCountry: widget.model.deliveryCountry, + ); + if (reqResp.hasError || reqResp.value == null) { + if (mounted) { + setState(() => _flowState = _PaymentFlowState.error); + await showDialog( + context: context, + barrierDismissible: false, + builder: (ctx) => StackDialog( + title: "Request Failed", + message: + "Payment was confirmed but we couldn't submit your car " + "research request. You can retry from My Requests.\n\n" + "Error: ${reqResp.exception?.message ?? 'Unknown error'}", + leftButton: SecondaryButton( + label: "Retry Now", + onPressed: () { + Navigator.of(ctx).pop(); + _retryCreateRequest(existingFeeTicket, customerKey); + }, + ), + rightButton: PrimaryButton( + label: "My Requests", + onPressed: () { + Navigator.of(ctx).pop(); + _popToTickets(); + }, + ), + ), + ); + } + return; + } + final requestRef = reqResp.value!; + final prevTicketId = widget.model.ticketId; + widget.model.apiTicketId = requestRef.id; + widget.model.ticketId = requestRef.number; + widget.model.status = ShopInBitOrderStatus.pending; + widget.model.isPendingPayment = false; + widget.model.needsCreateRequest = false; + await MainDB.instance.putShopInBitTicket(widget.model.toIsarTicket()); + // Remove the sentinel record. + if (prevTicketId != null && prevTicketId != widget.model.ticketId) { + await MainDB.instance.deleteShopInBitTicket(prevTicketId); + } + if (!mounted) return; + setState(() => _flowState = _PaymentFlowState.complete); + if (Util.isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + unawaited( + showDialog( + context: context, + builder: (_) => ShopInBitOrderCreated(model: widget.model), + ), + ); + } else { + unawaited( + Navigator.of(context).pushNamed( + ShopInBitOrderCreated.routeName, + arguments: widget.model, + ), + ); + } + } catch (e) { + if (mounted) { + setState(() => _flowState = _PaymentFlowState.error); + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: e.toString(), + context: context, + ), + ); + } + } + return; + } + + setState(() => _flowState = _PaymentFlowState.loggingPayment); + _pollTimer?.cancel(); + + try { + final logResp = await ShopInBitService.instance.client + .logCarResearchPayment(widget.invoice.btcpayInvoice); + if (logResp.hasError || logResp.value == null) { + if (mounted) { + setState(() => _flowState = _PaymentFlowState.error); + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: logResp.exception?.message ?? "Failed to log payment", + context: context, + ), + ); + } + return; + } + + final feeResult = logResp.value!; + + // Persist feeTicketNumber on the existing model (a new DB row creates a spurious list entry). + widget.model.feeTicketNumber = feeResult.ticketNumber; + widget.model.needsCreateRequest = true; + await MainDB.instance.putShopInBitTicket(widget.model.toIsarTicket()); + + if (!mounted) return; + setState(() => _flowState = _PaymentFlowState.creatingRequest); + + final customerKey = await ShopInBitService.instance.ensureCustomerKey(); + final comment = + "${widget.model.requestDescription}\n\n" + "The Client paid the car research fee (#${feeResult.ticketNumber})"; + + final reqResp = await ShopInBitService.instance.client.createRequest( + customerPseudonym: widget.model.displayName, + externalCustomerKey: customerKey, + serviceType: "car", + comment: comment, + deliveryCountry: widget.model.deliveryCountry, + ); + + if (reqResp.hasError || reqResp.value == null) { + // createRequest failed: fee receipt already persisted, show retry + if (mounted) { + setState(() => _flowState = _PaymentFlowState.error); + await showDialog( + context: context, + barrierDismissible: false, + builder: (ctx) => StackDialog( + title: "Request Failed", + message: + "Payment was confirmed but we couldn't submit your car " + "research request. You can retry from My Requests.\n\n" + "Error: ${reqResp.exception?.message ?? 'Unknown error'}", + leftButton: SecondaryButton( + label: "Retry Now", + onPressed: () { + Navigator.of(ctx).pop(); + _retryCreateRequest(feeResult.ticketNumber, customerKey); + }, + ), + rightButton: PrimaryButton( + label: "My Requests", + onPressed: () { + Navigator.of(ctx).pop(); + _popToTickets(); + }, + ), + ), + ); + } + return; + } + + final requestRef = reqResp.value!; + final prevTicketId = widget.model.ticketId; + widget.model.apiTicketId = requestRef.id; + widget.model.ticketId = requestRef.number; + widget.model.status = ShopInBitOrderStatus.pending; + widget.model.isPendingPayment = false; + widget.model.needsCreateRequest = false; + await MainDB.instance.putShopInBitTicket(widget.model.toIsarTicket()); + if (prevTicketId != null && prevTicketId != widget.model.ticketId) { + await MainDB.instance.deleteShopInBitTicket(prevTicketId); + } + + if (!mounted) return; + setState(() => _flowState = _PaymentFlowState.complete); + + if (Util.isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + unawaited( + showDialog( + context: context, + builder: (_) => ShopInBitOrderCreated(model: widget.model), + ), + ); + } else { + unawaited( + Navigator.of( + context, + ).pushNamed(ShopInBitOrderCreated.routeName, arguments: widget.model), + ); + } + } catch (e) { + if (mounted) { + setState(() => _flowState = _PaymentFlowState.error); + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: e.toString(), + context: context, + ), + ); + } + } + } + + Future _retryCreateRequest( + String feeTicketNumber, + String customerKey, + ) async { + if (_flowState == _PaymentFlowState.creatingRequest) return; + setState(() => _flowState = _PaymentFlowState.creatingRequest); + + try { + final comment = + "${widget.model.requestDescription}\n\n" + "The Client paid the car research fee (#$feeTicketNumber)"; + + final reqResp = await ShopInBitService.instance.client.createRequest( + customerPseudonym: widget.model.displayName, + externalCustomerKey: customerKey, + serviceType: "car", + comment: comment, + deliveryCountry: widget.model.deliveryCountry, + ); + + if (reqResp.hasError || reqResp.value == null) { + if (mounted) { + setState(() => _flowState = _PaymentFlowState.error); + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: reqResp.exception?.message ?? "Retry failed", + context: context, + ), + ); + } + return; + } + + final requestRef = reqResp.value!; + widget.model.apiTicketId = requestRef.id; + widget.model.ticketId = requestRef.number; + widget.model.status = ShopInBitOrderStatus.pending; + // Flow complete: clear the resume flag before saving. + widget.model.isPendingPayment = false; + await MainDB.instance.putShopInBitTicket(widget.model.toIsarTicket()); + + // Update fee receipt ticket + final feeTickets = MainDB.instance.getShopInBitTickets().where( + (t) => t.ticketId == feeTicketNumber, + ); + if (feeTickets.isNotEmpty) { + final feeTicket = feeTickets.first; + feeTicket.needsCreateRequest = false; + await MainDB.instance.putShopInBitTicket(feeTicket); + } + + if (!mounted) return; + setState(() => _flowState = _PaymentFlowState.complete); + + if (Util.isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + unawaited( + showDialog( + context: context, + builder: (_) => ShopInBitOrderCreated(model: widget.model), + ), + ); + } else { + unawaited( + Navigator.of( + context, + ).pushNamed(ShopInBitOrderCreated.routeName, arguments: widget.model), + ); + } + } catch (e) { + if (mounted) { + setState(() => _flowState = _PaymentFlowState.error); + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: e.toString(), + context: context, + ), + ); + } + } + } + + void _copyAddress(BuildContext context) { + final addr = _currentAddress; + if (addr.isEmpty) return; + Clipboard.setData(ClipboardData(text: addr)); + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ), + ); + } + + @override + Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + + final ticker = _selectedMethod < _methods.length + ? _methods[_selectedMethod].toUpperCase() + : ""; + + bool hasWallets = false; + if (ticker == "USDT") { + const usdtAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7"; + hasWallets = ref + .watch(pWallets) + .wallets + .any( + (w) => + w.info.coin is Ethereum && + w.info.tokenContractAddresses.contains(usdtAddress), + ); + } else { + final coin = AppConfig.getCryptoCurrencyForTicker(ticker); + if (coin != null) { + hasWallets = ref + .watch(pWallets) + .wallets + .any((e) => e.info.coin == coin); + } + } + + final methodSelector = _methods.length <= 1 + ? Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Text( + _methods.isEmpty ? "" : _methods.first, + textAlign: TextAlign.center, + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + ) + : Row( + children: List.generate(_methods.length, (index) { + final isSelected = _selectedMethod == index; + return Expanded( + child: GestureDetector( + onTap: () => setState(() => _selectedMethod = index), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 10), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: isSelected + ? Theme.of( + context, + ).extension()!.accentColorBlue + : Colors.transparent, + width: 2, + ), + ), + ), + child: Text( + _methods[index], + textAlign: TextAlign.center, + style: + (isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle12(context)) + .copyWith( + color: isSelected + ? Theme.of(context) + .extension()! + .accentColorBlue + : null, + fontWeight: isSelected ? FontWeight.w600 : null, + ), + ), + ), + ), + ); + }), + ); + + final content = Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Car research payment", + style: isDesktop + ? STextStyles.desktopH2(context) + : STextStyles.pageTitleH1(context), + ), + SizedBox(height: isDesktop ? 16 : 8), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Research fee", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.itemSubtitle(context), + ), + Text( + _displayedFee, + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.itemSubtitle(context), + ), + ], + ), + ), + SizedBox(height: isDesktop ? 16 : 8), + RoundedWhiteContainer( + child: Row( + children: [ + Text( + "Status:", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + const SizedBox(width: 8), + Text( + _statusLabel, + style: + (isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context)) + .copyWith( + color: _isTerminal + ? Theme.of( + context, + ).extension()!.accentColorGreen + : null, + fontWeight: _isTerminal ? FontWeight.w600 : null, + ), + ), + ], + ), + ), + SizedBox(height: isDesktop ? 24 : 16), + methodSelector, + SizedBox(height: isDesktop ? 24 : 16), + if (_currentAddress.isNotEmpty) + Center( + child: QR(data: _currentAddress, size: isDesktop ? 200 : 180), + ) + else + Center( + child: Padding( + padding: const EdgeInsets.all(32), + child: Text( + "No payment address available", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.itemSubtitle(context), + ), + ), + ), + SizedBox(height: isDesktop ? 16 : 12), + if (_currentAddress.isNotEmpty) + GestureDetector( + onTap: () => _copyAddress(context), + child: RoundedWhiteContainer( + child: Column( + children: [ + Row( + children: [ + Text( + "${_methods[_selectedMethod]} address", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + const Spacer(), + Icon( + Icons.copy, + size: 14, + color: Theme.of( + context, + ).extension()!.accentColorBlue, + ), + const SizedBox(width: 4), + Text("Copy", style: STextStyles.link2(context)), + ], + ), + const SizedBox(height: 4), + Row( + children: [ + Expanded( + child: Text( + _currentAddress, + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + ), + ], + ), + ], + ), + ), + ), + if (!isDesktop) const Spacer(), + if (isDesktop) const SizedBox(height: 24), + PrimaryButton( + label: _flowState == _PaymentFlowState.polling + ? "Checking..." + : (_flowState == _PaymentFlowState.loggingPayment || + _flowState == _PaymentFlowState.creatingRequest) + ? "Processing..." + : (hasWallets ? "PAY NOW" : "CHECK FOR PAYMENT"), + enabled: _payNowEnabled, + onPressed: _payNowEnabled + ? (hasWallets + ? _confirmPayment + : () => unawaited(_checkForPayment())) + : null, + ), + ], + ); + + if (isDesktop) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 750, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "ShopinBit", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), + child: SingleChildScrollView(child: content), + ), + ), + ], + ), + ); + } + + return Background( + child: PopScope( + canPop: false, + onPopInvokedWithResult: (bool didPop, dynamic result) { + if (!didPop) { + _popToTickets(); + } + }, + child: Scaffold( + backgroundColor: Theme.of( + context, + ).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton(onPressed: _popToTickets), + title: Text("ShopinBit", style: STextStyles.navBarTitle(context)), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.all(16), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 32, + ), + child: IntrinsicHeight(child: content), + ), + ), + ); + }, + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/shopinbit/shopinbit_confirm_send_view.dart b/lib/pages/shopinbit/shopinbit_confirm_send_view.dart new file mode 100644 index 0000000000..de7bc1770e --- /dev/null +++ b/lib/pages/shopinbit/shopinbit_confirm_send_view.dart @@ -0,0 +1,750 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../db/isar/main_db.dart'; +import '../../models/isar/models/isar_models.dart'; +import '../../models/shopinbit/shopinbit_order_model.dart'; +import '../../notifications/show_flush_bar.dart'; +import '../../pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart'; +import '../../providers/providers.dart'; +import '../../route_generator.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/amount/amount.dart'; +import '../../utilities/amount/amount_formatter.dart'; +import '../../utilities/constants.dart'; +import '../../utilities/logger.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../wallets/models/tx_data.dart'; +import '../../wallets/wallet/impl/ethereum_wallet.dart'; +import '../../wallets/wallet/wallet.dart'; +import '../../widgets/background.dart'; +import '../../widgets/conditional_parent.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../../widgets/desktop/secondary_button.dart'; +import '../../widgets/rounded_container.dart'; +import '../../widgets/rounded_white_container.dart'; +import '../../widgets/stack_dialog.dart'; +import '../pinpad_views/lock_screen_view.dart'; +import '../send_view/sub_widgets/sending_transaction_dialog.dart'; +import '../wallet_view/wallet_view.dart'; + +class ShopInBitConfirmSendView extends ConsumerStatefulWidget { + const ShopInBitConfirmSendView({ + super.key, + required this.txData, + required this.walletId, + this.routeOnSuccessName = WalletView.routeName, + required this.model, + this.tokenContract, + }); + + static const String routeName = "/shopInBitConfirmSend"; + + final TxData txData; + final String walletId; + final String routeOnSuccessName; + final ShopInBitOrderModel model; + final EthContract? tokenContract; + + @override + ConsumerState createState() => + _ShopInBitConfirmSendViewState(); +} + +class _ShopInBitConfirmSendViewState + extends ConsumerState { + late final String walletId; + late final String routeOnSuccessName; + late final ShopInBitOrderModel model; + + final isDesktop = Util.isDesktop; + + Future _attemptSend(BuildContext context) async { + final parentWallet = ref.read(pWallets).getWallet(walletId); + final coin = parentWallet.info.coin; + + final sendProgressController = ProgressAndSuccessController(); + + unawaited( + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return SendingTransactionDialog( + coin: coin, + controller: sendProgressController, + ); + }, + ), + ); + + final time = Future.delayed(const Duration(milliseconds: 2500)); + + late String txid; + Future txidFuture; + + final String note = widget.txData.note ?? ""; + + try { + final wallet = widget.tokenContract != null + ? Wallet.loadTokenWallet( + ethWallet: parentWallet as EthereumWallet, + contract: widget.tokenContract!, + ) + : parentWallet; + + txidFuture = wallet.confirmSend(txData: widget.txData); + + unawaited(wallet.refresh()); + + final results = await Future.wait([txidFuture, time]); + + sendProgressController.triggerSuccess?.call(); + await Future.delayed(const Duration(seconds: 5)); + + txid = (results.first as TxData).txid!; + + // save note + await ref + .read(mainDBProvider) + .putTransactionNote( + TransactionNote(walletId: walletId, txid: txid, value: note), + ); + + // Update model status after successful broadcast + model.status = ShopInBitOrderStatus.paymentPending; + model.paymentMethod = widget.tokenContract != null + ? widget.tokenContract!.symbol.toUpperCase() + : coin.ticker.toUpperCase(); + + unawaited(MainDB.instance.putShopInBitTicket(model.toIsarTicket())); + + // pop back to wallet + if (context.mounted) { + // pop sending dialog (pushed via showDialog which uses root navigator) + Navigator.of(context, rootNavigator: true).pop(); + + if (Util.isDesktop) { + // pop the confirm send desktop dialog + Navigator.of(context, rootNavigator: true).pop(); + } + + Navigator.of(context).popUntil(ModalRoute.withName(routeOnSuccessName)); + } + } catch (e, s) { + Logging.instance.e( + "Broadcast transaction failed: ", + error: e, + stackTrace: s, + ); + + if (context.mounted) { + // pop sending dialog (pushed via showDialog which uses root navigator) + Navigator.of(context, rootNavigator: true).pop(); + + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return StackDialog( + title: "Broadcast transaction failed", + message: e.toString(), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: Theme.of( + context, + ).extension()!.buttonTextSecondary, + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ); + }, + ); + } + } + } + + Future _confirmSend() async { + final dynamic unlocked; + + final coin = ref.read(pWalletCoin(walletId)); + + if (Util.isDesktop) { + unlocked = await showDialog( + context: context, + builder: (context) => DesktopDialog( + maxWidth: 580, + maxHeight: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [DesktopDialogCloseButton()], + ), + Padding( + padding: const EdgeInsets.only(left: 32, right: 32, bottom: 32), + child: DesktopAuthSend( + coin: coin, + tokenTicker: widget.tokenContract?.symbol, + ), + ), + ], + ), + ), + ); + } else { + unlocked = await Navigator.push( + context, + RouteGenerator.getRoute( + shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, + builder: (_) => const LockscreenView( + showBackButton: true, + popOnSuccess: true, + routeOnSuccessArguments: true, + routeOnSuccess: "", + biometricsCancelButtonString: "CANCEL", + biometricsLocalizedReason: "Authenticate to send transaction", + biometricsAuthenticationTitle: "Confirm Transaction", + ), + settings: const RouteSettings(name: "/confirmsendlockscreen"), + ), + ); + } + + if (unlocked is bool && mounted) { + if (unlocked) { + await _attemptSend(context); + } else { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Invalid passphrase", + context: context, + ), + ); + } + } + } + + @override + void initState() { + walletId = widget.walletId; + routeOnSuccessName = widget.routeOnSuccessName; + model = widget.model; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return ConditionalParent( + condition: !isDesktop, + builder: (child) { + return Background( + child: Scaffold( + backgroundColor: Theme.of( + context, + ).extension()!.background, + appBar: AppBar( + backgroundColor: Theme.of( + context, + ).extension()!.backgroundAppBar, + leading: AppBarBackButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Confirm transaction", + style: STextStyles.navBarTitle(context), + ), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (builderContext, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, + ), + ), + ), + ), + ); + }, + ), + ), + ), + ); + }, + child: ConditionalParent( + condition: isDesktop, + builder: (child) => DesktopDialog( + maxHeight: double.infinity, + maxWidth: 580, + child: Column( + children: [ + Row( + children: [ + const SizedBox(width: 6), + const AppBarBackButton(isCompact: true, iconSize: 23), + const SizedBox(width: 12), + Text( + "Confirm ${widget.tokenContract?.symbol ?? ref.watch(pWalletCoin(walletId)).ticker} transaction", + style: STextStyles.desktopH3(context), + ), + ], + ), + Padding( + padding: const EdgeInsets.only(left: 32, right: 32, bottom: 32), + child: Column( + children: [ + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + borderColor: Theme.of( + context, + ).extension()!.background, + child: child, + ), + const SizedBox(height: 16), + Row( + children: [ + Text( + "Transaction fee", + style: STextStyles.desktopTextExtraExtraSmall( + context, + ), + ), + ], + ), + const SizedBox(height: 10), + RoundedContainer( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + ref + .watch( + pAmountFormatter( + ref.watch(pWalletCoin(walletId)), + ), + ) + .format(widget.txData.fee!), + style: + STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: Theme.of( + context, + ).extension()!.textDark, + ), + ), + ], + ), + ), + const SizedBox(height: 16), + RoundedContainer( + color: Theme.of( + context, + ).extension()!.snackBarBackSuccess, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Total amount", + style: STextStyles.titleBold12(context).copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ), + ), + Builder( + builder: (context) { + final coin = ref.read(pWalletCoin(walletId)); + final fee = widget.txData.fee!; + final amount = widget.txData.amountWithoutChange!; + + if (widget.tokenContract != null) { + final amountStr = + "${amount.decimal.toStringAsFixed(widget.tokenContract!.decimals)} ${widget.tokenContract!.symbol}"; + final feeStr = ref + .watch(pAmountFormatter(coin)) + .format(fee); + return Text( + "$amountStr + $feeStr", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ), + textAlign: TextAlign.right, + ); + } + + final total = amount + fee; + return Text( + ref.watch(pAmountFormatter(coin)).format(total), + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ), + textAlign: TextAlign.right, + ); + }, + ), + ], + ), + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox(width: 16), + Expanded( + child: PrimaryButton( + label: "Send", + buttonHeight: isDesktop ? ButtonHeight.l : null, + onPressed: _confirmSend, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ConditionalParent( + condition: isDesktop, + builder: (child) => Container( + decoration: BoxDecoration( + color: Theme.of(context).extension()!.background, + borderRadius: BorderRadius.vertical( + top: Radius.circular(Constants.size.circularBorderRadius), + ), + ), + child: Padding( + padding: const EdgeInsets.all(12), + child: Row(children: [child]), + ), + ), + child: Text( + "Send ${widget.tokenContract?.symbol ?? ref.watch(pWalletCoin(walletId)).ticker}", + style: isDesktop + ? STextStyles.desktopTextMedium(context) + : STextStyles.pageTitleH1(context), + ), + ), + isDesktop + ? Container( + color: Theme.of( + context, + ).extension()!.background, + height: 1, + ) + : const SizedBox(height: 12), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text("Send from", style: STextStyles.smallMed12(context)), + const SizedBox(height: 4), + Text( + widget.tokenContract != null + ? "${ref.watch(pWalletName(walletId))} (${widget.tokenContract!.symbol})" + : ref.watch(pWalletName(walletId)), + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + isDesktop + ? Container( + color: Theme.of( + context, + ).extension()!.background, + height: 1, + ) + : const SizedBox(height: 12), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "ShopinBit address", + style: STextStyles.smallMed12(context), + ), + const SizedBox(height: 4), + Text( + widget.txData.recipients!.first.address, + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + isDesktop + ? Container( + color: Theme.of( + context, + ).extension()!.background, + height: 1, + ) + : const SizedBox(height: 12), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Amount", style: STextStyles.smallMed12(context)), + ConditionalParent( + condition: isDesktop, + builder: (child) => Row( + children: [ + child, + if (widget.tokenContract == null) + Builder( + builder: (context) { + final coin = ref.watch(pWalletCoin(walletId)); + final price = ref.watch( + priceAnd24hChangeNotifierProvider.select( + (value) => value.getPrice(coin), + ), + ); + final String extra; + if (price == null) { + extra = ""; + } else { + final amountWithoutChange = + widget.txData.amountWithoutChange!; + final value = + (price.value * amountWithoutChange.decimal) + .toAmount(fractionDigits: 2); + final currency = ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.currency, + ), + ); + final locale = ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ); + + extra = + " | ${value.fiatString(locale: locale)} $currency"; + } + + return Text( + extra, + style: + STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: Theme.of( + context, + ).extension()!.textSubtitle2, + ), + ); + }, + ), + ], + ), + child: Text( + widget.tokenContract != null + ? "${widget.txData.amountWithoutChange!.decimal.toStringAsFixed(widget.tokenContract!.decimals)} ${widget.tokenContract!.symbol}" + : ref + .watch( + pAmountFormatter( + ref.watch(pWalletCoin(walletId)), + ), + ) + .format(widget.txData.amountWithoutChange!), + style: STextStyles.itemSubtitle12(context), + textAlign: TextAlign.right, + ), + ), + ], + ), + ), + isDesktop + ? Container( + color: Theme.of( + context, + ).extension()!.background, + height: 1, + ) + : const SizedBox(height: 12), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Transaction fee", + style: STextStyles.smallMed12(context), + ), + Text( + ref + .watch( + pAmountFormatter(ref.read(pWalletCoin(walletId))), + ) + .format(widget.txData.fee!), + style: STextStyles.itemSubtitle12(context), + textAlign: TextAlign.right, + ), + ], + ), + ), + isDesktop + ? Container( + color: Theme.of( + context, + ).extension()!.background, + height: 1, + ) + : const SizedBox(height: 12), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text("Note", style: STextStyles.smallMed12(context)), + const SizedBox(height: 4), + Text( + widget.txData.note ?? "", + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + isDesktop + ? Container( + color: Theme.of( + context, + ).extension()!.background, + height: 1, + ) + : const SizedBox(height: 12), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("Request ID", style: STextStyles.smallMed12(context)), + Text( + model.ticketId ?? "", + style: STextStyles.itemSubtitle12(context), + textAlign: TextAlign.right, + ), + ], + ), + ), + if (!isDesktop) const SizedBox(height: 12), + if (!isDesktop) + RoundedContainer( + color: Theme.of( + context, + ).extension()!.snackBarBackSuccess, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Total amount", + style: STextStyles.titleBold12(context).copyWith( + color: Theme.of( + context, + ).extension()!.textConfirmTotalAmount, + ), + ), + Builder( + builder: (context) { + final coin = ref.watch(pWalletCoin(walletId)); + final fee = widget.txData.fee!; + final amount = widget.txData.amountWithoutChange!; + + if (widget.tokenContract != null) { + final amountStr = + "${amount.decimal.toStringAsFixed(widget.tokenContract!.decimals)} ${widget.tokenContract!.symbol}"; + final feeStr = ref + .watch(pAmountFormatter(coin)) + .format(fee); + return Text( + "$amountStr + $feeStr", + style: STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ), + textAlign: TextAlign.right, + ); + } + + final total = amount + fee; + return Text( + ref.watch(pAmountFormatter(coin)).format(total), + style: STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of( + context, + ).extension()!.textConfirmTotalAmount, + ), + textAlign: TextAlign.right, + ); + }, + ), + ], + ), + ), + if (!isDesktop) const SizedBox(height: 16), + if (!isDesktop) const Spacer(), + if (!isDesktop) + PrimaryButton( + label: "Send", + buttonHeight: isDesktop ? ButtonHeight.l : null, + onPressed: _confirmSend, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/shopinbit/shopinbit_offer_view.dart b/lib/pages/shopinbit/shopinbit_offer_view.dart new file mode 100644 index 0000000000..ace2f3d37d --- /dev/null +++ b/lib/pages/shopinbit/shopinbit_offer_view.dart @@ -0,0 +1,232 @@ +import 'package:flutter/material.dart'; + +import '../../models/shopinbit/shopinbit_order_model.dart'; +import '../../services/shopinbit/shopinbit_service.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../widgets/background.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../../widgets/desktop/secondary_button.dart'; +import '../../widgets/rounded_white_container.dart'; +import 'shopinbit_shipping_view.dart'; + +class ShopInBitOfferView extends StatefulWidget { + const ShopInBitOfferView({super.key, required this.model}); + + static const String routeName = "/shopInBitOffer"; + + final ShopInBitOrderModel model; + + @override + State createState() => _ShopInBitOfferViewState(); +} + +class _ShopInBitOfferViewState extends State { + bool _loading = false; + + @override + void initState() { + super.initState(); + if (widget.model.apiTicketId != 0) { + _loadOffer(); + } + } + + Future _loadOffer() async { + setState(() => _loading = true); + try { + final resp = await ShopInBitService.instance.client.getTicketFull( + widget.model.apiTicketId, + ); + if (!resp.hasError && resp.value != null) { + final t = resp.value!; + widget.model.setOffer( + productName: t.productName, + price: t.customerPrice, + ); + } + } catch (_) { + // Fall back to local data + } finally { + if (mounted) setState(() => _loading = false); + } + } + + @override + Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + final model = widget.model; + + final content = Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Review offer", + style: isDesktop + ? STextStyles.desktopH2(context) + : STextStyles.pageTitleH1(context), + ), + SizedBox(height: isDesktop ? 16 : 8), + Text( + "ShopinBit has found a match for your request.", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.itemSubtitle(context), + ), + SizedBox(height: isDesktop ? 16 : 12), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Product", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + const SizedBox(height: 4), + Text( + model.offerProductName ?? (_loading ? "Loading..." : "N/A"), + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + ], + ), + ), + SizedBox(height: isDesktop ? 12 : 8), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Price (incl. service fee)", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + const SizedBox(height: 4), + Text( + _loading && model.offerPrice == null + ? "Loading..." + : "${model.offerPrice ?? '0'} EUR", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + ], + ), + ), + const Spacer(), + PrimaryButton( + label: "Accept offer", + enabled: !_loading, + onPressed: () { + model.status = ShopInBitOrderStatus.accepted; + if (isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + showDialog( + context: context, + builder: (_) => ShopInBitShippingView(model: model), + ); + } else { + Navigator.of( + context, + ).pushNamed(ShopInBitShippingView.routeName, arguments: model); + } + }, + ), + SizedBox(height: isDesktop ? 16 : 12), + SecondaryButton( + label: "Decline", + onPressed: () { + if (isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + } else { + Navigator.of(context).pop(); + } + }, + ), + ], + ); + + const loadingOverlay = Center( + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ); + + if (isDesktop) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 600, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "ShopinBit", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), + child: Stack(children: [content, if (_loading) loadingOverlay]), + ), + ), + ], + ), + ); + } + + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () => Navigator.of(context).pop(), + ), + title: Text("ShopinBit", style: STextStyles.navBarTitle(context)), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return Stack( + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 32, + ), + child: IntrinsicHeight(child: content), + ), + ), + ), + if (_loading) loadingOverlay, + ], + ); + }, + ), + ), + ), + ); + } +} diff --git a/lib/pages/shopinbit/shopinbit_order_created.dart b/lib/pages/shopinbit/shopinbit_order_created.dart new file mode 100644 index 0000000000..e522e88d78 --- /dev/null +++ b/lib/pages/shopinbit/shopinbit_order_created.dart @@ -0,0 +1,211 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../models/shopinbit/shopinbit_order_model.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/assets.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../widgets/background.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../../widgets/desktop/secondary_button.dart'; +import '../../widgets/rounded_white_container.dart'; +import '../more_view/services_view.dart'; +import 'shopinbit_ticket_detail.dart'; + +class ShopInBitOrderCreated extends StatelessWidget { + const ShopInBitOrderCreated({super.key, required this.model}); + + static const String routeName = "/shopInBitOrderCreated"; + + final ShopInBitOrderModel model; + + static void _popToServices(BuildContext context) { + Navigator.of(context).popUntil((route) { + if (route.settings.name == ServicesView.routeName) { + return true; + } + if (route.isFirst) { + return true; + } + return false; + }); + } + + @override + Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + + final content = Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Spacer(), + SvgPicture.asset( + Assets.svg.checkCircle, + width: isDesktop ? 64 : 48, + height: isDesktop ? 64 : 48, + color: Theme.of(context).extension()!.accentColorGreen, + ), + SizedBox(height: isDesktop ? 24 : 16), + Text( + "Request created!", + style: isDesktop + ? STextStyles.desktopH2(context) + : STextStyles.pageTitleH1(context), + ), + SizedBox(height: isDesktop ? 16 : 8), + Text( + "Your request has been submitted.", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.itemSubtitle(context), + textAlign: TextAlign.center, + ), + SizedBox(height: isDesktop ? 32 : 24), + RoundedWhiteContainer( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Request ID", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + Text( + model.ticketId ?? "N/A", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + ], + ), + SizedBox(height: isDesktop ? 12 : 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Status", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + Text( + "Pending review", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + ], + ), + ], + ), + ), + const Spacer(), + PrimaryButton( + label: "View request", + onPressed: () { + if (Util.isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + showDialog( + context: context, + + builder: (_) => ShopInBitTicketDetail(model: model), + ); + } else { + Navigator.of( + context, + ).pushNamed(ShopInBitTicketDetail.routeName, arguments: model); + } + }, + ), + SizedBox(height: isDesktop ? 16 : 12), + SecondaryButton( + label: "Back to services", + onPressed: () { + if (Util.isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + } else { + _popToServices(context); + } + }, + ), + ], + ); + + if (isDesktop) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 550, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "ShopinBit", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), + child: content, + ), + ), + ], + ), + ); + } + + return Background( + child: PopScope( + canPop: false, + onPopInvokedWithResult: (bool didPop, dynamic result) { + if (!didPop) { + _popToServices(context); + } + }, + child: Scaffold( + backgroundColor: Theme.of( + context, + ).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton(onPressed: () => _popToServices(context)), + title: Text("ShopinBit", style: STextStyles.navBarTitle(context)), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.all(16), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 32, + ), + child: IntrinsicHeight(child: content), + ), + ), + ); + }, + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/shopinbit/shopinbit_payment_view.dart b/lib/pages/shopinbit/shopinbit_payment_view.dart new file mode 100644 index 0000000000..874767033d --- /dev/null +++ b/lib/pages/shopinbit/shopinbit_payment_view.dart @@ -0,0 +1,848 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:decimal/decimal.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../../app_config.dart'; +import '../../models/isar/models/ethereum/eth_contract.dart'; +import '../../models/shopinbit/shopinbit_order_model.dart'; +import '../../notifications/show_flush_bar.dart'; +import '../../providers/providers.dart'; +import '../../route_generator.dart'; +import '../../services/shopinbit/shopinbit_service.dart'; +import '../../services/shopinbit/src/models/payment.dart'; +import '../../themes/coin_icon_provider.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/address_utils.dart'; +import '../../utilities/amount/amount.dart'; +import '../../utilities/assets.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../wallets/crypto_currency/crypto_currency.dart'; +import '../../widgets/background.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../../widgets/desktop/secondary_button.dart'; +import '../../widgets/rounded_white_container.dart'; +import 'shopinbit_send_from_view.dart'; +import 'shopinbit_tickets_view.dart'; + +class ShopInBitPaymentView extends ConsumerStatefulWidget { + const ShopInBitPaymentView({super.key, required this.model}); + + static const String routeName = "/shopInBitPayment"; + + final ShopInBitOrderModel model; + + @override + ConsumerState createState() => + _ShopInBitPaymentViewState(); +} + +class _ShopInBitPaymentViewState extends ConsumerState { + bool _termsAccepted = false; + bool _loading = false; + int _selectedMethod = 0; + Timer? _pollTimer; + + PaymentInfo? _paymentInfo; + + // Derived from API payment_links keys, fallback to defaults + List _methods = ["BTC", "XMR", "USDT"]; + List _addresses = ["", "", ""]; + + String get _currentAddress => + _selectedMethod < _addresses.length ? _addresses[_selectedMethod] : ""; + + String get _totalPrice => + _paymentInfo?.customerPrice ?? widget.model.offerPrice ?? "0"; + + String get _status => _paymentInfo?.status ?? 'ready_to_pay'; + + bool get _isExpiredOrInvalid => _status == 'expired' || _status == 'invalid'; + + bool get _isTerminal => const { + 'paid', + 'paid_over', + 'paid_late', + 'payment_processing', + }.contains(_status); + + bool get _payNowEnabled => + _termsAccepted && !_isExpiredOrInvalid && !_isTerminal; + + @override + void initState() { + super.initState(); + if (widget.model.apiTicketId != 0) { + _loadPayment(); + } + } + + @override + void dispose() { + _pollTimer?.cancel(); + super.dispose(); + } + + void _applyPaymentInfo(PaymentInfo info) { + _paymentInfo = info; + final links = info.paymentLinks; + if (links.isNotEmpty) { + _methods = links.keys.map((k) => k.toUpperCase()).toList(); + _addresses = links.values.toList(); + } + } + + void _startPolling() { + _pollTimer?.cancel(); + _pollTimer = Timer.periodic( + const Duration(seconds: 15), + (_) => _pollPayment(), + ); + } + + Future _pollPayment() async { + try { + final resp = await ShopInBitService.instance.client.getPayment( + widget.model.apiTicketId, + ); + if (!resp.hasError && resp.value != null && mounted) { + setState(() => _applyPaymentInfo(resp.value!)); + if (_isTerminal) { + _pollTimer?.cancel(); + } + } + } catch (_) {} + } + + Future _loadPayment() async { + setState(() => _loading = true); + try { + final resp = await ShopInBitService.instance.client.getPayment( + widget.model.apiTicketId, + ); + if (!resp.hasError && resp.value != null) { + _applyPaymentInfo(resp.value!); + } + } catch (_) { + // Fall back to local/dummy data + } finally { + if (mounted) { + setState(() => _loading = false); + _startPolling(); + } + } + } + + Future _refreshInvoice() async { + setState(() => _loading = true); + try { + final resp = await ShopInBitService.instance.client.getPayment( + widget.model.apiTicketId, + retry: true, + ); + if (!resp.hasError && resp.value != null) { + _applyPaymentInfo(resp.value!); + } + } catch (_) {} + if (mounted) { + setState(() => _loading = false); + _startPolling(); + } + } + + Future _openTerms() async { + const url = "https://api.shopinbit.com/static/policy/terms.html"; + await launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication); + } + + Future _checkForPayment() async { + _pollTimer?.cancel(); + setState(() => _loading = true); + try { + final resp = await ShopInBitService.instance.client.getPayment( + widget.model.apiTicketId, + ); + if (!resp.hasError && resp.value != null && mounted) { + setState(() => _applyPaymentInfo(resp.value!)); + final status = resp.value!.status; + if (const { + 'paid', + 'paid_over', + 'paid_late', + 'payment_processing', + }.contains(status)) { + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "Payment received!", + context: context, + ), + ); + } + } else if (status == 'underpaid') { + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Underpaid. Remaining: ${resp.value!.due ?? '?'} EUR.", + context: context, + ), + ); + } + } else { + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "No payment detected yet.", + context: context, + ), + ); + } + } + } else if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: resp.exception?.message ?? "Failed to check payment.", + context: context, + ), + ); + } + } catch (e) { + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: e.toString(), + context: context, + ), + ); + } + } finally { + if (mounted) { + setState(() => _loading = false); + if (!_isTerminal) { + _startPolling(); + } + } + } + } + + void _confirmPayment() { + _pollTimer?.cancel(); + final method = _methods[_selectedMethod]; + final ticker = method.toUpperCase(); + + final coin = AppConfig.getCryptoCurrencyForTicker(ticker); + + String address = ""; + Amount? amount; + EthContract? tokenContract; + + if (_currentAddress.isNotEmpty) { + final parsed = AddressUtils.parsePaymentUri(_currentAddress); + + if (parsed?.address != null && parsed!.address.isNotEmpty) { + address = parsed.address; + } else { + final raw = _currentAddress; + final colonIdx = raw.indexOf(':'); + if (colonIdx != -1) { + final afterScheme = raw.substring(colonIdx + 1); + final qIdx = afterScheme.indexOf('?'); + address = qIdx != -1 ? afterScheme.substring(0, qIdx) : afterScheme; + } else { + address = raw; + } + } + + String? amountStr = parsed?.amount; + if (amountStr == null || amountStr.isEmpty) { + final uri = Uri.tryParse(_currentAddress); + if (uri != null) { + amountStr = uri.queryParameters['amount']; + } + } + if (amountStr == null || amountStr.isEmpty) { + amountStr = _paymentInfo?.due; + } + + final int fractionDigits; + if (coin != null) { + fractionDigits = coin.fractionDigits; + } else if (ticker == "USDT") { + fractionDigits = 6; + } else { + fractionDigits = 8; + } + + if (amountStr != null && amountStr.isNotEmpty) { + try { + amount = Amount.fromDecimal( + Decimal.parse(amountStr), + fractionDigits: fractionDigits, + ); + } catch (_) {} + } + } + + if (coin != null && address.isNotEmpty) { + _navigateToSendFrom(coin: coin, amount: amount, address: address); + return; + } + + if (ticker == "USDT" && address.isNotEmpty) { + const usdtAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7"; + tokenContract = ref.read(mainDBProvider).getEthContractSync(usdtAddress); + if (tokenContract != null) { + final ethCoin = AppConfig.getCryptoCurrencyForTicker("ETH"); + if (ethCoin != null) { + _navigateToSendFrom( + coin: ethCoin, + amount: amount, + address: address, + tokenContract: tokenContract, + ); + return; + } + } + } + + widget.model.status = ShopInBitOrderStatus.paymentPending; + widget.model.paymentMethod = method; + + if (Util.isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + } else { + Navigator.of(context).popUntil((route) => route.isFirst); + } + } + + void _popToTickets() { + Navigator.of(context).pop(); + } + + void _navigateToSendFrom({ + required CryptoCurrency coin, + required Amount? amount, + required String address, + EthContract? tokenContract, + }) { + if (Util.isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + unawaited( + showDialog( + context: context, + builder: (_) => ShopInBitSendFromView( + coin: coin, + amount: amount, + address: address, + model: widget.model, + shouldPopRoot: true, + tokenContract: tokenContract, + ), + ), + ); + } else { + Navigator.of(context).push( + RouteGenerator.getRoute( + shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, + builder: (_) => ShopInBitSendFromView( + coin: coin, + amount: amount, + address: address, + model: widget.model, + tokenContract: tokenContract, + ), + settings: const RouteSettings(name: ShopInBitSendFromView.routeName), + ), + ); + } + } + + bool _hasWalletForTicker(String ticker) { + if (ticker == "USDT") { + const usdtAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7"; + return ref + .read(pWallets) + .wallets + .any( + (w) => + w.info.coin is Ethereum && + w.info.tokenContractAddresses.contains(usdtAddress), + ); + } else { + final coin = AppConfig.getCryptoCurrencyForTicker(ticker); + if (coin != null) { + return ref.read(pWallets).wallets.any((e) => e.info.coin == coin); + } + } + return false; + } + + String? _parseBip21Amount(String bip21Uri) { + final parsed = AddressUtils.parsePaymentUri(bip21Uri); + String? amountStr = parsed?.amount; + if (amountStr == null || amountStr.isEmpty) { + final uri = Uri.tryParse(bip21Uri); + if (uri != null) { + amountStr = uri.queryParameters['amount']; + } + } + return (amountStr != null && amountStr.isNotEmpty) ? amountStr : null; + } + + void _onOwnedCoinTap(int methodIndex) { + if (!_payNowEnabled) return; + _selectedMethod = methodIndex; + _confirmPayment(); + } + + void _onUnownedCoinTap(int methodIndex) { + if (_isExpiredOrInvalid || _isTerminal) return; + final ticker = _methods[methodIndex].toUpperCase(); + final address = _addresses[methodIndex]; + + showModalBottomSheet( + context: context, + builder: (ctx) => Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text("$ticker Payment", style: STextStyles.pageTitleH2(context)), + const SizedBox(height: 16), + GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: address)); + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ); + }, + child: RoundedWhiteContainer( + child: Row( + children: [ + Expanded( + child: Text( + address, + style: STextStyles.itemSubtitle12(context), + ), + ), + const SizedBox(width: 8), + Icon( + Icons.copy, + size: 14, + color: Theme.of( + context, + ).extension()!.accentColorBlue, + ), + ], + ), + ), + ), + const SizedBox(height: 16), + PrimaryButton( + label: "CHECK FOR PAYMENT", + onPressed: () { + Navigator.of(ctx).pop(); + _checkForPayment(); + }, + ), + ], + ), + ), + ); + } + + void _copyAddress(BuildContext context) { + Clipboard.setData(ClipboardData(text: _currentAddress)); + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ); + } + + @override + Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + + const loadingOverlay = Center( + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ); + + // Build coin rows from _methods/_addresses + final coinRows = []; + for (int i = 0; i < _methods.length; i++) { + final ticker = _methods[i].toUpperCase(); + final coin = AppConfig.getCryptoCurrencyForTicker(ticker); + final hasWallet = _hasWalletForTicker(ticker); + final amountStr = _addresses[i].isNotEmpty + ? _parseBip21Amount(_addresses[i]) + : null; + + if (i > 0) { + coinRows.add(const SizedBox(height: 8)); + } + + coinRows.add( + RoundedWhiteContainer( + child: Opacity( + opacity: hasWallet ? 1.0 : 0.5, + child: InkWell( + onTap: hasWallet + ? () => _onOwnedCoinTap(i) + : () => _onUnownedCoinTap(i), + child: Row( + children: [ + if (coin != null) + SvgPicture.file( + File(ref.watch(coinIconProvider(coin))), + width: 24, + height: 24, + ) + else + SizedBox( + width: 24, + height: 24, + child: Center( + child: Text( + ticker.substring( + 0, + ticker.length > 2 ? 2 : ticker.length, + ), + style: STextStyles.itemSubtitle12(context), + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(ticker, style: STextStyles.titleBold12(context)), + if (amountStr != null) + Text( + "$amountStr $ticker", + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + if (hasWallet) + Text("PAY NOW", style: STextStyles.link2(context)) + else + Icon( + Icons.info_outline, + size: 18, + color: Theme.of( + context, + ).extension()!.textSubtitle2, + ), + ], + ), + ), + ), + ), + ); + } + + final content = Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Payment", + style: isDesktop + ? STextStyles.desktopH2(context) + : STextStyles.pageTitleH1(context), + ), + SizedBox(height: isDesktop ? 16 : 8), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Total", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + Text( + "$_totalPrice EUR", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + ], + ), + ), + // Status banner + if (_status == 'underpaid') ...[ + SizedBox(height: isDesktop ? 16 : 8), + RoundedWhiteContainer( + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.alertCircle, + width: 20, + height: 20, + color: Theme.of( + context, + ).extension()!.accentColorOrange, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + "Payment underpaid. Remaining: " + "${_paymentInfo?.due ?? '?'} EUR. " + "Please send the remaining amount.", + style: + (isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle12(context)) + .copyWith( + color: Theme.of( + context, + ).extension()!.accentColorOrange, + ), + ), + ), + ], + ), + ), + ], + if (_isExpiredOrInvalid) ...[ + SizedBox(height: isDesktop ? 16 : 8), + RoundedWhiteContainer( + child: Column( + children: [ + Row( + children: [ + SvgPicture.asset( + Assets.svg.alertCircle, + width: 20, + height: 20, + color: Theme.of( + context, + ).extension()!.accentColorRed, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + "Invoice expired.", + style: + (isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle12(context)) + .copyWith( + color: Theme.of( + context, + ).extension()!.accentColorRed, + ), + ), + ), + ], + ), + const SizedBox(height: 8), + SecondaryButton( + label: "Refresh Invoice", + onPressed: _refreshInvoice, + ), + ], + ), + ), + ], + if (_isTerminal) ...[ + SizedBox(height: isDesktop ? 16 : 8), + RoundedWhiteContainer( + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.checkCircle, + width: 20, + height: 20, + color: Theme.of( + context, + ).extension()!.accentColorGreen, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + "Payment received.", + style: + (isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle12(context)) + .copyWith( + color: Theme.of( + context, + ).extension()!.accentColorGreen, + ), + ), + ), + ], + ), + ), + ], + SizedBox(height: isDesktop ? 24 : 16), + // Coin list (replaces tab selector + QR + address + global button) + if (!_isExpiredOrInvalid) ...coinRows, + SizedBox(height: isDesktop ? 16 : 12), + GestureDetector( + onTap: () { + setState(() { + _termsAccepted = !_termsAccepted; + }); + }, + child: Container( + color: Colors.transparent, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20, + height: 20, + child: IgnorePointer( + child: Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: _termsAccepted, + onChanged: (_) {}, + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: RichText( + text: TextSpan( + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.w500_14(context), + children: [ + const TextSpan(text: "I accept the "), + TextSpan( + text: "Terms & Conditions", + style: STextStyles.richLink( + context, + ).copyWith(fontSize: isDesktop ? null : 14), + recognizer: TapGestureRecognizer() + ..onTap = _openTerms, + ), + const TextSpan(text: "."), + ], + ), + ), + ), + ], + ), + ), + ), + ], + ); + + if (isDesktop) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 750, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "ShopinBit", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 8, + ), + child: Stack( + children: [ + SingleChildScrollView(child: content), + if (_loading) loadingOverlay, + ], + ), + ), + ), + ], + ), + ); + } + + return Background( + child: PopScope( + canPop: false, + onPopInvokedWithResult: (bool didPop, dynamic result) { + if (!didPop) { + _popToTickets(); + } + }, + child: Scaffold( + backgroundColor: Theme.of( + context, + ).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton(onPressed: _popToTickets), + title: Text("ShopinBit", style: STextStyles.navBarTitle(context)), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return Stack( + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 32, + ), + child: IntrinsicHeight(child: content), + ), + ), + ), + if (_loading) loadingOverlay, + ], + ); + }, + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/shopinbit/shopinbit_send_from_view.dart b/lib/pages/shopinbit/shopinbit_send_from_view.dart new file mode 100644 index 0000000000..0060cf596f --- /dev/null +++ b/lib/pages/shopinbit/shopinbit_send_from_view.dart @@ -0,0 +1,523 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../app_config.dart'; +import '../../models/isar/models/blockchain_data/address.dart'; +import '../../models/isar/models/ethereum/eth_contract.dart'; +import '../../models/shopinbit/shopinbit_order_model.dart'; +import '../../providers/providers.dart'; +import '../../route_generator.dart'; +import '../../themes/coin_icon_provider.dart'; +import '../../themes/stack_colors.dart'; +import '../../themes/theme_providers.dart'; +import '../../utilities/amount/amount.dart'; +import '../../utilities/amount/amount_formatter.dart'; +import '../../utilities/constants.dart'; +import '../../utilities/enums/fee_rate_type_enum.dart'; +import '../../utilities/logger.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../wallets/crypto_currency/crypto_currency.dart'; +import '../../wallets/isar/providers/eth/token_balance_provider.dart'; +import '../../wallets/isar/providers/wallet_info_provider.dart'; +import '../../wallets/models/tx_data.dart'; +import '../../services/shopinbit/shopinbit_service.dart'; +import '../../wallets/wallet/impl/ethereum_wallet.dart'; +import '../../wallets/wallet/intermediate/external_wallet.dart'; +import '../../wallets/wallet/wallet.dart'; +import '../../widgets/background.dart'; +import '../../widgets/conditional_parent.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/rounded_white_container.dart'; +import '../../widgets/stack_dialog.dart'; +import '../../pages_desktop_specific/desktop_home_view.dart'; +import '../home_view/home_view.dart'; +import '../send_view/sub_widgets/building_transaction_dialog.dart'; +import 'shopinbit_confirm_send_view.dart'; + +class ShopInBitSendFromView extends ConsumerStatefulWidget { + const ShopInBitSendFromView({ + super.key, + required this.coin, + required this.model, + this.amount, + required this.address, + this.shouldPopRoot = false, + this.tokenContract, + this.routeOnSuccessName, + }); + + static const String routeName = "/shopInBitSendFrom"; + + final CryptoCurrency coin; + final Amount? amount; + final String address; + final ShopInBitOrderModel model; + final bool shouldPopRoot; + final EthContract? tokenContract; + // If set, overrides the default success route (HomeView/DesktopHomeView). + final String? routeOnSuccessName; + + @override + ConsumerState createState() => + _ShopInBitSendFromViewState(); +} + +class _ShopInBitSendFromViewState extends ConsumerState { + late final CryptoCurrency coin; + late final Amount? amount; + late final String address; + late final ShopInBitOrderModel model; + late final EthContract? tokenContract; + + @override + void initState() { + coin = widget.coin; + address = widget.address; + amount = widget.amount; + model = widget.model; + tokenContract = widget.tokenContract; + super.initState(); + } + + @override + Widget build(BuildContext context) { + final List walletIds; + if (tokenContract != null) { + walletIds = ref + .watch(pWallets) + .wallets + .where( + (w) => + w.info.coin == coin && + w.info.tokenContractAddresses.contains(tokenContract!.address), + ) + .map((e) => e.walletId) + .toList(); + } else { + walletIds = ref + .watch(pWallets) + .wallets + .where((e) => e.info.coin == coin) + .map((e) => e.walletId) + .toList(); + } + + final isDesktop = Util.isDesktop; + + return ConditionalParent( + condition: !isDesktop, + builder: (child) { + return Background( + child: Scaffold( + backgroundColor: Theme.of( + context, + ).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text("Send from", style: STextStyles.navBarTitle(context)), + ), + body: SafeArea( + child: Padding(padding: const EdgeInsets.all(16), child: child), + ), + ), + ); + }, + child: ConditionalParent( + condition: isDesktop, + builder: (child) => DesktopDialog( + maxHeight: double.infinity, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Send from ${AppConfig.prefix}", + style: STextStyles.desktopH3(context), + ), + ), + DesktopDialogCloseButton( + onPressedOverride: Navigator.of( + context, + rootNavigator: widget.shouldPopRoot, + ).pop, + ), + ], + ), + Padding( + padding: const EdgeInsets.only(left: 32, right: 32, bottom: 32), + child: child, + ), + ], + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + children: [ + Text( + amount != null + ? tokenContract != null + ? "You need to send ${amount!.decimal.toStringAsFixed(tokenContract!.decimals)} ${tokenContract!.symbol}" + : "You need to send ${ref.watch(pAmountFormatter(coin)).format(amount!)}" + : "Select a wallet to pay", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle(context), + ), + ], + ), + const SizedBox(height: 16), + ConditionalParent( + condition: !isDesktop, + builder: (child) => Expanded(child: child), + child: ListView.builder( + primary: isDesktop ? false : null, + shrinkWrap: isDesktop, + itemCount: walletIds.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: ShopInBitSendFromCard( + walletId: walletIds[index], + amount: amount, + address: address, + model: model, + tokenContract: tokenContract, + routeOnSuccessName: widget.routeOnSuccessName, + ), + ); + }, + ), + ), + ], + ), + ), + ); + } +} + +class ShopInBitSendFromCard extends ConsumerStatefulWidget { + const ShopInBitSendFromCard({ + super.key, + required this.walletId, + this.amount, + required this.address, + required this.model, + this.tokenContract, + this.routeOnSuccessName, + }); + + final String walletId; + final Amount? amount; + final String address; + final ShopInBitOrderModel model; + final EthContract? tokenContract; + final String? routeOnSuccessName; + + @override + ConsumerState createState() => + _ShopInBitSendFromCardState(); +} + +class _ShopInBitSendFromCardState extends ConsumerState { + late final String walletId; + late final Amount? amount; + late final String address; + late final ShopInBitOrderModel model; + late final EthContract? tokenContract; + + Future _send() async { + final coin = ref.read(pWalletCoin(walletId)); + + final int fractionDigits = tokenContract != null + ? tokenContract!.decimals + : coin.fractionDigits; + + Amount? sendAmount = amount; + if (sendAmount == null) { + if (ShopInBitService.instance.client.sandbox) { + sendAmount = Amount( + rawValue: BigInt.from(10000), + fractionDigits: fractionDigits, + ); + } else { + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return StackDialog( + title: "Transaction failed", + message: "Payment amount not available yet", + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: Theme.of( + context, + ).extension()!.buttonTextSecondary, + ), + ), + onPressed: () => Navigator.of(context).pop(), + ), + ); + }, + ); + return; + } + } + + bool wasCancelled = false; + + try { + final parentWallet = ref.read(pWallets).getWallet(walletId); + + unawaited( + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return ConditionalParent( + condition: Util.isDesktop, + builder: (child) => DesktopDialog( + maxWidth: 400, + maxHeight: double.infinity, + child: Padding(padding: const EdgeInsets.all(32), child: child), + ), + child: BuildingTransactionDialog( + coin: coin, + isSpark: false, + onCancel: () { + wasCancelled = true; + + Navigator.of(context).pop(); + }, + ), + ); + }, + ), + ); + + if (parentWallet is ExternalWallet) { + await parentWallet.init(); + await parentWallet.open(); + } + + final time = Future.delayed(const Duration(milliseconds: 2500)); + + TxData txData; + + // Use token wallet for ERC-20 tokens, parent wallet otherwise + final wallet = tokenContract != null + ? Wallet.loadTokenWallet( + ethWallet: parentWallet as EthereumWallet, + contract: tokenContract!, + ) + : parentWallet; + + if (tokenContract != null) { + await wallet.init(); + } + + final addressType = + wallet.cryptoCurrency.getAddressType(address) ?? + parentWallet.cryptoCurrency.getAddressType(address) ?? + AddressType.ethereum; + + final recipient = TxRecipient( + address: address, + amount: sendAmount, + isChange: false, + addressType: addressType, + ); + + final txDataFuture = wallet.prepareSend( + txData: TxData( + recipients: [recipient], + feeRateType: FeeRateType.average, + ), + ); + + final results = await Future.wait([txDataFuture, time]); + + txData = results.first as TxData; + + if (!wasCancelled) { + if (mounted) { + Navigator.of(context, rootNavigator: true).pop(); + } + + txData = txData.copyWith(note: "ShopinBit payment"); + + if (mounted) { + await Navigator.of(context).push( + RouteGenerator.getRoute( + shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, + builder: (_) => ShopInBitConfirmSendView( + txData: txData, + walletId: walletId, + routeOnSuccessName: + widget.routeOnSuccessName ?? + (Util.isDesktop + ? DesktopHomeView.routeName + : HomeView.routeName), + model: model, + tokenContract: tokenContract, + ), + settings: const RouteSettings( + name: ShopInBitConfirmSendView.routeName, + ), + ), + ); + } + } + } catch (e, s) { + Logging.instance.e("$e\n$s", error: e, stackTrace: s); + if (mounted && !wasCancelled) { + Navigator.of(context, rootNavigator: true).pop(); + + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return StackDialog( + title: "Transaction failed", + message: e.toString(), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: Theme.of( + context, + ).extension()!.buttonTextSecondary, + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ); + }, + ); + } + } + } + + @override + void initState() { + walletId = widget.walletId; + amount = widget.amount; + address = widget.address; + model = widget.model; + tokenContract = widget.tokenContract; + super.initState(); + } + + @override + Widget build(BuildContext context) { + final coin = ref.watch(pWalletCoin(walletId)); + + return RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: MaterialButton( + splashColor: Theme.of(context).extension()!.highlight, + key: Key("walletsSheetItemButtonKey_$walletId"), + padding: const EdgeInsets.all(8), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () async { + if (mounted) { + unawaited(_send()); + } + }, + child: Row( + children: [ + Container( + decoration: BoxDecoration( + color: ref.watch(pCoinColor(coin)).withOpacity(0.5), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: Padding( + padding: const EdgeInsets.all(6), + child: SvgPicture.file( + File(ref.watch(coinIconProvider(coin))), + width: 24, + height: 24, + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + tokenContract != null + ? "${ref.watch(pWalletName(walletId))} (${tokenContract!.symbol})" + : ref.watch(pWalletName(walletId)), + style: STextStyles.titleBold12(context), + ), + const SizedBox(height: 2), + if (tokenContract != null) + Builder( + builder: (context) { + final balance = ref.watch( + pTokenBalance(( + walletId: walletId, + contractAddress: tokenContract!.address, + )), + ); + return Text( + "${balance.spendable.decimal.toStringAsFixed(tokenContract!.decimals)} ${tokenContract!.symbol}", + style: STextStyles.itemSubtitle(context), + ); + }, + ) + else + Text( + ref + .watch(pAmountFormatter(coin)) + .format( + ref.watch(pWalletBalance(walletId)).spendable, + ), + style: STextStyles.itemSubtitle(context), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/shopinbit/shopinbit_settings_view.dart b/lib/pages/shopinbit/shopinbit_settings_view.dart new file mode 100644 index 0000000000..0682b74e6e --- /dev/null +++ b/lib/pages/shopinbit/shopinbit_settings_view.dart @@ -0,0 +1,545 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +import '../../notifications/show_flush_bar.dart'; +import '../../services/shopinbit/shopinbit_service.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/assets.dart'; +import '../../utilities/constants.dart'; +import '../../utilities/text_styles.dart'; +import '../../widgets/background.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../../widgets/rounded_container.dart'; +import '../../widgets/rounded_white_container.dart'; +import '../../widgets/stack_dialog.dart'; +import '../../widgets/stack_text_field.dart'; + +class ShopInBitSettingsView extends ConsumerStatefulWidget { + const ShopInBitSettingsView({super.key}); + + static const String routeName = "/shopInBitSettings"; + + @override + ConsumerState createState() => + _ShopInBitSettingsViewState(); +} + +class _ShopInBitSettingsViewState extends ConsumerState { + final _manualKeyController = TextEditingController(); + final _manualKeyFocusNode = FocusNode(); + final _verifyKeyController = TextEditingController(); + final _verifyKeyFocusNode = FocusNode(); + late final TextEditingController _displayNameController; + late final FocusNode _displayNameFocusNode; + + String? _currentKey; + bool _loading = false; + bool _savingName = false; + + @override + void initState() { + super.initState(); + _currentKey = ShopInBitService.instance.loadCustomerKey(); + final savedName = ShopInBitService.instance.loadDisplayName(); + _displayNameController = TextEditingController(text: savedName ?? ''); + _displayNameFocusNode = FocusNode(); + } + + @override + void dispose() { + _manualKeyController.dispose(); + _manualKeyFocusNode.dispose(); + _verifyKeyController.dispose(); + _verifyKeyFocusNode.dispose(); + _displayNameController.dispose(); + _displayNameFocusNode.dispose(); + super.dispose(); + } + + Future _saveDisplayName() async { + final name = _displayNameController.text.trim(); + if (name.isEmpty) return; + setState(() => _savingName = true); + try { + await ShopInBitService.instance.setDisplayName(name); + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "Display name updated", + context: context, + ), + ); + } + } finally { + if (mounted) setState(() => _savingName = false); + } + } + + Future _generate() async { + if (_currentKey != null) { + final proceed = await _showChangeWarning(); + if (proceed != true) return; + } + + setState(() => _loading = true); + try { + final String key; + if (_currentKey != null) { + final resp = await ShopInBitService.instance.client.generateKey(); + key = resp.valueOrThrow; + await ShopInBitService.instance.setCustomerKey(key); + } else { + key = await ShopInBitService.instance.ensureCustomerKey(); + } + setState(() => _currentKey = key); + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "Customer key generated", + context: context, + ), + ); + } + } catch (e) { + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Failed to generate key: $e", + context: context, + ), + ); + } + } finally { + setState(() => _loading = false); + } + } + + Future _setManualKey() async { + final newKey = _manualKeyController.text.trim(); + if (newKey.isEmpty) return; + + if (_currentKey != null) { + final proceed = await _showChangeWarning(); + if (proceed != true) return; + } + + setState(() => _loading = true); + try { + await ShopInBitService.instance.setCustomerKey(newKey); + setState(() { + _currentKey = newKey; + _manualKeyController.clear(); + }); + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "Customer key set", + context: context, + ), + ); + } + } catch (e) { + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Failed to set key: $e", + context: context, + ), + ); + } + } finally { + setState(() => _loading = false); + } + } + + Future _showChangeWarning() async { + final result = await showDialog( + context: context, + barrierDismissible: true, + builder: (context) => StackDialogBase( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Save your current key", + style: STextStyles.pageTitleH2(context), + ), + const SizedBox(height: 8), + SelectableText( + "Your current customer key is:", + style: STextStyles.smallMed14(context), + ), + const SizedBox(height: 8), + RoundedContainer( + color: Theme.of( + context, + ).extension()!.warningBackground, + child: SelectableText( + _currentKey!, + style: STextStyles.smallMed14(context).copyWith( + color: Theme.of( + context, + ).extension()!.warningForeground, + ), + ), + ), + const SizedBox(height: 8), + SelectableText( + "Changing your key will disconnect you from " + "existing ShopinBit conversations. Make sure " + "you have saved your current key before " + "proceeding.", + style: STextStyles.smallMed14(context), + ), + const SizedBox(height: 20), + Row( + children: [ + Expanded( + child: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + onPressed: () => Navigator.of(context).pop(false), + child: Text( + "Cancel", + style: STextStyles.button(context).copyWith( + color: Theme.of( + context, + ).extension()!.accentColorDark, + ), + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonStyle(context), + onPressed: () => Navigator.of(context).pop(null), + child: Text( + "I saved my key", + style: STextStyles.button(context), + ), + ), + ), + ], + ), + ], + ), + ), + ); + + if (result == false || !mounted) return false; + + return _showVerifyDialog(); + } + + Future _showVerifyDialog() async { + _verifyKeyController.clear(); + return showDialog( + context: context, + barrierDismissible: true, + builder: (ctx) { + return StatefulBuilder( + builder: (ctx, setDialogState) { + final matches = _verifyKeyController.text.trim() == _currentKey; + return StackDialogBase( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Verify your key", style: STextStyles.pageTitleH2(ctx)), + const SizedBox(height: 8), + Text( + "Enter your current customer key to " + "confirm you have saved it.", + style: STextStyles.smallMed14(ctx), + ), + const SizedBox(height: 16), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + controller: _verifyKeyController, + focusNode: _verifyKeyFocusNode, + style: STextStyles.field(ctx), + decoration: standardInputDecoration( + "Enter current key", + _verifyKeyFocusNode, + ctx, + ), + onChanged: (_) => setDialogState(() {}), + ), + ), + const SizedBox(height: 20), + Row( + children: [ + Expanded( + child: TextButton( + style: Theme.of(ctx) + .extension()! + .getSecondaryEnabledButtonStyle(ctx), + onPressed: () => Navigator.of(ctx).pop(false), + child: Text( + "Cancel", + style: STextStyles.button(ctx).copyWith( + color: Theme.of( + ctx, + ).extension()!.accentColorDark, + ), + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: TextButton( + style: matches + ? Theme.of(ctx) + .extension()! + .getPrimaryEnabledButtonStyle(ctx) + : Theme.of(ctx) + .extension()! + .getPrimaryDisabledButtonStyle(ctx), + onPressed: matches + ? () => Navigator.of(ctx).pop(true) + : null, + child: Text( + "Confirm", + style: STextStyles.button(ctx), + ), + ), + ), + ], + ), + ], + ), + ); + }, + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () => Navigator.of(context).pop(), + ), + title: Text("ShopinBit", style: STextStyles.navBarTitle(context)), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.only(left: 12, top: 12, right: 12), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Customer Key", + style: STextStyles.titleBold12(context), + ), + const SizedBox(height: 8), + Text( + "Your customer key identifies you " + "to ShopinBit. Save it to restore " + "access to your conversations on " + "another device. If you change it, " + "you will lose access to existing " + "conversations.", + style: STextStyles.itemSubtitle12(context), + ), + const SizedBox(height: 16), + if (_currentKey != null) ...[ + RoundedContainer( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + child: Row( + children: [ + Expanded( + child: SelectableText( + _currentKey!, + style: STextStyles.field(context), + ), + ), + const SizedBox(width: 8), + GestureDetector( + onTap: () async { + await Clipboard.setData( + ClipboardData( + text: _currentKey!, + ), + ); + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: + "Key copied to clipboard", + context: context, + ), + ); + } + }, + child: SvgPicture.asset( + Assets.svg.copy, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .textDark3, + ), + ), + ], + ), + ), + ] else + Text( + "No key set", + style: STextStyles.itemSubtitle(context), + ), + const SizedBox(height: 16), + PrimaryButton( + label: _currentKey == null + ? "Generate key" + : "Generate new key", + enabled: !_loading, + onPressed: _generate, + ), + ], + ), + ), + const SizedBox(height: 12), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Restore key", + style: STextStyles.titleBold12(context), + ), + const SizedBox(height: 8), + Text( + "Enter a previously saved customer " + "key to restore access to your " + "ShopinBit conversations.", + style: STextStyles.itemSubtitle12(context), + ), + const SizedBox(height: 12), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + controller: _manualKeyController, + focusNode: _manualKeyFocusNode, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Enter customer key", + _manualKeyFocusNode, + context, + ), + onChanged: (_) => setState(() {}), + ), + ), + const SizedBox(height: 12), + PrimaryButton( + label: "Set key", + enabled: + !_loading && + _manualKeyController.text + .trim() + .isNotEmpty, + onPressed: _setManualKey, + ), + ], + ), + ), + const SizedBox(height: 12), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Display Name", + style: STextStyles.titleBold12(context), + ), + const SizedBox(height: 8), + Text( + "The name ShopinBit staff will see " + "when communicating with you.", + style: STextStyles.itemSubtitle12(context), + ), + const SizedBox(height: 12), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + controller: _displayNameController, + focusNode: _displayNameFocusNode, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Display name", + _displayNameFocusNode, + context, + ), + onChanged: (_) => setState(() {}), + ), + ), + const SizedBox(height: 12), + PrimaryButton( + label: "Save", + enabled: + !_savingName && + _displayNameController.text + .trim() + .isNotEmpty, + onPressed: _saveDisplayName, + ), + ], + ), + ), + const SizedBox(height: 12), + ], + ), + ), + ), + ), + ), + ); + }, + ), + ), + ), + ); + } +} diff --git a/lib/pages/shopinbit/shopinbit_setup_view.dart b/lib/pages/shopinbit/shopinbit_setup_view.dart new file mode 100644 index 0000000000..5566d5320c --- /dev/null +++ b/lib/pages/shopinbit/shopinbit_setup_view.dart @@ -0,0 +1,204 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import '../../models/shopinbit/shopinbit_order_model.dart'; +import '../../notifications/show_flush_bar.dart'; +import '../../services/shopinbit/shopinbit_service.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/constants.dart'; +import '../../utilities/text_styles.dart'; +import '../../widgets/background.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../../widgets/rounded_white_container.dart'; +import '../../widgets/stack_text_field.dart'; +import 'shopinbit_step_2.dart'; + +class ShopInBitSetupView extends StatefulWidget { + const ShopInBitSetupView({super.key, required this.model}); + + static const String routeName = "/shopInBitSetup"; + + final ShopInBitOrderModel model; + + @override + State createState() => _ShopInBitSetupViewState(); +} + +class _ShopInBitSetupViewState extends State { + late final Future _keyFuture; + late final TextEditingController _nameController; + late final FocusNode _nameFocusNode; + + bool get _canContinue => _nameController.text.trim().isNotEmpty; + + @override + void initState() { + super.initState(); + _keyFuture = ShopInBitService.instance.ensureCustomerKey(); + final existingName = ShopInBitService.instance.loadDisplayName(); + _nameController = TextEditingController(text: existingName ?? ''); + _nameFocusNode = FocusNode(); + + _nameFocusNode.addListener(() { + setState(() {}); + }); + } + + @override + void dispose() { + _nameController.dispose(); + _nameFocusNode.dispose(); + super.dispose(); + } + + Future _completeSetup() async { + final name = _nameController.text.trim(); + widget.model.displayName = name; + await ShopInBitService.instance.setDisplayName(name); + await ShopInBitService.instance.setSetupComplete(true); + + if (mounted) { + Navigator.of( + context, + ).pushReplacementNamed(ShopInBitStep2.routeName, arguments: widget.model); + } + } + + @override + Widget build(BuildContext context) { + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () => Navigator.of(context).pop(), + ), + title: Text("ShopinBit", style: STextStyles.navBarTitle(context)), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.all(16), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 32, + ), + child: IntrinsicHeight( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Your ShopinBit Customer Key", + style: STextStyles.pageTitleH1(context), + ), + const SizedBox(height: 8), + Text( + "This is your ShopinBit customer key. Save it " + "somewhere safe: you'll need it to recover " + "your ShopinBit account on a new device.", + style: STextStyles.itemSubtitle(context), + ), + const SizedBox(height: 16), + FutureBuilder( + future: _keyFuture, + builder: (context, snapshot) { + if (snapshot.connectionState != + ConnectionState.done) { + return const Center( + child: CircularProgressIndicator(), + ); + } + if (snapshot.hasError) { + return Text( + "Failed to generate key. Please try again.", + style: STextStyles.itemSubtitle(context) + .copyWith( + color: Theme.of( + context, + ).extension()!.textError, + ), + ); + } + final key = snapshot.data!; + return RoundedWhiteContainer( + child: Row( + children: [ + Expanded( + child: SelectableText( + key, + style: STextStyles.itemSubtitle12( + context, + ), + ), + ), + IconButton( + icon: const Icon(Icons.copy, size: 20), + onPressed: () { + Clipboard.setData( + ClipboardData(text: key), + ); + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard!", + context: context, + ); + }, + ), + ], + ), + ); + }, + ), + const SizedBox(height: 32), + Text( + "Set a Display Name to use with ShopinBit staff", + style: STextStyles.smallMed12(context), + ), + const SizedBox(height: 8), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + controller: _nameController, + focusNode: _nameFocusNode, + autocorrect: false, + enableSuggestions: false, + onChanged: (_) => setState(() {}), + style: STextStyles.field(context), + decoration: + standardInputDecoration( + "Display name", + _nameFocusNode, + context, + ).copyWith( + filled: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + ), + ), + const Spacer(), + PrimaryButton( + label: "Complete Setup", + enabled: _canContinue, + onPressed: _canContinue ? _completeSetup : null, + ), + ], + ), + ), + ), + ), + ); + }, + ), + ), + ), + ); + } +} diff --git a/lib/pages/shopinbit/shopinbit_shipping_view.dart b/lib/pages/shopinbit/shopinbit_shipping_view.dart new file mode 100644 index 0000000000..4ad2fe4ec4 --- /dev/null +++ b/lib/pages/shopinbit/shopinbit_shipping_view.dart @@ -0,0 +1,751 @@ +import 'dart:async'; + +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../models/shopinbit/shopinbit_order_model.dart'; +import '../../notifications/show_flush_bar.dart'; +import '../../services/shopinbit/shopinbit_service.dart'; +import '../../services/shopinbit/src/models/address.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/assets.dart'; +import '../../utilities/constants.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../widgets/background.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../../widgets/stack_text_field.dart'; +import 'shopinbit_payment_view.dart'; + +class ShopInBitShippingView extends StatefulWidget { + const ShopInBitShippingView({super.key, required this.model}); + + static const String routeName = "/shopInBitShipping"; + + final ShopInBitOrderModel model; + + @override + State createState() => _ShopInBitShippingViewState(); +} + +class _ShopInBitShippingViewState extends State { + late final TextEditingController _nameController; + late final TextEditingController _streetController; + late final TextEditingController _cityController; + late final TextEditingController _postalCodeController; + final TextEditingController _countrySearchController = + TextEditingController(); + late final FocusNode _nameFocusNode; + late final FocusNode _streetFocusNode; + late final FocusNode _cityFocusNode; + late final FocusNode _postalCodeFocusNode; + + // Billing address controllers + late final TextEditingController _billingNameController; + late final TextEditingController _billingStreetController; + late final TextEditingController _billingCityController; + late final TextEditingController _billingPostalCodeController; + final TextEditingController _billingCountrySearchController = + TextEditingController(); + late final FocusNode _billingNameFocusNode; + late final FocusNode _billingStreetFocusNode; + late final FocusNode _billingCityFocusNode; + late final FocusNode _billingPostalCodeFocusNode; + + String? _billingSelectedCountryIso; + bool _differentBilling = false; + + List> _countries = []; + String? _selectedCountryIso; + bool _loadingCountries = false; + + bool _submitting = false; + + bool get _canContinue { + if (_submitting) return false; + final shippingValid = + _nameController.text.trim().isNotEmpty && + _streetController.text.trim().isNotEmpty && + _cityController.text.trim().isNotEmpty && + _postalCodeController.text.trim().isNotEmpty && + _selectedCountryIso != null; + if (!shippingValid) return false; + if (_differentBilling) { + return _billingNameController.text.trim().isNotEmpty && + _billingStreetController.text.trim().isNotEmpty && + _billingCityController.text.trim().isNotEmpty && + _billingPostalCodeController.text.trim().isNotEmpty && + _billingSelectedCountryIso != null; + } + return true; + } + + @override + void initState() { + super.initState(); + _nameController = TextEditingController(); + _streetController = TextEditingController(); + _cityController = TextEditingController(); + _postalCodeController = TextEditingController(); + _nameFocusNode = FocusNode(); + _streetFocusNode = FocusNode(); + _cityFocusNode = FocusNode(); + _postalCodeFocusNode = FocusNode(); + + _billingNameController = TextEditingController(); + _billingStreetController = TextEditingController(); + _billingCityController = TextEditingController(); + _billingPostalCodeController = TextEditingController(); + _billingNameFocusNode = FocusNode(); + _billingStreetFocusNode = FocusNode(); + _billingCityFocusNode = FocusNode(); + _billingPostalCodeFocusNode = FocusNode(); + + for (final node in [ + _nameFocusNode, + _streetFocusNode, + _cityFocusNode, + _postalCodeFocusNode, + _billingNameFocusNode, + _billingStreetFocusNode, + _billingCityFocusNode, + _billingPostalCodeFocusNode, + ]) { + node.addListener(() => setState(() {})); + } + + _fetchCountries(); + } + + @override + void dispose() { + _nameController.dispose(); + _streetController.dispose(); + _cityController.dispose(); + _postalCodeController.dispose(); + _countrySearchController.dispose(); + _nameFocusNode.dispose(); + _streetFocusNode.dispose(); + _cityFocusNode.dispose(); + _postalCodeFocusNode.dispose(); + _billingNameController.dispose(); + _billingStreetController.dispose(); + _billingCityController.dispose(); + _billingPostalCodeController.dispose(); + _billingCountrySearchController.dispose(); + _billingNameFocusNode.dispose(); + _billingStreetFocusNode.dispose(); + _billingCityFocusNode.dispose(); + _billingPostalCodeFocusNode.dispose(); + super.dispose(); + } + + Future _fetchCountries() async { + setState(() => _loadingCountries = true); + try { + final resp = await ShopInBitService.instance.client.getCountries(); + if (resp.hasError || resp.value == null) return; + _countries = resp.value!; + if (_selectedCountryIso != null && + !_countries.any((c) => c['iso'] == _selectedCountryIso)) { + _selectedCountryIso = null; + } + } catch (_) { + // leave list empty; user will see no items + } finally { + if (mounted) setState(() => _loadingCountries = false); + } + } + + Future _continue() async { + final name = _nameController.text.trim(); + final street = _streetController.text.trim(); + final city = _cityController.text.trim(); + final postalCode = _postalCodeController.text.trim(); + final country = _selectedCountryIso!; + + widget.model.setShippingAddress( + name: name, + street: street, + city: city, + postalCode: postalCode, + country: country, + ); + + if (widget.model.apiTicketId != 0) { + setState(() => _submitting = true); + try { + // Split name into first/last + final parts = name.split(' '); + final firstName = parts.first; + final lastName = parts.length > 1 ? parts.sublist(1).join(' ') : ''; + + Address? billingAddress; + if (_differentBilling) { + final billingName = _billingNameController.text.trim(); + final billingParts = billingName.split(' '); + final billingFirst = billingParts.first; + final billingLast = billingParts.length > 1 + ? billingParts.sublist(1).join(' ') + : ''; + billingAddress = Address( + firstName: billingFirst, + lastName: billingLast, + street: _billingStreetController.text.trim(), + zip: _billingPostalCodeController.text.trim(), + city: _billingCityController.text.trim(), + country: _billingSelectedCountryIso!, + ); + } + + final resp = await ShopInBitService.instance.client.submitAddress( + widget.model.apiTicketId, + shipping: Address( + firstName: firstName, + lastName: lastName, + street: street, + zip: postalCode, + city: city, + country: country, + ), + billing: billingAddress, + ); + + if (resp.hasError) { + // Sandbox may fail here; continue anyway. + debugPrint("submitAddress failed: ${resp.exception?.message}"); + } + } catch (e) { + debugPrint("submitAddress threw: $e"); + } finally { + if (mounted) setState(() => _submitting = false); + } + } + + if (!mounted) return; + if (Util.isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + unawaited( + showDialog( + context: context, + builder: (_) => ShopInBitPaymentView(model: widget.model), + ), + ); + } else { + unawaited( + Navigator.of( + context, + ).pushNamed(ShopInBitPaymentView.routeName, arguments: widget.model), + ); + } + } + + Widget _buildField({ + required TextEditingController controller, + required FocusNode focusNode, + required String label, + required bool isDesktop, + }) { + return ClipRRect( + borderRadius: BorderRadius.circular(Constants.size.circularBorderRadius), + child: TextField( + controller: controller, + focusNode: focusNode, + autocorrect: false, + enableSuggestions: false, + onChanged: (_) => setState(() {}), + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: + standardInputDecoration( + label, + focusNode, + context, + desktopMed: isDesktop, + ).copyWith( + filled: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + final spacing = SizedBox(height: isDesktop ? 16 : 12); + + final content = Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Shipping address", + style: isDesktop + ? STextStyles.desktopH2(context) + : STextStyles.pageTitleH1(context), + ), + SizedBox(height: isDesktop ? 16 : 8), + Text( + "Where should we deliver your order?", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.itemSubtitle(context), + ), + SizedBox(height: isDesktop ? 32 : 24), + _buildField( + controller: _nameController, + focusNode: _nameFocusNode, + label: "Full name", + isDesktop: isDesktop, + ), + spacing, + _buildField( + controller: _streetController, + focusNode: _streetFocusNode, + label: "Street address", + isDesktop: isDesktop, + ), + spacing, + Row( + children: [ + Expanded( + child: _buildField( + controller: _cityController, + focusNode: _cityFocusNode, + label: "City", + isDesktop: isDesktop, + ), + ), + SizedBox(width: isDesktop ? 16 : 12), + Expanded( + child: _buildField( + controller: _postalCodeController, + focusNode: _postalCodeFocusNode, + label: "Postal code", + isDesktop: isDesktop, + ), + ), + ], + ), + spacing, + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: DropdownButtonHideUnderline( + child: DropdownButton2( + value: _selectedCountryIso, + items: _countries + .map( + (c) => DropdownMenuItem( + value: c['iso'] as String, + child: Text( + c['label'] as String, + style: isDesktop + ? STextStyles.desktopTextExtraSmall( + context, + ).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + ) + : STextStyles.w500_14(context), + ), + ), + ) + .toList(), + onMenuStateChange: (isOpen) { + if (!isOpen) { + _countrySearchController.clear(); + } + }, + onChanged: _loadingCountries + ? null + : (value) { + setState(() { + _selectedCountryIso = value; + }); + }, + hint: Text( + _loadingCountries ? "Loading countries..." : "Country", + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldDefaultSearchIconLeft, + ) + : STextStyles.fieldLabel(context), + ), + isExpanded: true, + buttonStyleData: ButtonStyleData( + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + iconStyleData: IconStyleData( + icon: Padding( + padding: const EdgeInsets.only(right: 10), + child: SvgPicture.asset( + Assets.svg.chevronDown, + width: 12, + height: 6, + color: Theme.of( + context, + ).extension()!.textFieldActiveSearchIconRight, + ), + ), + ), + dropdownStyleData: DropdownStyleData( + offset: const Offset(0, 0), + elevation: 0, + maxHeight: 300, + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + dropdownSearchData: DropdownSearchData( + searchController: _countrySearchController, + searchInnerWidgetHeight: 48, + searchInnerWidget: TextFormField( + controller: _countrySearchController, + decoration: InputDecoration( + isDense: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 14, + ), + hintText: "Search...", + hintStyle: STextStyles.fieldLabel(context), + border: InputBorder.none, + ), + ), + searchMatchFn: (item, searchValue) { + final label = _countries + .where((c) => c['iso'] == item.value) + .map((c) => c['label'] as String) + .firstOrNull; + return label?.toLowerCase().contains( + searchValue.toLowerCase(), + ) ?? + false; + }, + ), + menuItemStyleData: const MenuItemStyleData( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + ), + ), + ), + ), + spacing, + // Billing address toggle. + GestureDetector( + onTap: () { + setState(() { + _differentBilling = !_differentBilling; + if (!_differentBilling) { + // Clear billing fields. + _billingNameController.clear(); + _billingStreetController.clear(); + _billingCityController.clear(); + _billingPostalCodeController.clear(); + _billingSelectedCountryIso = null; + } + }); + }, + child: Row( + children: [ + SizedBox( + width: 24, + height: 24, + child: Checkbox( + value: _differentBilling, + onChanged: (v) { + setState(() { + _differentBilling = v ?? false; + if (!_differentBilling) { + _billingNameController.clear(); + _billingStreetController.clear(); + _billingCityController.clear(); + _billingPostalCodeController.clear(); + _billingSelectedCountryIso = null; + } + }); + }, + activeColor: Theme.of( + context, + ).extension()!.accentColorBlue, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + "Different billing address?", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.itemSubtitle(context), + ), + ), + ], + ), + ), + // Billing fields (expanded). + if (_differentBilling) ...[ + SizedBox(height: isDesktop ? 24 : 16), + Text( + "Billing address", + style: isDesktop + ? STextStyles.desktopTextMedium(context) + : STextStyles.titleBold12(context), + ), + spacing, + _buildField( + controller: _billingNameController, + focusNode: _billingNameFocusNode, + label: "Full name", + isDesktop: isDesktop, + ), + spacing, + _buildField( + controller: _billingStreetController, + focusNode: _billingStreetFocusNode, + label: "Street address", + isDesktop: isDesktop, + ), + spacing, + Row( + children: [ + Expanded( + child: _buildField( + controller: _billingCityController, + focusNode: _billingCityFocusNode, + label: "City", + isDesktop: isDesktop, + ), + ), + SizedBox(width: isDesktop ? 16 : 12), + Expanded( + child: _buildField( + controller: _billingPostalCodeController, + focusNode: _billingPostalCodeFocusNode, + label: "Postal code", + isDesktop: isDesktop, + ), + ), + ], + ), + spacing, + // Billing country dropdown. + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: DropdownButtonHideUnderline( + child: DropdownButton2( + value: _billingSelectedCountryIso, + items: _countries + .map( + (c) => DropdownMenuItem( + value: c['iso'] as String, + child: Text( + c['label'] as String, + style: isDesktop + ? STextStyles.desktopTextExtraSmall( + context, + ).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + ) + : STextStyles.w500_14(context), + ), + ), + ) + .toList(), + onMenuStateChange: (isOpen) { + if (!isOpen) { + _billingCountrySearchController.clear(); + } + }, + onChanged: _loadingCountries + ? null + : (value) { + setState(() { + _billingSelectedCountryIso = value; + }); + }, + hint: Text( + _loadingCountries ? "Loading countries..." : "Country", + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldDefaultSearchIconLeft, + ) + : STextStyles.fieldLabel(context), + ), + isExpanded: true, + buttonStyleData: ButtonStyleData( + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + iconStyleData: IconStyleData( + icon: Padding( + padding: const EdgeInsets.only(right: 10), + child: SvgPicture.asset( + Assets.svg.chevronDown, + width: 12, + height: 6, + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, + ), + ), + ), + dropdownStyleData: DropdownStyleData( + offset: const Offset(0, 0), + elevation: 0, + maxHeight: 300, + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + dropdownSearchData: DropdownSearchData( + searchController: _billingCountrySearchController, + searchInnerWidgetHeight: 48, + searchInnerWidget: TextFormField( + controller: _billingCountrySearchController, + decoration: InputDecoration( + isDense: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 14, + ), + hintText: "Search...", + hintStyle: STextStyles.fieldLabel(context), + border: InputBorder.none, + ), + ), + searchMatchFn: (item, searchValue) { + final label = _countries + .where((c) => c['iso'] == item.value) + .map((c) => c['label'] as String) + .firstOrNull; + return label?.toLowerCase().contains( + searchValue.toLowerCase(), + ) ?? + false; + }, + ), + menuItemStyleData: const MenuItemStyleData( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + ), + ), + ), + ), + ], + const Spacer(), + PrimaryButton( + label: _submitting ? "Submitting..." : "Continue to payment", + enabled: _canContinue, + onPressed: _canContinue ? _continue : null, + ), + ], + ); + + if (isDesktop) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 600, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "ShopinBit", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), + child: content, + ), + ), + ], + ), + ); + } + + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () => Navigator.of(context).pop(), + ), + title: Text("ShopinBit", style: STextStyles.navBarTitle(context)), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.all(16), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 32, + ), + child: IntrinsicHeight(child: content), + ), + ), + ); + }, + ), + ), + ), + ); + } +} diff --git a/lib/pages/shopinbit/shopinbit_step_1.dart b/lib/pages/shopinbit/shopinbit_step_1.dart new file mode 100644 index 0000000000..6e6a097c42 --- /dev/null +++ b/lib/pages/shopinbit/shopinbit_step_1.dart @@ -0,0 +1,200 @@ +import 'package:flutter/material.dart'; + +import '../../models/shopinbit/shopinbit_order_model.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/constants.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../widgets/background.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../../widgets/stack_text_field.dart'; +import '../exchange_view/sub_widgets/step_row.dart'; +import 'shopinbit_step_2.dart'; + +class ShopInBitStep1 extends StatefulWidget { + const ShopInBitStep1({super.key, required this.model}); + + static const String routeName = "/shopInBitStep1"; + + final ShopInBitOrderModel model; + + @override + State createState() => _ShopInBitStep1State(); +} + +class _ShopInBitStep1State extends State { + late final TextEditingController _nameController; + late final FocusNode _nameFocusNode; + + bool get _canContinue => _nameController.text.trim().isNotEmpty; + + @override + void initState() { + super.initState(); + _nameController = TextEditingController(text: widget.model.displayName); + _nameFocusNode = FocusNode(); + + _nameFocusNode.addListener(() { + setState(() {}); + }); + } + + @override + void dispose() { + _nameController.dispose(); + _nameFocusNode.dispose(); + super.dispose(); + } + + void _continue() { + widget.model.displayName = _nameController.text.trim(); + if (Util.isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => ShopInBitStep2(model: widget.model), + ); + } else { + Navigator.of( + context, + ).pushNamed(ShopInBitStep2.routeName, arguments: widget.model); + } + } + + @override + Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + + final content = Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (!isDesktop) + StepRow( + count: 4, + current: 0, + width: MediaQuery.of(context).size.width - 32, + ), + if (!isDesktop) const SizedBox(height: 14), + Text( + "Create your profile", + style: isDesktop + ? STextStyles.desktopH2(context) + : STextStyles.pageTitleH1(context), + ), + SizedBox(height: isDesktop ? 16 : 8), + Text( + "Enter a display name to use with ShopinBit.", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.itemSubtitle(context), + ), + SizedBox(height: isDesktop ? 32 : 24), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + controller: _nameController, + focusNode: _nameFocusNode, + autocorrect: false, + enableSuggestions: false, + onChanged: (_) => setState(() {}), + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: + standardInputDecoration( + "Display name", + _nameFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + filled: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + ), + ), + const Spacer(), + PrimaryButton( + label: "Next", + enabled: _canContinue, + onPressed: _canContinue ? _continue : null, + ), + ], + ); + + if (isDesktop) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 400, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "ShopinBit", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), + child: content, + ), + ), + ], + ), + ); + } + + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () => Navigator.of(context).pop(), + ), + title: Text("ShopinBit", style: STextStyles.navBarTitle(context)), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.all(16), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 32, + ), + child: IntrinsicHeight(child: content), + ), + ), + ); + }, + ), + ), + ), + ); + } +} diff --git a/lib/pages/shopinbit/shopinbit_step_2.dart b/lib/pages/shopinbit/shopinbit_step_2.dart new file mode 100644 index 0000000000..6fa7fe1ea9 --- /dev/null +++ b/lib/pages/shopinbit/shopinbit_step_2.dart @@ -0,0 +1,293 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../models/shopinbit/shopinbit_order_model.dart'; +import '../../services/shopinbit/shopinbit_service.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/assets.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../widgets/background.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../exchange_view/sub_widgets/step_row.dart'; +import 'shopinbit_step_1.dart'; +import 'shopinbit_step_3.dart'; +import 'shopinbit_step_4.dart'; + +class ShopInBitStep2 extends StatefulWidget { + const ShopInBitStep2({super.key, required this.model}); + + static const String routeName = "/shopInBitStep2"; + + final ShopInBitOrderModel model; + + @override + State createState() => _ShopInBitStep2State(); +} + +class _ShopInBitStep2State extends State { + ShopInBitCategory? _selected; + + @override + void initState() { + super.initState(); + // Reset category selection. + widget.model.category = null; + _selected = null; + } + + void _popBack() { + if (Util.isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => ShopInBitStep1(model: widget.model), + ); + } else { + Navigator.of(context).pop(); + } + } + + void _continue() { + widget.model.category = _selected; + + final skipGuidelines = ShopInBitService.instance.loadGuidelinesAccepted(); + + if (Util.isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => ShopInBitStep3(model: widget.model), + ); + } else { + if (skipGuidelines) { + // Returning user — skip guidelines. + widget.model.guidelinesAccepted = true; + Navigator.of( + context, + ).pushNamed(ShopInBitStep4.routeName, arguments: widget.model); + } else { + Navigator.of( + context, + ).pushNamed(ShopInBitStep3.routeName, arguments: widget.model); + } + } + } + + Widget _categoryCard({ + required ShopInBitCategory category, + required String title, + required String description, + required String iconAsset, + required bool isDesktop, + }) { + final isSelected = _selected == category; + return GestureDetector( + onTap: () => setState(() => _selected = category), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(isDesktop ? 16 : 12), + border: Border.all( + color: isSelected + ? Theme.of(context).extension()!.textDark + : Theme.of(context).extension()!.background, + width: 2, + ), + color: Theme.of(context).extension()!.popupBG, + ), + padding: EdgeInsets.all(isDesktop ? 20 : 16), + child: Row( + children: [ + Container( + width: isDesktop ? 48 : 40, + height: isDesktop ? 48 : 40, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Theme.of( + context, + ).extension()!.textDark.withOpacity(0.1), + ), + alignment: Alignment.center, + child: SvgPicture.asset( + iconAsset, + width: isDesktop ? 24 : 20, + height: isDesktop ? 24 : 20, + color: Theme.of(context).extension()!.textDark, + ), + ), + SizedBox(width: isDesktop ? 16 : 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + const SizedBox(height: 4), + Text( + description, + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of( + context, + ).extension()!.textSubtitle1, + ), + ), + ], + ), + ), + if (isSelected) + Icon( + Icons.check_circle, + color: Theme.of(context).extension()!.textDark, + size: isDesktop ? 24 : 20, + ), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + + final content = Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (!isDesktop) + StepRow( + count: 4, + current: 1, + width: MediaQuery.of(context).size.width - 32, + ), + if (!isDesktop) const SizedBox(height: 14), + Text( + "Choose a service", + style: isDesktop + ? STextStyles.desktopH2(context) + : STextStyles.pageTitleH1(context), + ), + SizedBox(height: isDesktop ? 16 : 8), + Text( + "Select the type of service you need.", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.itemSubtitle(context), + ), + SizedBox(height: isDesktop ? 32 : 24), + _categoryCard( + category: ShopInBitCategory.concierge, + title: "Concierge", + description: "Purchase products and services online.", + iconAsset: Assets.svg.dollarSign, + isDesktop: isDesktop, + ), + SizedBox(height: isDesktop ? 16 : 12), + _categoryCard( + category: ShopInBitCategory.travel, + title: "Travel", + description: "Book flights, hotels, and more.", + iconAsset: Assets.svg.circleArrowUpRight, + isDesktop: isDesktop, + ), + SizedBox(height: isDesktop ? 16 : 12), + _categoryCard( + category: ShopInBitCategory.car, + title: "Car", + description: "Find and purchase vehicles.", + iconAsset: Assets.svg.boxAuto, + isDesktop: isDesktop, + ), + const Spacer(), + PrimaryButton( + label: "Next", + enabled: _selected != null, + onPressed: _selected != null ? _continue : null, + ), + ], + ); + + if (isDesktop) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 700, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + AppBarBackButton( + isCompact: true, + iconSize: 23, + onPressed: _popBack, + ), + Text("ShopinBit", style: STextStyles.desktopH3(context)), + ], + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), + child: content, + ), + ), + ], + ), + ); + } + + return Background( + child: PopScope( + canPop: false, + onPopInvokedWithResult: (bool didPop, dynamic result) { + if (!didPop) { + _popBack(); + } + }, + child: Scaffold( + backgroundColor: Theme.of( + context, + ).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton(onPressed: _popBack), + title: Text("ShopinBit", style: STextStyles.navBarTitle(context)), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.all(16), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 32, + ), + child: IntrinsicHeight(child: content), + ), + ), + ); + }, + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/shopinbit/shopinbit_step_3.dart b/lib/pages/shopinbit/shopinbit_step_3.dart new file mode 100644 index 0000000000..21f7b146f7 --- /dev/null +++ b/lib/pages/shopinbit/shopinbit_step_3.dart @@ -0,0 +1,241 @@ +import 'package:flutter/material.dart'; + +import '../../models/shopinbit/shopinbit_order_model.dart'; +import '../../services/shopinbit/shopinbit_service.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../widgets/background.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../../widgets/rounded_white_container.dart'; +import '../exchange_view/sub_widgets/step_row.dart'; +import 'shopinbit_step_2.dart'; +import 'shopinbit_step_4.dart'; + +class ShopInBitStep3 extends StatefulWidget { + const ShopInBitStep3({super.key, required this.model}); + + static const String routeName = "/shopInBitStep3"; + + final ShopInBitOrderModel model; + + @override + State createState() => _ShopInBitStep3State(); +} + +class _ShopInBitStep3State extends State { + bool _agreed = false; + + String _guidelinesText() { + switch (widget.model.category) { + case ShopInBitCategory.concierge: + return "Concierge Service Guidelines:\n\n" + "\u2022 Minimum: fee of 100 EUR or minimum order " + "value of 1,000 EUR.\n\n" + "\u2022 Service Fee: 10% of the order total.\n\n" + "\u2022 Only legal products and services are allowed.\n\n" + "\u2022 Prohibited: precious metals, prescription " + "medicine, live animals, weapons, adult " + "entertainment, EU real estate.\n\n" + "\u2022 Provide a clear and detailed description of the " + "product or service you want to purchase.\n\n" + "\u2022 Include links to the exact item when possible."; + case ShopInBitCategory.travel: + return "Travel Service Guidelines:\n\n" + "\u2022 Recommended budget: 2,500 EUR and above " + "for custom trips.\n\n" + "\u2022 Minimum: fee of 100 EUR or booking value " + "of 1,000 EUR.\n\n" + "\u2022 Service Fee: 10% of the booking amount.\n\n" + "\u2022 Only legal travel services are allowed.\n\n" + "\u2022 Prohibited: sanctioned destinations, illegal " + "bookings, adult entertainment, real estate " + "disguised as travel.\n\n" + "\u2022 Provide full details of your travel request " + "including dates, destinations, and preferences."; + case ShopInBitCategory.car: + return "Car Service Guidelines:\n\n" + "\u2022 Minimum Order: \u20AC20,000.\n\n" + "\u2022 Research Fee: \u20AC223 (incl. VAT) \u2014 " + "one-time, credited toward purchase.\n\n" + "\u2022 Service Fee: 10% of the vehicle value.\n\n" + "\u2022 Only legal vehicle transactions are allowed.\n\n" + "\u2022 Prohibited: export to sanctioned regions, " + "armored/military vehicles without licensing, " + "weapons/tactical accessories, real estate " + "disguised as vehicle purchases.\n\n" + "\u2022 Provide details about the make, model, year, " + "and any specific requirements."; + case null: + return ""; + } + } + + void _popBack() { + if (Util.isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => ShopInBitStep2(model: widget.model), + ); + } else { + Navigator.of(context).pop(); + } + } + + void _continue() { + widget.model.guidelinesAccepted = true; + // Persist acceptance. + ShopInBitService.instance.setGuidelinesAccepted(true); + if (Util.isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => ShopInBitStep4(model: widget.model), + ); + } else { + Navigator.of( + context, + ).pushNamed(ShopInBitStep4.routeName, arguments: widget.model); + } + } + + @override + Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + + final content = Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (!isDesktop) + StepRow( + count: 4, + current: 2, + width: MediaQuery.of(context).size.width - 32, + ), + if (!isDesktop) const SizedBox(height: 14), + Text( + "Service guidelines", + style: isDesktop + ? STextStyles.desktopH2(context) + : STextStyles.pageTitleH1(context), + ), + SizedBox(height: isDesktop ? 16 : 8), + Text( + "Please read the following carefully before continuing.", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.itemSubtitle(context), + ), + SizedBox(height: isDesktop ? 24 : 16), + Flexible( + child: RoundedWhiteContainer( + child: SingleChildScrollView( + child: Text( + _guidelinesText(), + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textDark, + ) + : STextStyles.itemSubtitle12(context), + ), + ), + ), + ), + CheckboxListTile( + value: _agreed, + onChanged: (v) => setState(() => _agreed = v ?? false), + title: Text( + "I have read and agree to the Service Guidelines", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + controlAffinity: ListTileControlAffinity.leading, + contentPadding: EdgeInsets.zero, + activeColor: Theme.of( + context, + ).extension()!.accentColorBlue, + ), + SizedBox(height: isDesktop ? 24 : 16), + PrimaryButton( + label: "Next", + enabled: _agreed, + onPressed: _agreed ? _continue : null, + ), + ], + ); + + if (isDesktop) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 650, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + AppBarBackButton( + isCompact: true, + iconSize: 23, + onPressed: _popBack, + ), + Text("ShopinBit", style: STextStyles.desktopH3(context)), + ], + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), + child: content, + ), + ), + ], + ), + ); + } + + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () => Navigator.of(context).pop(), + ), + title: Text("ShopinBit", style: STextStyles.navBarTitle(context)), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.all(16), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 32, + ), + child: IntrinsicHeight(child: content), + ), + ), + ); + }, + ), + ), + ), + ); + } +} diff --git a/lib/pages/shopinbit/shopinbit_step_4.dart b/lib/pages/shopinbit/shopinbit_step_4.dart new file mode 100644 index 0000000000..ea3757835b --- /dev/null +++ b/lib/pages/shopinbit/shopinbit_step_4.dart @@ -0,0 +1,2305 @@ +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import 'dart:async'; + +import '../../db/isar/main_db.dart'; +import '../../models/shopinbit/shopinbit_order_model.dart'; +import '../../notifications/show_flush_bar.dart'; +import '../../services/shopinbit/shopinbit_service.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/assets.dart'; +import '../../utilities/constants.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../widgets/background.dart'; +import '../../widgets/stack_dialog.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../../widgets/desktop/secondary_button.dart'; +import '../../widgets/rounded_white_container.dart'; +import '../../widgets/stack_text_field.dart'; +import '../exchange_view/sub_widgets/step_row.dart'; +import 'shopinbit_step_3.dart'; +import 'shopinbit_car_fee_view.dart'; +import 'shopinbit_order_created.dart'; +import 'shopinbit_tickets_view.dart'; + +class ShopInBitStep4 extends StatefulWidget { + const ShopInBitStep4({super.key, required this.model}); + + static const String routeName = "/shopInBitStep4"; + + final ShopInBitOrderModel model; + + @override + State createState() => _ShopInBitStep4State(); +} + +class _ShopInBitStep4State extends State { + // Generic form controllers. + late final TextEditingController _descriptionController; + late final FocusNode _descriptionFocusNode; + final TextEditingController _countrySearchController = + TextEditingController(); + + // Concierge-specific controllers + late final TextEditingController _whatToPurchaseController; + late final FocusNode _whatToPurchaseFocusNode; + late final TextEditingController _budgetController; + late final FocusNode _budgetFocusNode; + String? _selectedCondition; + bool _noLimit = false; + bool _whatToPurchaseTouched = false; + bool _budgetTouched = false; + + // Car Research-specific controllers + late final TextEditingController _brandController; + late final FocusNode _brandFocusNode; + late final TextEditingController _modelController; + late final FocusNode _modelFocusNode; + late final TextEditingController _carDescriptionController; + late final FocusNode _carDescriptionFocusNode; + late final TextEditingController _carBudgetController; + late final FocusNode _carBudgetFocusNode; + String? _selectedCarCondition; + bool _feeAcknowledged = false; + bool _brandTouched = false; + bool _modelTouched = false; + bool _carDescriptionTouched = false; + bool _carBudgetTouched = false; + + // Travel-specific controllers + late final TextEditingController _departureCountryController; + late final FocusNode _departureCountryFocusNode; + late final TextEditingController _departureCityController; + late final FocusNode _departureCityFocusNode; + late final TextEditingController _destinationsController; + late final FocusNode _destinationsFocusNode; + late final TextEditingController _departureDateController; + late final FocusNode _departureDateFocusNode; + late final TextEditingController _returnDateController; + late final FocusNode _returnDateFocusNode; + late final TextEditingController _tripLengthController; + late final FocusNode _tripLengthFocusNode; + late final TextEditingController _travelBudgetController; + late final FocusNode _travelBudgetFocusNode; + + // Travel dropdown state + String? _selectedArrangement; + String? _selectedDateMode; + String? _selectedFlexibility; + String? _selectedYear; + String? _selectedMonthSeason; + bool _needsRecommendations = false; + int _adults = 1; + int _children = 0; + int _infants = 0; + int _pets = 0; + + // Travel touched booleans + bool _departureCountryTouched = false; + bool _departureCityTouched = false; + bool _destinationsTouched = false; + bool _departureDateTouched = false; + bool _returnDateTouched = false; + bool _tripLengthTouched = false; + bool _travelBudgetTouched = false; + + List> _countries = []; + String? _selectedCountryIso; + bool _loadingCountries = false; + + bool _submitting = false; + bool _privacyAccepted = false; + + Future _showOpenBrowserWarning(BuildContext context, String url) async { + final uri = Uri.parse(url); + final shouldContinue = await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => Util.isDesktop + ? DesktopDialog( + maxWidth: 550, + maxHeight: 250, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 20, + ), + child: Column( + children: [ + Text("Attention", style: STextStyles.desktopH2(context)), + const SizedBox(height: 16), + Text( + "You are about to open " + "${uri.scheme}://${uri.host} " + "in your browser.", + style: STextStyles.desktopTextSmall(context), + ), + const SizedBox(height: 35), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 200, + buttonHeight: ButtonHeight.l, + label: "Cancel", + onPressed: () { + Navigator.of( + context, + rootNavigator: true, + ).pop(false); + }, + ), + const SizedBox(width: 20), + PrimaryButton( + width: 200, + buttonHeight: ButtonHeight.l, + label: "Continue", + onPressed: () { + Navigator.of( + context, + rootNavigator: true, + ).pop(true); + }, + ), + ], + ), + ], + ), + ), + ) + : StackDialog( + title: "Attention", + message: + "You are about to open " + "${uri.scheme}://${uri.host} " + "in your browser.", + leftButton: TextButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: Text( + "Cancel", + style: STextStyles.button(context).copyWith( + color: Theme.of( + context, + ).extension()!.accentColorDark, + ), + ), + ), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonStyle(context), + onPressed: () { + Navigator.of(context).pop(true); + }, + child: Text("Continue", style: STextStyles.button(context)), + ), + ), + ); + return shouldContinue ?? false; + } + + bool get _budgetIsValid { + final text = _budgetController.text.trim(); + if (text.isEmpty) return false; + final value = int.tryParse(text); + return value != null && value >= 1000 && value <= 100000; + } + + bool get _canContinue { + final cat = widget.model.category; + if (cat == ShopInBitCategory.concierge) { + return !_submitting && + _privacyAccepted && + _whatToPurchaseController.text.trim().length >= 10 && + _selectedCondition != null && + (_noLimit || _budgetIsValid) && + _selectedCountryIso != null; + } + if (cat == ShopInBitCategory.car) { + final carBudgetVal = int.tryParse(_carBudgetController.text.trim()); + return !_submitting && + _privacyAccepted && + _feeAcknowledged && + _brandController.text.trim().length >= 3 && + _modelController.text.trim().length >= 3 && + _carDescriptionController.text.trim().length >= 3 && + _selectedCarCondition != null && + carBudgetVal != null && + carBudgetVal >= 20000 && + _selectedCountryIso != null; + } + if (cat == ShopInBitCategory.travel) { + final travelBudgetVal = int.tryParse(_travelBudgetController.text.trim()); + final hasValidDates = _selectedDateMode == "Flexible dates" + ? (_selectedYear != null && + _selectedMonthSeason != null && + _tripLengthController.text.trim().isNotEmpty) + : (_selectedDateMode == "Exact dates" && + _departureDateController.text.trim().isNotEmpty && + _returnDateController.text.trim().isNotEmpty); + return !_submitting && + _privacyAccepted && + _selectedArrangement != null && + _departureCountryController.text.trim().isNotEmpty && + _departureCityController.text.trim().isNotEmpty && + (_needsRecommendations || + _destinationsController.text.trim().isNotEmpty) && + _selectedDateMode != null && + hasValidDates && + _adults >= 1 && + travelBudgetVal != null && + travelBudgetVal >= 1000; + } + // generic fallback + return !_submitting && + _privacyAccepted && + _descriptionController.text.trim().isNotEmpty && + _selectedCountryIso != null; + } + + @override + void initState() { + super.initState(); + _descriptionController = TextEditingController( + text: widget.model.requestDescription, + ); + _descriptionFocusNode = FocusNode(); + _descriptionFocusNode.addListener(() => setState(() {})); + + // Concierge-specific init + _whatToPurchaseController = TextEditingController(); + _whatToPurchaseFocusNode = FocusNode(); + _whatToPurchaseFocusNode.addListener(() { + if (!_whatToPurchaseFocusNode.hasFocus) { + _whatToPurchaseTouched = true; + } + setState(() {}); + }); + _budgetController = TextEditingController(text: "1000"); + _budgetFocusNode = FocusNode(); + _budgetFocusNode.addListener(() { + if (!_budgetFocusNode.hasFocus) { + _budgetTouched = true; + } + setState(() {}); + }); + + // Car Research-specific init + _brandController = TextEditingController(); + _brandFocusNode = FocusNode(); + _brandFocusNode.addListener(() { + if (!_brandFocusNode.hasFocus) { + _brandTouched = true; + } + setState(() {}); + }); + _modelController = TextEditingController(); + _modelFocusNode = FocusNode(); + _modelFocusNode.addListener(() { + if (!_modelFocusNode.hasFocus) { + _modelTouched = true; + } + setState(() {}); + }); + _carDescriptionController = TextEditingController(); + _carDescriptionFocusNode = FocusNode(); + _carDescriptionFocusNode.addListener(() { + if (!_carDescriptionFocusNode.hasFocus) { + _carDescriptionTouched = true; + } + setState(() {}); + }); + _carBudgetController = TextEditingController(); + _carBudgetFocusNode = FocusNode(); + _carBudgetFocusNode.addListener(() { + if (!_carBudgetFocusNode.hasFocus) { + _carBudgetTouched = true; + } + setState(() {}); + }); + + // Travel-specific init + _departureCountryController = TextEditingController(); + _departureCountryFocusNode = FocusNode(); + _departureCountryFocusNode.addListener(() { + if (!_departureCountryFocusNode.hasFocus) { + _departureCountryTouched = true; + } + setState(() {}); + }); + _departureCityController = TextEditingController(); + _departureCityFocusNode = FocusNode(); + _departureCityFocusNode.addListener(() { + if (!_departureCityFocusNode.hasFocus) { + _departureCityTouched = true; + } + setState(() {}); + }); + _destinationsController = TextEditingController(); + _destinationsFocusNode = FocusNode(); + _destinationsFocusNode.addListener(() { + if (!_destinationsFocusNode.hasFocus) { + _destinationsTouched = true; + } + setState(() {}); + }); + _departureDateController = TextEditingController(); + _departureDateFocusNode = FocusNode(); + _departureDateFocusNode.addListener(() { + if (!_departureDateFocusNode.hasFocus) { + _departureDateTouched = true; + } + setState(() {}); + }); + _returnDateController = TextEditingController(); + _returnDateFocusNode = FocusNode(); + _returnDateFocusNode.addListener(() { + if (!_returnDateFocusNode.hasFocus) { + _returnDateTouched = true; + } + setState(() {}); + }); + _tripLengthController = TextEditingController(); + _tripLengthFocusNode = FocusNode(); + _tripLengthFocusNode.addListener(() { + if (!_tripLengthFocusNode.hasFocus) { + _tripLengthTouched = true; + } + setState(() {}); + }); + _travelBudgetController = TextEditingController(text: "5000"); + _travelBudgetFocusNode = FocusNode(); + _travelBudgetFocusNode.addListener(() { + if (!_travelBudgetFocusNode.hasFocus) { + _travelBudgetTouched = true; + } + setState(() {}); + }); + + if (widget.model.deliveryCountry.isNotEmpty) { + _selectedCountryIso = widget.model.deliveryCountry; + } + _fetchCountries(); + } + + @override + void dispose() { + _descriptionController.dispose(); + _descriptionFocusNode.dispose(); + _countrySearchController.dispose(); + _whatToPurchaseController.dispose(); + _whatToPurchaseFocusNode.dispose(); + _budgetController.dispose(); + _budgetFocusNode.dispose(); + _brandController.dispose(); + _brandFocusNode.dispose(); + _modelController.dispose(); + _modelFocusNode.dispose(); + _carDescriptionController.dispose(); + _carDescriptionFocusNode.dispose(); + _carBudgetController.dispose(); + _carBudgetFocusNode.dispose(); + _departureCountryController.dispose(); + _departureCountryFocusNode.dispose(); + _departureCityController.dispose(); + _departureCityFocusNode.dispose(); + _destinationsController.dispose(); + _destinationsFocusNode.dispose(); + _departureDateController.dispose(); + _departureDateFocusNode.dispose(); + _returnDateController.dispose(); + _returnDateFocusNode.dispose(); + _tripLengthController.dispose(); + _tripLengthFocusNode.dispose(); + _travelBudgetController.dispose(); + _travelBudgetFocusNode.dispose(); + super.dispose(); + } + + void _popBack() { + if (Util.isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => ShopInBitStep3(model: widget.model), + ); + } else { + Navigator.of(context).pop(); + } + } + + Future _fetchCountries() async { + setState(() => _loadingCountries = true); + try { + final resp = await ShopInBitService.instance.client.getCountries(); + if (resp.hasError || resp.value == null) return; + _countries = resp.value!; + if (_selectedCountryIso != null && + !_countries.any((c) => c['iso'] == _selectedCountryIso)) { + _selectedCountryIso = null; + } + } catch (_) { + // leave list empty; user will see no items + } finally { + if (mounted) setState(() => _loadingCountries = false); + } + } + + Future _submit() async { + // Format structured comment per category. + // Use ISO code for delivery country in comment: country labels can + // contain non-ASCII (e.g. "Åland Islands") which HttpClientRequest.write() + // encodes as Latin-1, corrupting the JSON body on mobile. + final countryIso = _selectedCountryIso!; + if (widget.model.category == ShopInBitCategory.concierge) { + final budgetText = _noLimit + ? "No limit" + : "${_budgetController.text.trim()} EUR"; + widget.model.requestDescription = + "What to purchase: ${_whatToPurchaseController.text.trim()}\n" + "Condition: $_selectedCondition\n" + "Budget: $budgetText\n" + "Delivery country: $countryIso"; + } else if (widget.model.category == ShopInBitCategory.car) { + widget.model.requestDescription = + "Brand: ${_brandController.text.trim()}\n" + "Model: ${_modelController.text.trim()}\n" + "Condition: $_selectedCarCondition\n" + "Description: ${_carDescriptionController.text.trim()}\n" + "Budget: ${_carBudgetController.text.trim()} EUR\n" + "Delivery country: $countryIso"; + } else if (widget.model.category == ShopInBitCategory.travel) { + final parts = [ + "Arrangement: $_selectedArrangement", + "Departure: ${_departureCityController.text.trim()}, " + "${_departureCountryController.text.trim()}", + ]; + + if (_needsRecommendations) { + parts.add("Destinations: Recommendations requested"); + } else { + parts.add("Destinations: ${_destinationsController.text.trim()}"); + } + + if (_selectedDateMode == "Exact dates") { + final flex = + _selectedFlexibility != null && _selectedFlexibility != "Exact" + ? " ($_selectedFlexibility)" + : ""; + parts.add( + "Dates: ${_departureDateController.text.trim()} - " + "${_returnDateController.text.trim()}$flex", + ); + } else if (_selectedDateMode == "Flexible dates") { + parts.add( + "Dates: $_selectedMonthSeason $_selectedYear, " + "${_tripLengthController.text.trim()} nights", + ); + } + + final travelers = []; + travelers.add("$_adults adult${_adults > 1 ? 's' : ''}"); + if (_children > 0) { + travelers.add("$_children child${_children > 1 ? 'ren' : ''}"); + } + if (_infants > 0) { + travelers.add("$_infants infant${_infants > 1 ? 's' : ''}"); + } + if (_pets > 0) { + travelers.add("$_pets pet${_pets > 1 ? 's' : ''}"); + } + parts.add("Travelers: ${travelers.join(', ')}"); + + parts.add("Budget: ${_travelBudgetController.text.trim()} EUR"); + + widget.model.requestDescription = parts.join("\n"); + } else { + widget.model.requestDescription = _descriptionController.text.trim(); + } + // Travel doesn't collect delivery country: use departure country or "DE" + // as a default since the API requires the field. + if (widget.model.category == ShopInBitCategory.travel) { + widget.model.deliveryCountry = "DE"; + } else { + widget.model.deliveryCountry = _selectedCountryIso!; + } + + if (widget.model.category == ShopInBitCategory.car) { + // Block if another car research flow is already in progress. + final existingPending = MainDB.instance + .getShopInBitTickets() + .where((t) => t.isPendingPayment) + .toList(); + + if (existingPending.isNotEmpty && mounted) { + final resumePrevious = await showDialog( + context: context, + barrierDismissible: false, + builder: (ctx) => AlertDialog( + title: const Text("In-Progress Car Research"), + content: const Text( + "You have an unfinished car research payment. " + "Would you like to resume it or start a new search?", + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(ctx).pop(true), + child: const Text("Resume Previous"), + ), + TextButton( + onPressed: () => Navigator.of(ctx).pop(false), + child: const Text("Start New"), + ), + ], + ), + ); + + if (resumePrevious == true && mounted) { + setState(() => _submitting = false); + unawaited( + Navigator.of(context).pushNamedAndRemoveUntil( + ShopInBitTicketsView.routeName, + (route) => route.isFirst, + ), + ); + return; + } + } + + if (!mounted) return; + + if (Util.isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + unawaited( + showDialog( + context: context, + builder: (_) => ShopInBitCarFeeView(model: widget.model), + ), + ); + } else { + unawaited( + Navigator.of( + context, + ).pushNamed(ShopInBitCarFeeView.routeName, arguments: widget.model), + ); + } + return; + } + + setState(() => _submitting = true); + try { + final service = ShopInBitService.instance; + final customerKey = await service.ensureCustomerKey(); + + assert( + widget.model.category != null, + 'Step 4 reached with null category: Step 2 must set category before reaching Step 4', + ); + + // API service_type: travel requests use "concierge" because the + // ShopinBit API routes both through the same concierge pipeline. + // Travel-specific details are captured in the structured comment field. + final categoryStr = switch (widget.model.category) { + ShopInBitCategory.concierge => "concierge", + ShopInBitCategory.travel => "concierge", + ShopInBitCategory.car => "car", + null => throw StateError('category must be non-null at Step 4 submit'), + }; + + final resp = await service.client.createRequest( + customerPseudonym: widget.model.displayName, + externalCustomerKey: customerKey, + serviceType: categoryStr, + comment: widget.model.requestDescription, + deliveryCountry: widget.model.deliveryCountry, + ); + + if (resp.hasError) { + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: resp.exception?.message ?? "Failed to create request", + context: context, + ), + ); + } + return; + } + + final ref = resp.value!; + widget.model.apiTicketId = ref.id; + widget.model.ticketId = ref.number; + widget.model.status = ShopInBitOrderStatus.pending; + await MainDB.instance.putShopInBitTicket(widget.model.toIsarTicket()); + + if (!mounted) return; + if (Util.isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + unawaited( + showDialog( + context: context, + builder: (_) => ShopInBitOrderCreated(model: widget.model), + ), + ); + } else { + unawaited( + Navigator.of( + context, + ).pushNamed(ShopInBitOrderCreated.routeName, arguments: widget.model), + ); + } + } catch (e) { + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Failed to create request: $e", + context: context, + ), + ); + } + } finally { + if (mounted) setState(() => _submitting = false); + } + } + + // Shared widgets. + Widget _buildCountryPicker(bool isDesktop) { + return ClipRRect( + borderRadius: BorderRadius.circular(Constants.size.circularBorderRadius), + child: DropdownButtonHideUnderline( + child: DropdownButton2( + value: _selectedCountryIso, + items: _countries + .map( + (c) => DropdownMenuItem( + value: c['iso'] as String, + child: Text( + c['label'] as String, + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + ) + : STextStyles.w500_14(context), + ), + ), + ) + .toList(), + onMenuStateChange: (isOpen) { + if (!isOpen) { + _countrySearchController.clear(); + } + }, + onChanged: _loadingCountries + ? null + : (value) { + setState(() { + _selectedCountryIso = value; + }); + }, + hint: Text( + _loadingCountries ? "Loading countries..." : "Delivery country", + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldDefaultSearchIconLeft, + ) + : STextStyles.fieldLabel(context), + ), + isExpanded: true, + buttonStyleData: ButtonStyleData( + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + iconStyleData: IconStyleData( + icon: Padding( + padding: const EdgeInsets.only(right: 10), + child: SvgPicture.asset( + Assets.svg.chevronDown, + width: 12, + height: 6, + color: Theme.of( + context, + ).extension()!.textFieldActiveSearchIconRight, + ), + ), + ), + dropdownStyleData: DropdownStyleData( + offset: const Offset(0, 0), + elevation: 0, + maxHeight: 300, + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + dropdownSearchData: DropdownSearchData( + searchController: _countrySearchController, + searchInnerWidgetHeight: 48, + searchInnerWidget: TextFormField( + controller: _countrySearchController, + decoration: InputDecoration( + isDense: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 14, + ), + hintText: "Search...", + hintStyle: STextStyles.fieldLabel(context), + border: InputBorder.none, + ), + ), + searchMatchFn: (item, searchValue) { + final label = _countries + .where((c) => c['iso'] == item.value) + .map((c) => c['label'] as String) + .firstOrNull; + return label?.toLowerCase().contains(searchValue.toLowerCase()) ?? + false; + }, + ), + menuItemStyleData: const MenuItemStyleData( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + ), + ), + ), + ); + } + + Widget _buildPrivacyCheckbox(bool isDesktop) { + return GestureDetector( + onTap: () { + setState(() { + _privacyAccepted = !_privacyAccepted; + }); + }, + child: Container( + color: Colors.transparent, + child: Row( + crossAxisAlignment: isDesktop + ? CrossAxisAlignment.center + : CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(top: isDesktop ? 3 : 0), + child: SizedBox( + width: 20, + height: 20, + child: IgnorePointer( + child: Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: _privacyAccepted, + onChanged: (_) {}, + ), + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: RichText( + text: TextSpan( + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.w500_14(context), + children: [ + const TextSpan( + text: "I have read and agree to the ShopinBit ", + ), + TextSpan( + text: "Privacy Policy", + style: STextStyles.richLink( + context, + ).copyWith(fontSize: isDesktop ? 18 : 14), + recognizer: TapGestureRecognizer() + ..onTap = () async { + const url = + "https://api.shopinbit.com/static/policy/privacy.html"; + final shouldOpen = await _showOpenBrowserWarning( + context, + url, + ); + if (shouldOpen) { + await launchUrl( + Uri.parse(url), + mode: LaunchMode.externalApplication, + ); + } + }, + ), + const TextSpan(text: "."), + ], + ), + ), + ), + ], + ), + ), + ); + } + + Widget _buildSubmitButton() { + return PrimaryButton( + label: _submitting ? "Submitting..." : "Submit request", + enabled: _canContinue, + onPressed: _canContinue ? _submit : null, + ); + } + + // Per-category form builders. + + Widget _buildConciergeContent(bool isDesktop) { + final whatToPurchaseError = + _whatToPurchaseTouched && + _whatToPurchaseController.text.trim().length < 10 + ? "Minimum 10 characters" + : null; + + final budgetError = _budgetTouched && !_noLimit && !_budgetIsValid + ? "Enter a value between 1,000 and 100,000" + : null; + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (!isDesktop) + StepRow( + count: 4, + current: 3, + width: MediaQuery.of(context).size.width - 32, + ), + if (!isDesktop) const SizedBox(height: 14), + Text( + "What would you like to purchase?", + style: isDesktop + ? STextStyles.desktopH2(context) + : STextStyles.pageTitleH1(context), + ), + SizedBox(height: isDesktop ? 16 : 8), + Text( + "Tell us what you're looking for and we'll find it for you.", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.itemSubtitle(context), + ), + SizedBox(height: isDesktop ? 32 : 24), + + // What to purchase free-text field + TextField( + controller: _whatToPurchaseController, + focusNode: _whatToPurchaseFocusNode, + autocorrect: false, + enableSuggestions: false, + minLines: 3, + maxLines: 6, + onChanged: (_) => setState(() {}), + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: + standardInputDecoration( + "Describe what you'd like to purchase (e.g., electronics, luxury goods, services...)", + _whatToPurchaseFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + filled: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + errorText: whatToPurchaseError, + ), + ), + SizedBox(height: isDesktop ? 24 : 16), + + // Condition picker + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: DropdownButtonHideUnderline( + child: DropdownButton2( + value: _selectedCondition, + items: ["NEW", "USED"] + .map( + (c) => DropdownMenuItem( + value: c, + child: Text( + c, + style: isDesktop + ? STextStyles.desktopTextExtraSmall( + context, + ).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + ) + : STextStyles.w500_14(context), + ), + ), + ) + .toList(), + onChanged: (value) { + setState(() { + _selectedCondition = value; + }); + }, + hint: Text( + "Condition", + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldDefaultSearchIconLeft, + ) + : STextStyles.fieldLabel(context), + ), + isExpanded: true, + buttonStyleData: ButtonStyleData( + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + iconStyleData: IconStyleData( + icon: Padding( + padding: const EdgeInsets.only(right: 10), + child: SvgPicture.asset( + Assets.svg.chevronDown, + width: 12, + height: 6, + color: Theme.of( + context, + ).extension()!.textFieldActiveSearchIconRight, + ), + ), + ), + dropdownStyleData: DropdownStyleData( + offset: const Offset(0, 0), + elevation: 0, + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + menuItemStyleData: const MenuItemStyleData( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + ), + ), + ), + ), + SizedBox(height: isDesktop ? 24 : 16), + + // Budget field + TextField( + controller: _budgetController, + focusNode: _budgetFocusNode, + autocorrect: false, + enableSuggestions: false, + enabled: !_noLimit, + keyboardType: TextInputType.number, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + onChanged: (_) => setState(() {}), + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: + standardInputDecoration( + "Budget (\u20AC)", + _budgetFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + filled: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + suffixText: "\u20AC", + errorText: budgetError, + ), + ), + SizedBox(height: isDesktop ? 12 : 8), + + // No budget limit checkbox + GestureDetector( + onTap: () { + setState(() { + _noLimit = !_noLimit; + }); + }, + child: Container( + color: Colors.transparent, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: 20, + height: 20, + child: IgnorePointer( + child: Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: _noLimit, + onChanged: (_) {}, + ), + ), + ), + const SizedBox(width: 12), + Text( + "No budget limit", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.w500_14(context), + ), + ], + ), + ), + ), + SizedBox(height: isDesktop ? 24 : 16), + + // Country picker (shared) + _buildCountryPicker(isDesktop), + SizedBox(height: isDesktop ? 16 : 12), + + // Privacy checkbox (shared) + _buildPrivacyCheckbox(isDesktop), + SizedBox(height: isDesktop ? 16 : 12), + + // Submit button (shared) + _buildSubmitButton(), + ], + ); + } + + Widget _buildCarContent(bool isDesktop) { + final brandError = _brandTouched && _brandController.text.trim().length < 3 + ? "Minimum 3 characters" + : null; + + final modelError = _modelTouched && _modelController.text.trim().length < 3 + ? "Minimum 3 characters" + : null; + + final carDescriptionError = + _carDescriptionTouched && + _carDescriptionController.text.trim().length < 3 + ? "Minimum 3 characters" + : null; + + final carBudgetText = _carBudgetController.text.trim(); + final carBudgetVal = int.tryParse(carBudgetText); + final carBudgetError = + _carBudgetTouched && + (carBudgetText.isEmpty || + carBudgetVal == null || + carBudgetVal < 20000) + ? "Minimum budget is 20,000\u20AC" + : null; + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (!isDesktop) + StepRow( + count: 4, + current: 3, + width: MediaQuery.of(context).size.width - 32, + ), + if (!isDesktop) const SizedBox(height: 14), + Text( + "Car Research request", + style: isDesktop + ? STextStyles.desktopH2(context) + : STextStyles.pageTitleH1(context), + ), + SizedBox(height: isDesktop ? 16 : 8), + Text( + "Tell us about the car you're looking for.", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.itemSubtitle(context), + ), + SizedBox(height: isDesktop ? 32 : 24), + + // Country picker (shared) + _buildCountryPicker(isDesktop), + SizedBox(height: isDesktop ? 24 : 16), + + // Brand field + TextField( + controller: _brandController, + focusNode: _brandFocusNode, + autocorrect: false, + enableSuggestions: false, + onChanged: (_) => setState(() {}), + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: + standardInputDecoration( + "Car brand (e.g., BMW, Mercedes, Toyota...)", + _brandFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + filled: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + errorText: brandError, + ), + ), + SizedBox(height: isDesktop ? 24 : 16), + + // Model field + TextField( + controller: _modelController, + focusNode: _modelFocusNode, + autocorrect: false, + enableSuggestions: false, + onChanged: (_) => setState(() {}), + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: + standardInputDecoration( + "Car model (e.g., 3 Series, E-Class, Camry...)", + _modelFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + filled: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + errorText: modelError, + ), + ), + SizedBox(height: isDesktop ? 24 : 16), + + // Condition picker + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: DropdownButtonHideUnderline( + child: DropdownButton2( + value: _selectedCarCondition, + items: ["NEW", "PREOWNED"] + .map( + (c) => DropdownMenuItem( + value: c, + child: Text( + c, + style: isDesktop + ? STextStyles.desktopTextExtraSmall( + context, + ).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + ) + : STextStyles.w500_14(context), + ), + ), + ) + .toList(), + onChanged: (value) { + setState(() { + _selectedCarCondition = value; + }); + }, + hint: Text( + "Condition", + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldDefaultSearchIconLeft, + ) + : STextStyles.fieldLabel(context), + ), + isExpanded: true, + buttonStyleData: ButtonStyleData( + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + iconStyleData: IconStyleData( + icon: Padding( + padding: const EdgeInsets.only(right: 10), + child: SvgPicture.asset( + Assets.svg.chevronDown, + width: 12, + height: 6, + color: Theme.of( + context, + ).extension()!.textFieldActiveSearchIconRight, + ), + ), + ), + dropdownStyleData: DropdownStyleData( + offset: const Offset(0, 0), + elevation: 0, + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + menuItemStyleData: const MenuItemStyleData( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + ), + ), + ), + ), + SizedBox(height: isDesktop ? 24 : 16), + + // Description field (multiline) + TextField( + controller: _carDescriptionController, + focusNode: _carDescriptionFocusNode, + autocorrect: false, + enableSuggestions: false, + minLines: 3, + maxLines: 6, + onChanged: (_) => setState(() {}), + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: + standardInputDecoration( + "Describe your requirements (year, mileage, features...)", + _carDescriptionFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + filled: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + errorText: carDescriptionError, + ), + ), + SizedBox(height: isDesktop ? 24 : 16), + + // Budget field + TextField( + controller: _carBudgetController, + focusNode: _carBudgetFocusNode, + autocorrect: false, + enableSuggestions: false, + keyboardType: TextInputType.number, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + onChanged: (_) => setState(() {}), + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: + standardInputDecoration( + "Budget (\u20AC, minimum 20,000)", + _carBudgetFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + filled: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + suffixText: "\u20AC", + errorText: carBudgetError, + ), + ), + SizedBox(height: isDesktop ? 24 : 16), + + // Research fee info box + RoundedWhiteContainer( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.info_outline, + size: 20, + color: Theme.of( + context, + ).extension()!.textFieldActiveSearchIconLeft, + ), + const SizedBox(width: 12), + Expanded( + child: RichText( + text: TextSpan( + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.w500_14(context), + children: [ + TextSpan( + text: "Research fee: ", + style: isDesktop + ? STextStyles.desktopTextSmall( + context, + ).copyWith(fontWeight: FontWeight.bold) + : STextStyles.w500_14( + context, + ).copyWith(fontWeight: FontWeight.bold), + ), + const TextSpan( + text: + "\u20AC223 (incl. VAT): one-time payment, credited toward your purchase.", + ), + ], + ), + ), + ), + ], + ), + ), + SizedBox(height: isDesktop ? 16 : 12), + + // Fee acknowledgement checkbox + GestureDetector( + onTap: () { + setState(() { + _feeAcknowledged = !_feeAcknowledged; + }); + }, + child: Container( + color: Colors.transparent, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: 20, + height: 20, + child: IgnorePointer( + child: Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: _feeAcknowledged, + onChanged: (_) {}, + ), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + "I acknowledge the \u20AC223 research fee", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.w500_14(context), + ), + ), + ], + ), + ), + ), + SizedBox(height: isDesktop ? 16 : 12), + + // Privacy checkbox (shared) + _buildPrivacyCheckbox(isDesktop), + SizedBox(height: isDesktop ? 16 : 12), + + // Submit button (shared) + _buildSubmitButton(), + ], + ); + } + + Widget _buildGenericContent(bool isDesktop) { + const descriptionTitle = "Describe your travel request"; + const descriptionSubtitle = "Provide details about your trip."; + const descriptionPlaceholder = + "Describe your travel request (destinations, dates, passengers)"; + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (!isDesktop) + StepRow( + count: 4, + current: 3, + width: MediaQuery.of(context).size.width - 32, + ), + if (!isDesktop) const SizedBox(height: 14), + Text( + descriptionTitle, + style: isDesktop + ? STextStyles.desktopH2(context) + : STextStyles.pageTitleH1(context), + ), + SizedBox(height: isDesktop ? 16 : 8), + Text( + descriptionSubtitle, + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.itemSubtitle(context), + ), + SizedBox(height: isDesktop ? 32 : 24), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + controller: _descriptionController, + focusNode: _descriptionFocusNode, + autocorrect: false, + enableSuggestions: false, + minLines: 3, + maxLines: 6, + onChanged: (_) => setState(() {}), + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: + standardInputDecoration( + descriptionPlaceholder, + _descriptionFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + filled: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + ), + ), + SizedBox(height: isDesktop ? 24 : 16), + + // Country picker (shared) + _buildCountryPicker(isDesktop), + SizedBox(height: isDesktop ? 16 : 12), + + // Privacy checkbox (shared) + _buildPrivacyCheckbox(isDesktop), + SizedBox(height: isDesktop ? 16 : 12), + + // Submit button (shared) + _buildSubmitButton(), + ], + ); + } + + // Travel form helpers. + Widget _buildTravelDropdown({ + required String? value, + required List items, + required String hint, + required ValueChanged onChanged, + required bool isDesktop, + }) { + return ClipRRect( + borderRadius: BorderRadius.circular(Constants.size.circularBorderRadius), + child: DropdownButtonHideUnderline( + child: DropdownButton2( + value: value, + items: items + .map( + (c) => DropdownMenuItem( + value: c, + child: Text( + c, + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + ) + : STextStyles.w500_14(context), + ), + ), + ) + .toList(), + onChanged: onChanged, + hint: Text( + hint, + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldDefaultSearchIconLeft, + ) + : STextStyles.fieldLabel(context), + ), + isExpanded: true, + buttonStyleData: ButtonStyleData( + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + iconStyleData: IconStyleData( + icon: Padding( + padding: const EdgeInsets.only(right: 10), + child: SvgPicture.asset( + Assets.svg.chevronDown, + width: 12, + height: 6, + color: Theme.of( + context, + ).extension()!.textFieldActiveSearchIconRight, + ), + ), + ), + dropdownStyleData: DropdownStyleData( + offset: const Offset(0, 0), + elevation: 0, + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + menuItemStyleData: const MenuItemStyleData( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + ), + ), + ), + ); + } + + Widget _buildTravelerCounter({ + required String label, + required int value, + required int min, + required int max, + required ValueChanged onChanged, + required bool isDesktop, + }) { + return Row( + children: [ + Text( + label, + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.w500_14(context), + ), + const Spacer(), + InkWell( + onTap: value > min ? () => onChanged(value - 1) : null, + child: Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: Center( + child: Text( + "-", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.w500_14(context), + ), + ), + ), + ), + const SizedBox(width: 16), + SizedBox( + width: 24, + child: Center( + child: Text( + "$value", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.w500_14(context), + ), + ), + ), + const SizedBox(width: 16), + InkWell( + onTap: value < max ? () => onChanged(value + 1) : null, + child: Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: Center( + child: Text( + "+", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.w500_14(context), + ), + ), + ), + ), + ], + ); + } + + Widget _buildTravelContent(bool isDesktop) { + final departureCountryError = + _departureCountryTouched && + _departureCountryController.text.trim().isEmpty + ? "Required" + : null; + + final departureCityError = + _departureCityTouched && _departureCityController.text.trim().isEmpty + ? "Required" + : null; + + final destinationsError = + _destinationsTouched && + _destinationsController.text.trim().isEmpty && + !_needsRecommendations + ? "Required (or check 'I need recommendations')" + : null; + + final departureDateError = + _departureDateTouched && _departureDateController.text.trim().isEmpty + ? "Required" + : null; + + final returnDateError = + _returnDateTouched && _returnDateController.text.trim().isEmpty + ? "Required" + : null; + + final tripLengthError = + _tripLengthTouched && _tripLengthController.text.trim().isEmpty + ? "Required" + : null; + + final travelBudgetText = _travelBudgetController.text.trim(); + final travelBudgetVal = int.tryParse(travelBudgetText); + final travelBudgetError = + _travelBudgetTouched && + (travelBudgetText.isEmpty || + travelBudgetVal == null || + travelBudgetVal < 1000) + ? "Minimum budget is 1,000 EUR" + : null; + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (!isDesktop) + StepRow( + count: 4, + current: 3, + width: MediaQuery.of(context).size.width - 32, + ), + if (!isDesktop) const SizedBox(height: 14), + Text( + "Travel request", + style: isDesktop + ? STextStyles.desktopH2(context) + : STextStyles.pageTitleH1(context), + ), + SizedBox(height: isDesktop ? 16 : 8), + Text( + "Tell us about your trip and we'll arrange everything.", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.itemSubtitle(context), + ), + SizedBox(height: isDesktop ? 32 : 24), + + // === Trip Type === + Text( + "Trip type", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.w500_14(context), + ), + SizedBox(height: isDesktop ? 12 : 8), + _buildTravelDropdown( + value: _selectedArrangement, + items: const [ + "Flights Only", + "Hotels Only", + "Flights + Hotels", + "Full Service", + ], + hint: "Arrangement type", + onChanged: (val) => setState(() => _selectedArrangement = val), + isDesktop: isDesktop, + ), + + // === Where === + SizedBox(height: isDesktop ? 24 : 16), + Text( + "Where", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.w500_14(context), + ), + SizedBox(height: isDesktop ? 12 : 8), + TextField( + controller: _departureCountryController, + focusNode: _departureCountryFocusNode, + autocorrect: false, + enableSuggestions: false, + onChanged: (_) => setState(() {}), + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: + standardInputDecoration( + "Departure country", + _departureCountryFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + filled: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + errorText: departureCountryError, + ), + ), + SizedBox(height: isDesktop ? 16 : 12), + TextField( + controller: _departureCityController, + focusNode: _departureCityFocusNode, + autocorrect: false, + enableSuggestions: false, + onChanged: (_) => setState(() {}), + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: + standardInputDecoration( + "Departure city", + _departureCityFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + filled: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + errorText: departureCityError, + ), + ), + SizedBox(height: isDesktop ? 16 : 12), + TextField( + controller: _destinationsController, + focusNode: _destinationsFocusNode, + autocorrect: false, + enableSuggestions: false, + enabled: !_needsRecommendations, + onChanged: (_) => setState(() {}), + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: + standardInputDecoration( + "e.g. Paris, France; Rome, Italy", + _destinationsFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + filled: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + errorText: destinationsError, + ), + ), + SizedBox(height: isDesktop ? 12 : 8), + GestureDetector( + onTap: () { + setState(() { + _needsRecommendations = !_needsRecommendations; + }); + }, + child: Container( + color: Colors.transparent, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: 20, + height: 20, + child: IgnorePointer( + child: Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: _needsRecommendations, + onChanged: (_) {}, + ), + ), + ), + const SizedBox(width: 12), + Text( + "I need recommendations", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.w500_14(context), + ), + ], + ), + ), + ), + + // === When === + SizedBox(height: isDesktop ? 24 : 16), + Text( + "When", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.w500_14(context), + ), + SizedBox(height: isDesktop ? 12 : 8), + _buildTravelDropdown( + value: _selectedDateMode, + items: const ["Exact dates", "Flexible dates"], + hint: "Date mode", + onChanged: (val) => setState(() => _selectedDateMode = val), + isDesktop: isDesktop, + ), + SizedBox(height: isDesktop ? 16 : 12), + + if (_selectedDateMode == "Exact dates") ...[ + TextField( + controller: _departureDateController, + focusNode: _departureDateFocusNode, + autocorrect: false, + enableSuggestions: false, + keyboardType: TextInputType.datetime, + onChanged: (_) => setState(() {}), + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: + standardInputDecoration( + "DD/MM/YYYY", + _departureDateFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + filled: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + labelText: "Departure date", + errorText: departureDateError, + ), + ), + SizedBox(height: isDesktop ? 16 : 12), + TextField( + controller: _returnDateController, + focusNode: _returnDateFocusNode, + autocorrect: false, + enableSuggestions: false, + keyboardType: TextInputType.datetime, + onChanged: (_) => setState(() {}), + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: + standardInputDecoration( + "DD/MM/YYYY", + _returnDateFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + filled: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + labelText: "Return date", + errorText: returnDateError, + ), + ), + SizedBox(height: isDesktop ? 16 : 12), + _buildTravelDropdown( + value: _selectedFlexibility, + items: const [ + "Exact", + "\u00B1 1 day", + "\u00B1 2-3 days", + "+ 1 week", + ], + hint: "Flexibility", + onChanged: (val) => setState(() => _selectedFlexibility = val), + isDesktop: isDesktop, + ), + ], + + if (_selectedDateMode == "Flexible dates") ...[ + _buildTravelDropdown( + value: _selectedYear, + items: ["${DateTime.now().year}", "${DateTime.now().year + 1}"], + hint: "Year", + onChanged: (val) => setState(() => _selectedYear = val), + isDesktop: isDesktop, + ), + SizedBox(height: isDesktop ? 16 : 12), + _buildTravelDropdown( + value: _selectedMonthSeason, + items: const [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + "Spring (Mar-May)", + "Summer (Jun-Aug)", + "Fall (Sep-Nov)", + "Winter (Dec-Feb)", + ], + hint: "Month or season", + onChanged: (val) => setState(() => _selectedMonthSeason = val), + isDesktop: isDesktop, + ), + SizedBox(height: isDesktop ? 16 : 12), + TextField( + controller: _tripLengthController, + focusNode: _tripLengthFocusNode, + autocorrect: false, + enableSuggestions: false, + keyboardType: TextInputType.number, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + onChanged: (_) => setState(() {}), + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: + standardInputDecoration( + "Number of nights", + _tripLengthFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + filled: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + errorText: tripLengthError, + ), + ), + ], + + // === Who === + SizedBox(height: isDesktop ? 24 : 16), + Text( + "Who", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.w500_14(context), + ), + SizedBox(height: isDesktop ? 12 : 8), + _buildTravelerCounter( + label: "Adults", + value: _adults, + min: 1, + max: 20, + onChanged: (v) => setState(() => _adults = v), + isDesktop: isDesktop, + ), + SizedBox(height: isDesktop ? 12 : 8), + _buildTravelerCounter( + label: "Children", + value: _children, + min: 0, + max: 20, + onChanged: (v) => setState(() => _children = v), + isDesktop: isDesktop, + ), + SizedBox(height: isDesktop ? 12 : 8), + _buildTravelerCounter( + label: "Infants", + value: _infants, + min: 0, + max: 20, + onChanged: (v) => setState(() => _infants = v), + isDesktop: isDesktop, + ), + SizedBox(height: isDesktop ? 12 : 8), + _buildTravelerCounter( + label: "Pets", + value: _pets, + min: 0, + max: 20, + onChanged: (v) => setState(() => _pets = v), + isDesktop: isDesktop, + ), + + // === Budget === + SizedBox(height: isDesktop ? 24 : 16), + Text( + "Budget", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.w500_14(context), + ), + SizedBox(height: isDesktop ? 12 : 8), + TextField( + controller: _travelBudgetController, + focusNode: _travelBudgetFocusNode, + autocorrect: false, + enableSuggestions: false, + keyboardType: TextInputType.number, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + onChanged: (_) => setState(() {}), + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: + standardInputDecoration( + "Minimum 1000 EUR", + _travelBudgetFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + filled: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + suffixText: "EUR", + errorText: travelBudgetError, + ), + ), + + // Travel doesn't need delivery country: destinations are in the form. + SizedBox(height: isDesktop ? 16 : 12), + _buildPrivacyCheckbox(isDesktop), + SizedBox(height: isDesktop ? 16 : 12), + _buildSubmitButton(), + ], + ); + } + + @override + Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + + final Widget content; + switch (widget.model.category) { + case ShopInBitCategory.concierge: + content = _buildConciergeContent(isDesktop); + break; + case ShopInBitCategory.car: + content = _buildCarContent(isDesktop); + break; + case ShopInBitCategory.travel: + content = _buildTravelContent(isDesktop); + break; + case null: + content = _buildGenericContent(isDesktop); + break; + } + + if (isDesktop) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 750, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + AppBarBackButton( + isCompact: true, + iconSize: 23, + onPressed: _popBack, + ), + Text("ShopinBit", style: STextStyles.desktopH3(context)), + ], + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), + child: SingleChildScrollView(child: content), + ), + ), + ], + ), + ); + } + + return Background( + child: PopScope( + canPop: false, + onPopInvokedWithResult: (bool didPop, dynamic result) { + if (!didPop) { + _popBack(); + } + }, + child: Scaffold( + backgroundColor: Theme.of( + context, + ).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton(onPressed: _popBack), + title: Text("ShopinBit", style: STextStyles.navBarTitle(context)), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.all(16), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 32, + ), + child: IntrinsicHeight(child: content), + ), + ), + ); + }, + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/shopinbit/shopinbit_ticket_detail.dart b/lib/pages/shopinbit/shopinbit_ticket_detail.dart new file mode 100644 index 0000000000..b59239f01b --- /dev/null +++ b/lib/pages/shopinbit/shopinbit_ticket_detail.dart @@ -0,0 +1,640 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/material.dart'; + +import '../../db/isar/main_db.dart'; +import '../../models/shopinbit/shopinbit_order_model.dart'; +import '../../notifications/show_flush_bar.dart'; +import '../../services/shopinbit/shopinbit_service.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../widgets/background.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/desktop/primary_button.dart'; +import '../../widgets/rounded_white_container.dart'; +import 'shopinbit_offer_view.dart'; + +class ShopInBitTicketDetail extends StatefulWidget { + const ShopInBitTicketDetail({super.key, required this.model}); + + static const String routeName = "/shopInBitTicketDetail"; + + final ShopInBitOrderModel model; + + @override + State createState() => _ShopInBitTicketDetailState(); +} + +class _ShopInBitTicketDetailState extends State { + late final TextEditingController _messageController; + + String _statusLabel(ShopInBitOrderStatus status) { + switch (status) { + case ShopInBitOrderStatus.pending: + return "Pending"; + case ShopInBitOrderStatus.reviewing: + return "Under review"; + case ShopInBitOrderStatus.offerAvailable: + return "Offer available"; + case ShopInBitOrderStatus.accepted: + return "Accepted"; + case ShopInBitOrderStatus.paymentPending: + return "Awaiting payment"; + case ShopInBitOrderStatus.paid: + return "Paid"; + case ShopInBitOrderStatus.shipping: + return "Shipping"; + case ShopInBitOrderStatus.delivered: + return "Delivered"; + case ShopInBitOrderStatus.closed: + return "Closed"; + case ShopInBitOrderStatus.cancelled: + return "Cancelled"; + case ShopInBitOrderStatus.refunded: + return "Refunded"; + } + } + + Color _statusColor(BuildContext context, ShopInBitOrderStatus status) { + switch (status) { + case ShopInBitOrderStatus.delivered: + return Theme.of(context).extension()!.accentColorGreen; + case ShopInBitOrderStatus.offerAvailable: + return Theme.of(context).extension()!.accentColorBlue; + case ShopInBitOrderStatus.pending: + case ShopInBitOrderStatus.reviewing: + return Theme.of(context).extension()!.accentColorYellow; + case ShopInBitOrderStatus.closed: + case ShopInBitOrderStatus.cancelled: + case ShopInBitOrderStatus.refunded: + return Theme.of(context).extension()!.textSubtitle1; + default: + return Theme.of(context).extension()!.accentColorDark; + } + } + + bool _sending = false; + bool _loading = false; + bool _retrying = false; + + @override + void initState() { + super.initState(); + _messageController = TextEditingController(); + if (widget.model.apiTicketId != 0) { + _loadFromApi(); + } + } + + @override + void dispose() { + _messageController.dispose(); + super.dispose(); + } + + bool get _isCarResearch => widget.model.category == ShopInBitCategory.car; + + Future _loadFromApi() async { + setState(() => _loading = true); + try { + final client = ShopInBitService.instance.client; + final id = widget.model.apiTicketId; + + // Car research tickets created via /car-research/log-payment are not + // accessible via /tickets/:id/* endpoints (API returns 403). Skip + // those calls for car tickets to avoid log spam. Local data is used. + if (!_isCarResearch) { + final messagesResp = await client.getMessages(id); + final statusResp = await client.getTicketStatus(id); + + if (!messagesResp.hasError && messagesResp.value != null) { + final apiMessages = messagesResp.value!; + widget.model.clearMessages(); + for (final m in apiMessages) { + widget.model.addMessage( + ShopInBitMessage( + text: m.content, + timestamp: m.timestamp, + isFromUser: !m.fromAgent, + ), + ); + } + } + + if (!statusResp.hasError && statusResp.value != null) { + widget.model.status = ShopInBitOrderModel.statusFromTicketState( + statusResp.value!.state, + ); + } + } + + unawaited( + MainDB.instance.putShopInBitTicket(widget.model.toIsarTicket()), + ); + } catch (_) { + // Silently fall back to local data + } finally { + if (mounted) setState(() => _loading = false); + } + } + + Future _sendMessage() async { + final text = _messageController.text.trim(); + if (text.isEmpty || _sending) return; + + setState(() => _sending = true); + _messageController.clear(); + + // Add optimistic local message + widget.model.addMessage( + ShopInBitMessage(text: text, timestamp: DateTime.now(), isFromUser: true), + ); + setState(() {}); + + try { + if (widget.model.apiTicketId != 0) { + await ShopInBitService.instance.client.sendMessage( + widget.model.apiTicketId, + text, + ); + // Reload messages from API to get accurate state + await _loadFromApi(); + } + unawaited( + MainDB.instance.putShopInBitTicket(widget.model.toIsarTicket()), + ); + } catch (_) { + // Keep optimistic local message + } finally { + if (mounted) setState(() => _sending = false); + } + } + + Future _retryCreateRequest() async { + if (_retrying) return; + setState(() => _retrying = true); + + try { + final model = widget.model; + final customerKey = await ShopInBitService.instance.ensureCustomerKey(); + final comment = + "${model.requestDescription}\n\n" + "The Client paid the car research fee (#${model.feeTicketNumber})"; + + final reqResp = await ShopInBitService.instance.client.createRequest( + customerPseudonym: model.displayName, + externalCustomerKey: customerKey, + serviceType: "car_research", + comment: comment, + deliveryCountry: model.deliveryCountry, + ); + + if (reqResp.hasError || reqResp.value == null) { + if (mounted) { + setState(() => _retrying = false); + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: reqResp.exception?.message ?? "Failed to create request", + context: context, + ), + ); + } + return; + } + + final requestRef = reqResp.value!; + final requestModel = ShopInBitOrderModel() + ..ticketId = requestRef.number + ..apiTicketId = requestRef.id + ..category = ShopInBitCategory.car + ..status = ShopInBitOrderStatus.pending + ..displayName = model.displayName + ..requestDescription = model.requestDescription + ..deliveryCountry = model.deliveryCountry; + await MainDB.instance.putShopInBitTicket(requestModel.toIsarTicket()); + + model.needsCreateRequest = false; + await MainDB.instance.putShopInBitTicket(model.toIsarTicket()); + + if (!mounted) return; + setState(() => _retrying = false); + + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "Car research request submitted successfully!", + context: context, + ), + ); + Navigator.of(context).pop(); + } catch (e) { + if (mounted) { + setState(() => _retrying = false); + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: e.toString(), + context: context, + ), + ); + } + } + } + + String _formatTime(DateTime dt) { + final hour = dt.hour.toString().padLeft(2, '0'); + final minute = dt.minute.toString().padLeft(2, '0'); + return "$hour:$minute"; + } + + static final _imgTagRegex = RegExp( + r']+src="data:image/[^;]+;base64,([^"]+)"[^>]*/?>', + caseSensitive: false, + ); + + List _buildMessageContent( + String html, + bool isDesktop, + Color? textColor, + ) { + final textStyle = + (isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context)) + .copyWith(color: textColor); + + final widgets = []; + var lastEnd = 0; + + for (final match in _imgTagRegex.allMatches(html)) { + // Add any text before this + if (match.start > lastEnd) { + final textChunk = html + .substring(lastEnd, match.start) + .replaceAll(RegExp(r''), '') + .replaceAll(RegExp(r''), '\n') + .replaceAll(RegExp(r'<[^>]*>'), '') + .trim(); + if (textChunk.isNotEmpty) { + widgets.add(Text(textChunk, style: textStyle)); + } + } + + // Decode and render the image + try { + final bytes = base64Decode(match.group(1)!); + widgets.add( + Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Image.memory(bytes), + ), + ); + } catch (_) { + // Skip malformed images + } + + lastEnd = match.end; + } + + // Add any remaining text after the last + if (lastEnd < html.length) { + final textChunk = html + .substring(lastEnd) + .replaceAll(RegExp(r''), '') + .replaceAll(RegExp(r''), '\n') + .replaceAll(RegExp(r'<[^>]*>'), '') + .trim(); + if (textChunk.isNotEmpty) { + widgets.add(Text(textChunk, style: textStyle)); + } + } + + if (widgets.isEmpty) { + widgets.add(Text('', style: textStyle)); + } + + return widgets; + } + + Widget _chatBubble(ShopInBitMessage message, bool isDesktop) { + final textColor = message.isFromUser ? Colors.white : null; + + return Align( + alignment: message.isFromUser + ? Alignment.centerRight + : Alignment.centerLeft, + child: Container( + constraints: BoxConstraints(maxWidth: isDesktop ? 380 : 260), + margin: const EdgeInsets.symmetric(vertical: 4), + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10), + decoration: BoxDecoration( + color: message.isFromUser + ? Theme.of(context).extension()!.accentColorBlue + : Theme.of(context).extension()!.popupBG, + borderRadius: BorderRadius.only( + topLeft: const Radius.circular(12), + topRight: const Radius.circular(12), + bottomLeft: message.isFromUser + ? const Radius.circular(12) + : Radius.zero, + bottomRight: message.isFromUser + ? Radius.zero + : const Radius.circular(12), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + if (message.isFromUser) + Text( + message.text, + style: + (isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context)) + .copyWith(color: textColor), + ) + else + ..._buildMessageContent(message.text, isDesktop, textColor), + const SizedBox(height: 4), + Text( + _formatTime(message.timestamp), + style: + (isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context)) + .copyWith( + fontSize: 10, + color: message.isFromUser + ? Colors.white.withOpacity(0.7) + : Theme.of(context) + .extension()! + .textSubtitle1 + .withOpacity(0.7), + ), + ), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + final model = widget.model; + + final statusBar = RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + model.ticketId ?? "Request", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: _statusColor(context, model.status).withOpacity(0.2), + ), + child: Text( + _statusLabel(model.status), + style: + (isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context)) + .copyWith(color: _statusColor(context, model.status)), + ), + ), + ], + ), + ); + + final offerBanner = model.status == ShopInBitOrderStatus.offerAvailable + ? Padding( + padding: EdgeInsets.only(bottom: isDesktop ? 16 : 12), + child: RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Offer available", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + const SizedBox(height: 4), + Text( + "${model.offerProductName ?? 'Item'} \u2014 " + "${model.offerPrice ?? '0'} EUR", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + SizedBox(height: isDesktop ? 12 : 8), + PrimaryButton( + label: "Review offer", + onPressed: () { + if (isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + showDialog( + context: context, + + builder: (_) => ShopInBitOfferView(model: model), + ); + } else { + Navigator.of(context).pushNamed( + ShopInBitOfferView.routeName, + arguments: model, + ); + } + }, + ), + ], + ), + ), + ) + : const SizedBox.shrink(); + + final chatArea = Expanded( + child: Stack( + children: [ + ListView.builder( + reverse: true, + padding: const EdgeInsets.symmetric(vertical: 8), + itemCount: model.messages.length, + itemBuilder: (context, index) { + final message = model.messages[model.messages.length - 1 - index]; + return _chatBubble(message, isDesktop); + }, + ), + if (_loading) + const Center( + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ), + ], + ), + ); + + final inputBar = Container( + padding: EdgeInsets.all(isDesktop ? 16 : 8), + decoration: BoxDecoration( + color: Theme.of(context).extension()!.popupBG, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + children: [ + Expanded( + child: TextField( + controller: _messageController, + style: + (isDesktop + ? STextStyles.desktopTextExtraSmall(context) + : STextStyles.field(context)) + .copyWith( + color: Theme.of( + context, + ).extension()!.textDark, + ), + decoration: InputDecoration( + hintText: "Type a message...", + hintStyle: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.fieldLabel(context), + border: InputBorder.none, + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + ), + onSubmitted: (_) => _sendMessage(), + ), + ), + IconButton( + onPressed: _sendMessage, + icon: Icon( + Icons.send, + color: Theme.of( + context, + ).extension()!.accentColorBlue, + ), + ), + ], + ), + ); + + final requestDetailsSection = + _isCarResearch && model.requestDescription.isNotEmpty + ? Padding( + padding: EdgeInsets.only(bottom: isDesktop ? 12 : 8), + child: RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Request details", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + const SizedBox(height: 8), + Text( + model.requestDescription, + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + ) + : const SizedBox.shrink(); + + final retryButton = + widget.model.needsCreateRequest && + widget.model.category == ShopInBitCategory.car + ? Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: PrimaryButton( + label: _retrying ? "Submitting..." : "Complete Request", + enabled: !_retrying, + onPressed: _retrying + ? null + : () => unawaited(_retryCreateRequest()), + ), + ) + : const SizedBox.shrink(); + + final body = Column( + children: [ + statusBar, + retryButton, + offerBanner, + requestDetailsSection, + chatArea, + SizedBox(height: isDesktop ? 12 : 8), + inputBar, + ], + ); + + if (isDesktop) { + return DesktopDialog( + maxWidth: 600, + maxHeight: 650, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text("Request", style: STextStyles.desktopH3(context)), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 8, + ), + child: body, + ), + ), + ], + ), + ); + } + + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () => Navigator.of(context).pop(), + ), + title: Text( + model.ticketId ?? "Request", + style: STextStyles.navBarTitle(context), + ), + ), + body: SafeArea( + child: Padding(padding: const EdgeInsets.all(16), child: body), + ), + ), + ); + } +} diff --git a/lib/pages/shopinbit/shopinbit_tickets_view.dart b/lib/pages/shopinbit/shopinbit_tickets_view.dart new file mode 100644 index 0000000000..220900a8f4 --- /dev/null +++ b/lib/pages/shopinbit/shopinbit_tickets_view.dart @@ -0,0 +1,505 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/material.dart'; + +import '../../db/isar/main_db.dart'; +import '../../models/isar/models/shopinbit_ticket.dart'; +import '../../models/shopinbit/shopinbit_order_model.dart'; +import '../../services/shopinbit/shopinbit_service.dart'; +import '../../services/shopinbit/src/models/car_research.dart'; +import '../../themes/stack_colors.dart'; +import '../../utilities/text_styles.dart'; +import '../../utilities/util.dart'; +import '../../widgets/background.dart'; +import '../../widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; +import '../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../widgets/rounded_white_container.dart'; +import 'shopinbit_car_fee_view.dart'; +import 'shopinbit_car_research_payment_view.dart'; +import 'shopinbit_ticket_detail.dart'; + +class ShopInBitTicketsView extends StatefulWidget { + const ShopInBitTicketsView({super.key}); + + static const String routeName = "/shopInBitTickets"; + + @override + State createState() => _ShopInBitTicketsViewState(); +} + +class _ShopInBitTicketsViewState extends State { + List _tickets = []; + bool _syncing = false; + ShopInBitTicket? _pendingTicket; + StreamSubscription? _isarSub; + + @override + void initState() { + super.initState(); + _loadLocal(); + _syncFromApi(); + // Refresh on ticket writes. + _isarSub = MainDB.instance.isar.shopInBitTickets.watchLazy().listen((_) { + if (mounted) setState(_loadLocal); + }); + } + + @override + void dispose() { + _isarSub?.cancel(); + super.dispose(); + } + + void _loadLocal() { + final allTickets = MainDB.instance.getShopInBitTickets(); + _pendingTicket = allTickets.where((t) => t.isPendingPayment).firstOrNull; + _tickets = allTickets + .where((t) => !t.isPendingPayment) + .map(ShopInBitOrderModel.fromIsarTicket) + .toList(); + } + + void _resumeFlow(ShopInBitTicket pending) { + final model = ShopInBitOrderModel.fromIsarTicket(pending); + final expiresAt = pending.carResearchExpiresAt; + final linksJson = pending.carResearchPaymentLinks; + final isDesktop = Util.isDesktop; + + if (expiresAt != null && + expiresAt.isAfter(DateTime.now()) && + linksJson != null) { + // Invoice still live: navigate directly to payment view. + final links = (jsonDecode(linksJson) as Map).map( + (k, v) => MapEntry(k, v as String), + ); + final invoice = CarResearchInvoice( + btcpayInvoice: pending.carResearchInvoiceId!, + expiresAt: expiresAt, + paymentLinks: links, + ); + if (isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + showDialog( + context: context, + builder: (_) => + ShopInBitCarResearchPaymentView(model: model, invoice: invoice), + ); + } else { + Navigator.of(context).pushNamed( + ShopInBitCarResearchPaymentView.routeName, + arguments: (model, invoice), + ); + } + } else { + // Invoice expired: navigate to fee view. + if (isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + showDialog( + context: context, + builder: (_) => ShopInBitCarFeeView(model: model), + ); + } else { + Navigator.of( + context, + ).pushNamed(ShopInBitCarFeeView.routeName, arguments: model); + } + } + } + + Future _syncFromApi() async { + setState(() => _syncing = true); + try { + final service = ShopInBitService.instance; + final customerKey = await service.ensureCustomerKey(); + final resp = await service.client.getTicketsByCustomer(customerKey); + + if (resp.hasError || resp.value == null) return; + + for (final ref in resp.value!) { + final localIdx = _tickets.indexWhere((t) => t.apiTicketId == ref.id); + if (localIdx < 0) continue; + + // Skip API calls for terminal tickets; they can still be + // refreshed on-demand when the user opens the detail view. + final localStatus = _tickets[localIdx].status; + if (localStatus == ShopInBitOrderStatus.closed || + localStatus == ShopInBitOrderStatus.cancelled || + localStatus == ShopInBitOrderStatus.refunded) { + continue; + } + + // Car research tickets return 403 on /tickets/:id/* endpoints. + if (_tickets[localIdx].category == ShopInBitCategory.car) continue; + + final statusResp = await service.client.getTicketStatus(ref.id); + if (statusResp.hasError || statusResp.value == null) continue; + + _tickets[localIdx].status = ShopInBitOrderModel.statusFromTicketState( + statusResp.value!.state, + ); + + final msgsResp = await service.client.getMessages(ref.id); + if (!msgsResp.hasError && msgsResp.value != null) { + _tickets[localIdx].clearMessages(); + for (final m in msgsResp.value!) { + _tickets[localIdx].addMessage( + ShopInBitMessage( + text: m.content, + timestamp: m.timestamp, + isFromUser: !m.fromAgent, + ), + ); + } + } + + await MainDB.instance.putShopInBitTicket( + _tickets[localIdx].toIsarTicket(), + ); + } + } catch (_) { + // Fall back to local data + } finally { + if (mounted) { + _loadLocal(); + setState(() => _syncing = false); + } + } + } + + String _statusLabel(ShopInBitOrderStatus status) { + switch (status) { + case ShopInBitOrderStatus.pending: + return "Pending"; + case ShopInBitOrderStatus.reviewing: + return "Under review"; + case ShopInBitOrderStatus.offerAvailable: + return "Offer available"; + case ShopInBitOrderStatus.accepted: + return "Accepted"; + case ShopInBitOrderStatus.paymentPending: + return "Awaiting payment"; + case ShopInBitOrderStatus.paid: + return "Paid"; + case ShopInBitOrderStatus.shipping: + return "Shipping"; + case ShopInBitOrderStatus.delivered: + return "Delivered"; + case ShopInBitOrderStatus.closed: + return "Closed"; + case ShopInBitOrderStatus.cancelled: + return "Cancelled"; + case ShopInBitOrderStatus.refunded: + return "Refunded"; + } + } + + Color _statusColor(BuildContext context, ShopInBitOrderStatus status) { + switch (status) { + case ShopInBitOrderStatus.delivered: + return Theme.of(context).extension()!.accentColorGreen; + case ShopInBitOrderStatus.offerAvailable: + return Theme.of(context).extension()!.accentColorBlue; + case ShopInBitOrderStatus.pending: + case ShopInBitOrderStatus.reviewing: + return Theme.of(context).extension()!.accentColorYellow; + case ShopInBitOrderStatus.closed: + case ShopInBitOrderStatus.cancelled: + case ShopInBitOrderStatus.refunded: + return Theme.of(context).extension()!.textSubtitle1; + default: + return Theme.of(context).extension()!.accentColorDark; + } + } + + String _categoryLabel(ShopInBitCategory? category) { + switch (category) { + case ShopInBitCategory.concierge: + return "Concierge"; + case ShopInBitCategory.travel: + return "Travel"; + case ShopInBitCategory.car: + return "Car"; + case null: + return ""; + } + } + + @override + Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + + final resumeCard = _pendingTicket != null + ? GestureDetector( + onTap: () => _resumeFlow(_pendingTicket!), + child: RoundedWhiteContainer( + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Car Research (In Progress)", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 2, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Theme.of(context) + .extension()! + .accentColorYellow + .withOpacity(0.2), + ), + child: Text( + "Resume", + style: + (isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle12( + context, + )) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorYellow, + ), + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + "Tap to continue your car research payment", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of( + context, + ).extension()!.textSubtitle1, + ), + ), + ], + ), + ), + SizedBox(width: isDesktop ? 16 : 8), + Icon( + Icons.chevron_right, + color: Theme.of( + context, + ).extension()!.textSubtitle1, + ), + ], + ), + ), + ) + : const SizedBox.shrink(); + + final ticketList = _tickets.isEmpty + ? null + : ListView.separated( + shrinkWrap: true, + itemCount: _tickets.length, + separatorBuilder: (_, __) => SizedBox(height: isDesktop ? 16 : 12), + itemBuilder: (context, index) { + final ticket = _tickets[index]; + return GestureDetector( + onTap: () { + if (isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + showDialog( + context: context, + builder: (_) => ShopInBitTicketDetail(model: ticket), + ); + } else { + Navigator.of(context).pushNamed( + ShopInBitTicketDetail.routeName, + arguments: ticket, + ); + } + }, + child: RoundedWhiteContainer( + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + ticket.ticketId ?? "N/A", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.titleBold12(context), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 2, + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: _statusColor( + context, + ticket.status, + ).withOpacity(0.2), + ), + child: Text( + _statusLabel(ticket.status), + style: + (isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle12( + context, + )) + .copyWith( + color: _statusColor( + context, + ticket.status, + ), + ), + ), + ), + ], + ), + const SizedBox(height: 4), + Text( + "${_categoryLabel(ticket.category)} \u2022 " + "${ticket.requestDescription}", + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context, + ) + : STextStyles.itemSubtitle12( + context, + ).copyWith( + color: Theme.of( + context, + ).extension()!.textSubtitle1, + ), + ), + ], + ), + ), + SizedBox(width: isDesktop ? 16 : 8), + Icon( + Icons.chevron_right, + color: Theme.of( + context, + ).extension()!.textSubtitle1, + ), + ], + ), + ), + ); + }, + ); + + final Widget list; + if (_pendingTicket == null && _tickets.isEmpty) { + list = Center( + child: Text( + _syncing ? "Loading requests..." : "No requests yet", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.itemSubtitle(context), + ), + ); + } else if (ticketList == null) { + list = resumeCard; + } else { + list = Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (_pendingTicket != null) ...[ + resumeCard, + SizedBox(height: isDesktop ? 16 : 12), + ], + ticketList, + ], + ); + } + + final content = Stack( + children: [ + list, + if (_syncing) + const Center( + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ), + ], + ); + + if (isDesktop) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 550, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "My requests", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), + child: content, + ), + ), + ], + ), + ); + } + + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () => Navigator.of(context).pop(), + ), + title: Text("My requests", style: STextStyles.navBarTitle(context)), + ), + body: SafeArea( + child: Padding(padding: const EdgeInsets.all(16), child: content), + ), + ), + ); + } +} diff --git a/lib/pages/wallet_view/wallet_view.dart b/lib/pages/wallet_view/wallet_view.dart index 74a129efd8..12affd42b9 100644 --- a/lib/pages/wallet_view/wallet_view.dart +++ b/lib/pages/wallet_view/wallet_view.dart @@ -109,6 +109,8 @@ import '../settings_views/wallet_settings_view/wallet_network_settings_view/wall import '../settings_views/wallet_settings_view/wallet_settings_view.dart'; import '../signing/signing_view.dart'; import '../spark_names/spark_names_home_view.dart'; +import '../more_view/gift_cards_view.dart'; +import '../more_view/services_view.dart'; import '../token_view/my_tokens_view.dart'; import 'sub_widgets/transactions_list.dart'; import 'sub_widgets/wallet_summary.dart'; @@ -1343,6 +1345,39 @@ class _WalletViewState extends ConsumerState { ); }, ), + if (!viewOnly) + WalletNavigationBarItemData( + label: "Services", + icon: SvgPicture.asset( + Assets.svg.solidSliders, + height: 20, + width: 20, + color: Theme.of( + context, + ).extension()!.bottomNavIconIcon, + ), + onTap: () { + Navigator.of( + context, + ).pushNamed(ServicesView.routeName); + }, + ), + WalletNavigationBarItemData( + label: "Gift cards", + icon: SvgPicture.asset( + Assets.svg.creditCard, + height: 20, + width: 20, + color: Theme.of( + context, + ).extension()!.bottomNavIconIcon, + ), + onTap: () { + Navigator.of( + context, + ).pushNamed(GiftCardsView.routeName); + }, + ), ], ), ), diff --git a/lib/pages_desktop_specific/desktop_home_view.dart b/lib/pages_desktop_specific/desktop_home_view.dart index d29aeb4bc9..d1cd371434 100644 --- a/lib/pages_desktop_specific/desktop_home_view.dart +++ b/lib/pages_desktop_specific/desktop_home_view.dart @@ -31,6 +31,7 @@ import 'address_book_view/desktop_address_book.dart'; import 'desktop_buy/desktop_buy_view.dart'; import 'desktop_exchange/desktop_exchange_view.dart'; import 'desktop_menu.dart'; +import 'more_view/sub_widgets/desktop_services_view.dart'; import 'my_stack_view/my_stack_view.dart'; import 'notifications/desktop_notifications_view.dart'; import 'password/desktop_unlock_app_dialog.dart'; @@ -59,10 +60,8 @@ class _DesktopHomeViewState extends ConsumerState { barrierDismissible: false, context: context, useSafeArea: false, - builder: - (context) => const Background( - child: Center(child: DesktopUnlockAppDialog()), - ), + builder: (context) => + const Background(child: Center(child: DesktopUnlockAppDialog())), ); } } @@ -135,6 +134,11 @@ class _DesktopHomeViewState extends ConsumerState { onGenerateRoute: RouteGenerator.generateRoute, initialRoute: DesktopBuyView.routeName, ), + DesktopMenuItemId.services: const Navigator( + key: Key("desktopServicesHomeKey"), + onGenerateRoute: RouteGenerator.generateRoute, + initialRoute: DesktopServicesView.routeName, + ), DesktopMenuItemId.notifications: const Navigator( key: Key("desktopNotificationsHomeKey"), onGenerateRoute: RouteGenerator.generateRoute, @@ -201,8 +205,9 @@ class _DesktopHomeViewState extends ConsumerState { if (ref.read(currentDesktopMenuItemProvider.state).state == DesktopMenuItemId.notifications && newKey != DesktopMenuItemId.notifications) { - final Set unreadNotificationIds = - ref.read(unreadNotificationsStateProvider.state).state; + final Set unreadNotificationIds = ref + .read(unreadNotificationsStateProvider.state) + .state; if (unreadNotificationIds.isNotEmpty) { final List> futures = []; @@ -244,12 +249,12 @@ class _DesktopHomeViewState extends ConsumerState { child: IndexedStack( index: ref - .watch(currentDesktopMenuItemProvider.state) - .state - .index > - 0 - ? 1 - : 0, + .watch(currentDesktopMenuItemProvider.state) + .state + .index > + 0 + ? 1 + : 0, children: [ myStackViewNav, contentViews[ref diff --git a/lib/pages_desktop_specific/desktop_menu.dart b/lib/pages_desktop_specific/desktop_menu.dart index c0cbf107f5..9835701692 100644 --- a/lib/pages_desktop_specific/desktop_menu.dart +++ b/lib/pages_desktop_specific/desktop_menu.dart @@ -29,6 +29,7 @@ enum DesktopMenuItemId { myStack, exchange, buy, + services, notifications, addressBook, settings, @@ -95,6 +96,7 @@ class _DesktopMenuState extends ConsumerState { DMIController(), DMIController(), DMIController(), + DMIController(), ]; torButtonController = DMIController(); @@ -217,6 +219,17 @@ class _DesktopMenuState extends ConsumerState { ), ], const SizedBox(height: 2), + DesktopMenuItem( + key: const ValueKey('services'), + duration: duration, + icon: const DesktopServicesIcon(), + label: "Services", + value: DesktopMenuItemId.services, + onChanged: updateSelectedMenuItem, + controller: controllers[3], + isExpandedInitially: !_isMinimized, + ), + const SizedBox(height: 2), DesktopMenuItem( key: const ValueKey('notifications'), duration: duration, @@ -224,7 +237,7 @@ class _DesktopMenuState extends ConsumerState { label: "Notifications", value: DesktopMenuItemId.notifications, onChanged: updateSelectedMenuItem, - controller: controllers[3], + controller: controllers[4], isExpandedInitially: !_isMinimized, ), const SizedBox(height: 2), @@ -235,7 +248,7 @@ class _DesktopMenuState extends ConsumerState { label: "Address Book", value: DesktopMenuItemId.addressBook, onChanged: updateSelectedMenuItem, - controller: controllers[4], + controller: controllers[5], isExpandedInitially: !_isMinimized, ), const SizedBox(height: 2), @@ -246,7 +259,7 @@ class _DesktopMenuState extends ConsumerState { label: "Settings", value: DesktopMenuItemId.settings, onChanged: updateSelectedMenuItem, - controller: controllers[5], + controller: controllers[6], isExpandedInitially: !_isMinimized, ), const SizedBox(height: 2), @@ -257,7 +270,7 @@ class _DesktopMenuState extends ConsumerState { label: "Support", value: DesktopMenuItemId.support, onChanged: updateSelectedMenuItem, - controller: controllers[6], + controller: controllers[7], isExpandedInitially: !_isMinimized, ), const SizedBox(height: 2), @@ -268,7 +281,7 @@ class _DesktopMenuState extends ConsumerState { label: "About", value: DesktopMenuItemId.about, onChanged: updateSelectedMenuItem, - controller: controllers[7], + controller: controllers[8], isExpandedInitially: !_isMinimized, ), const Spacer(), @@ -291,7 +304,7 @@ class _DesktopMenuState extends ConsumerState { // SystemNavigator.pop(); // } }, - controller: controllers[8], + controller: controllers[9], isExpandedInitially: !_isMinimized, ), ], diff --git a/lib/pages_desktop_specific/desktop_menu_item.dart b/lib/pages_desktop_specific/desktop_menu_item.dart index ea0d69a81c..3decfaec9b 100644 --- a/lib/pages_desktop_specific/desktop_menu_item.dart +++ b/lib/pages_desktop_specific/desktop_menu_item.dart @@ -41,13 +41,13 @@ class DesktopMyStackIcon extends ConsumerWidget { Assets.svg.walletDesktop, width: 20, height: 20, - color: DesktopMenuItemId.myStack == + color: + DesktopMenuItemId.myStack == ref.watch(currentDesktopMenuItemProvider.state).state ? Theme.of(context).extension()!.accentColorDark - : Theme.of(context) - .extension()! - .accentColorDark - .withOpacity(0.8), + : Theme.of( + context, + ).extension()!.accentColorDark.withOpacity(0.8), ); } } @@ -61,13 +61,13 @@ class DesktopExchangeIcon extends ConsumerWidget { Assets.svg.exchangeDesktop, width: 20, height: 20, - color: DesktopMenuItemId.exchange == + color: + DesktopMenuItemId.exchange == ref.watch(currentDesktopMenuItemProvider.state).state ? Theme.of(context).extension()!.accentColorDark - : Theme.of(context) - .extension()! - .accentColorDark - .withOpacity(0.8), + : Theme.of( + context, + ).extension()!.accentColorDark.withOpacity(0.8), ); } } @@ -81,13 +81,33 @@ class DesktopBuyIcon extends ConsumerWidget { File(ref.watch(themeAssetsProvider).buy), width: 20, height: 20, - color: DesktopMenuItemId.buy == + color: + DesktopMenuItemId.buy == ref.watch(currentDesktopMenuItemProvider.state).state ? Theme.of(context).extension()!.accentColorDark - : Theme.of(context) - .extension()! - .accentColorDark - .withOpacity(0.8), + : Theme.of( + context, + ).extension()!.accentColorDark.withOpacity(0.8), + ); + } +} + +class DesktopServicesIcon extends ConsumerWidget { + const DesktopServicesIcon({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return SvgPicture.asset( + Assets.svg.solidSliders, + width: 20, + height: 20, + color: + DesktopMenuItemId.services == + ref.watch(currentDesktopMenuItemProvider.state).state + ? Theme.of(context).extension()!.accentColorDark + : Theme.of( + context, + ).extension()!.accentColorDark.withOpacity(0.8), ); } } @@ -98,15 +118,11 @@ class DesktopNotificationsIcon extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { return ref.watch( - notificationsProvider.select((value) => value.hasUnreadNotifications), - ) + notificationsProvider.select((value) => value.hasUnreadNotifications), + ) ? SvgPicture.file( File( - ref.watch( - themeProvider.select( - (value) => value.assets.bellNew, - ), - ), + ref.watch(themeProvider.select((value) => value.assets.bellNew)), ), width: 20, height: 20, @@ -115,20 +131,19 @@ class DesktopNotificationsIcon extends ConsumerWidget { Assets.svg.bell, width: 20, height: 20, - color: ref.watch( - notificationsProvider - .select((value) => value.hasUnreadNotifications), - ) + color: + ref.watch( + notificationsProvider.select( + (value) => value.hasUnreadNotifications, + ), + ) ? null : DesktopMenuItemId.notifications == - ref.watch(currentDesktopMenuItemProvider.state).state - ? Theme.of(context) - .extension()! - .accentColorDark - : Theme.of(context) - .extension()! - .accentColorDark - .withOpacity(0.8), + ref.watch(currentDesktopMenuItemProvider.state).state + ? Theme.of(context).extension()!.accentColorDark + : Theme.of( + context, + ).extension()!.accentColorDark.withOpacity(0.8), ); } } @@ -142,13 +157,13 @@ class DesktopAddressBookIcon extends ConsumerWidget { Assets.svg.addressBookDesktop, width: 20, height: 20, - color: DesktopMenuItemId.addressBook == + color: + DesktopMenuItemId.addressBook == ref.watch(currentDesktopMenuItemProvider.state).state ? Theme.of(context).extension()!.accentColorDark - : Theme.of(context) - .extension()! - .accentColorDark - .withOpacity(0.8), + : Theme.of( + context, + ).extension()!.accentColorDark.withOpacity(0.8), ); } } @@ -162,13 +177,13 @@ class DesktopSettingsIcon extends ConsumerWidget { Assets.svg.gear, width: 20, height: 20, - color: DesktopMenuItemId.settings == + color: + DesktopMenuItemId.settings == ref.watch(currentDesktopMenuItemProvider.state).state ? Theme.of(context).extension()!.accentColorDark - : Theme.of(context) - .extension()! - .accentColorDark - .withOpacity(0.8), + : Theme.of( + context, + ).extension()!.accentColorDark.withOpacity(0.8), ); } } @@ -182,13 +197,13 @@ class DesktopSupportIcon extends ConsumerWidget { Assets.svg.messageQuestion, width: 20, height: 20, - color: DesktopMenuItemId.support == + color: + DesktopMenuItemId.support == ref.watch(currentDesktopMenuItemProvider.state).state ? Theme.of(context).extension()!.accentColorDark - : Theme.of(context) - .extension()! - .accentColorDark - .withOpacity(0.8), + : Theme.of( + context, + ).extension()!.accentColorDark.withOpacity(0.8), ); } } @@ -202,13 +217,13 @@ class DesktopAboutIcon extends ConsumerWidget { Assets.svg.aboutDesktop, width: 20, height: 20, - color: DesktopMenuItemId.about == + color: + DesktopMenuItemId.about == ref.watch(currentDesktopMenuItemProvider.state).state ? Theme.of(context).extension()!.accentColorDark - : Theme.of(context) - .extension()! - .accentColorDark - .withOpacity(0.8), + : Theme.of( + context, + ).extension()!.accentColorDark.withOpacity(0.8), ); } } @@ -222,10 +237,9 @@ class DesktopExitIcon extends ConsumerWidget { Assets.svg.exitDesktop, width: 20, height: 20, - color: Theme.of(context) - .extension()! - .accentColorDark - .withOpacity(0.8), + color: Theme.of( + context, + ).extension()!.accentColorDark.withOpacity(0.8), ); } } @@ -294,10 +308,7 @@ class _DesktopMenuItemState extends ConsumerState> _iconOnly = !widget.isExpandedInitially; controller?.toggle = toggle; - animationController = AnimationController( - vsync: this, - duration: duration, - ); + animationController = AnimationController(vsync: this, duration: duration); if (_iconOnly) { animationController.value = 0; } else { @@ -321,25 +332,20 @@ class _DesktopMenuItemState extends ConsumerState> return TextButton( style: value == group ? Theme.of(context) - .extension()! - .getDesktopMenuButtonStyleSelected(context) - : Theme.of(context) - .extension()! - .getDesktopMenuButtonStyle(context), + .extension()! + .getDesktopMenuButtonStyleSelected(context) + : Theme.of( + context, + ).extension()!.getDesktopMenuButtonStyle(context), onPressed: () { onChanged(value); }, child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 16, - ), + padding: const EdgeInsets.symmetric(vertical: 16), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - AnimatedContainer( - duration: duration, - width: _iconOnly ? 0 : 16, - ), + AnimatedContainer(duration: duration, width: _iconOnly ? 0 : 16), icon, AnimatedOpacity( duration: duration, @@ -352,9 +358,7 @@ class _DesktopMenuItemState extends ConsumerState> width: labelLength, child: Row( children: [ - const SizedBox( - width: 12, - ), + const SizedBox(width: 12), Text( label, style: value == group diff --git a/lib/pages_desktop_specific/more_view/sub_widgets/desktop_gift_cards_view.dart b/lib/pages_desktop_specific/more_view/sub_widgets/desktop_gift_cards_view.dart new file mode 100644 index 0000000000..8c328bd477 --- /dev/null +++ b/lib/pages_desktop_specific/more_view/sub_widgets/desktop_gift_cards_view.dart @@ -0,0 +1,142 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../../app_config.dart'; +import '../../../pages/cakepay/cakepay_orders_view.dart'; +import '../../../pages/cakepay/cakepay_vendors_view.dart'; +import '../../../services/event_bus/events/global/tor_connection_status_changed_event.dart'; +import '../../../services/tor_service.dart'; +import '../../../themes/stack_colors.dart'; +import '../../../utilities/assets.dart'; +import '../../../utilities/text_styles.dart'; +import '../../../widgets/desktop/primary_button.dart'; +import '../../../widgets/desktop/secondary_button.dart'; +import '../../../widgets/rounded_white_container.dart'; +import '../../../widgets/tor_subscription.dart'; + +class DesktopGiftCardsView extends ConsumerStatefulWidget { + const DesktopGiftCardsView({super.key}); + + static const String routeName = "/desktopGiftCardsView"; + + @override + ConsumerState createState() => + _DesktopGiftCardsViewState(); +} + +class _DesktopGiftCardsViewState extends ConsumerState { + late bool _torEnabled; + + @override + void initState() { + _torEnabled = AppConfig.hasFeature(AppFeature.tor) + ? ref.read(pTorService).status != TorConnectionStatus.disconnected + : false; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return TorSubscription( + onTorStatusChanged: (status) { + setState(() { + _torEnabled = status != TorConnectionStatus.disconnected; + }); + }, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(right: 30), + child: RoundedWhiteContainer( + radiusMultiplier: 2, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + Assets.svg.creditCard, + width: 48, + height: 48, + ), + ), + Padding( + padding: const EdgeInsets.all(10), + child: RichText( + textAlign: TextAlign.start, + text: TextSpan( + children: [ + TextSpan( + text: "CakePay", + style: STextStyles.desktopTextSmall(context), + ), + TextSpan( + text: + "\n\nPurchase gift cards with cryptocurrency.", + style: STextStyles.desktopTextExtraExtraSmall( + context, + ), + ), + ], + ), + ), + ), + if (_torEnabled) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Text( + "CakePay is not available while Tor is enabled", + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of( + context, + ).extension()!.textSubtitle1, + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 6, + ), + child: Row( + children: [ + Expanded( + child: PrimaryButton( + buttonHeight: ButtonHeight.m, + label: "Browse Gift Cards", + enabled: !_torEnabled, + onPressed: () { + showDialog( + context: context, + builder: (_) => const CakePayVendorsView(), + ); + }, + ), + ), + const SizedBox(width: 16), + Expanded( + child: SecondaryButton( + buttonHeight: ButtonHeight.m, + label: "My Orders", + onPressed: () { + showDialog( + context: context, + builder: (_) => const CakePayOrdersView(), + ); + }, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages_desktop_specific/more_view/sub_widgets/desktop_services_view.dart b/lib/pages_desktop_specific/more_view/sub_widgets/desktop_services_view.dart new file mode 100644 index 0000000000..dca35f54d9 --- /dev/null +++ b/lib/pages_desktop_specific/more_view/sub_widgets/desktop_services_view.dart @@ -0,0 +1,131 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../../route_generator.dart'; +import '../../../themes/stack_colors.dart'; +import '../../../utilities/assets.dart'; +import '../../../utilities/text_styles.dart'; +import '../../../widgets/desktop/desktop_app_bar.dart'; +import '../../../widgets/desktop/desktop_scaffold.dart'; +import '../../settings/settings_menu_item.dart'; +import 'desktop_gift_cards_view.dart'; +import 'desktop_shopinbit_view.dart'; + +final selectedServicesMenuItemStateProvider = StateProvider((_) => 0); + +class DesktopServicesView extends ConsumerStatefulWidget { + const DesktopServicesView({super.key}); + + static const String routeName = "/desktopServicesView"; + + @override + ConsumerState createState() => + _DesktopServicesViewState(); +} + +class _DesktopServicesViewState extends ConsumerState { + final List _labels = const ["Services", "Gift Cards"]; + + @override + Widget build(BuildContext context) { + final List contentViews = [ + const Navigator( + key: Key("servicesShopInBitDesktopKey"), + onGenerateRoute: RouteGenerator.generateRoute, + initialRoute: DesktopShopInBitView.routeName, + ), + const Navigator( + key: Key("servicesGiftCardsDesktopKey"), + onGenerateRoute: RouteGenerator.generateRoute, + initialRoute: DesktopGiftCardsView.routeName, + ), + ]; + + return DesktopScaffold( + background: Theme.of(context).extension()!.background, + appBar: DesktopAppBar( + isCompactHeight: true, + leading: Row( + children: [ + const SizedBox(width: 24, height: 24), + Text("Services", style: STextStyles.desktopH3(context)), + ], + ), + ), + body: Row( + children: [ + Padding( + padding: const EdgeInsets.all(15.0), + child: Align( + alignment: Alignment.topLeft, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 250, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + for (int i = 0; i < _labels.length; i++) + Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (i > 0) const SizedBox(height: 2), + SettingsMenuItem( + icon: SvgPicture.asset( + Assets.svg.polygon, + width: 11, + height: 11, + color: + ref + .watch( + selectedServicesMenuItemStateProvider + .state, + ) + .state == + i + ? Theme.of(context) + .extension()! + .accentColorBlue + : Colors.transparent, + ), + label: _labels[i], + value: i, + group: ref + .watch( + selectedServicesMenuItemStateProvider + .state, + ) + .state, + onChanged: (newValue) => + ref + .read( + selectedServicesMenuItemStateProvider + .state, + ) + .state = + newValue, + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + ), + Expanded( + child: + contentViews[ref + .watch(selectedServicesMenuItemStateProvider.state) + .state], + ), + ], + ), + ); + } +} diff --git a/lib/pages_desktop_specific/more_view/sub_widgets/desktop_shopinbit_view.dart b/lib/pages_desktop_specific/more_view/sub_widgets/desktop_shopinbit_view.dart new file mode 100644 index 0000000000..b30ba6b8a1 --- /dev/null +++ b/lib/pages_desktop_specific/more_view/sub_widgets/desktop_shopinbit_view.dart @@ -0,0 +1,489 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:url_launcher/url_launcher.dart'; + +import '../../../app_config.dart'; +import '../../../db/isar/main_db.dart'; +import '../../../models/shopinbit/shopinbit_order_model.dart'; +import '../../../notifications/show_flush_bar.dart'; +import '../../../pages/shopinbit/shopinbit_step_1.dart'; +import '../../../pages/shopinbit/shopinbit_tickets_view.dart'; +import '../../../providers/desktop/current_desktop_menu_item.dart'; +import '../../../services/shopinbit/shopinbit_service.dart'; +import '../../../themes/stack_colors.dart'; +import '../../../utilities/assets.dart'; +import '../../../utilities/text_styles.dart'; +import '../../../widgets/desktop/desktop_dialog.dart'; +import '../../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../../widgets/desktop/primary_button.dart'; +import '../../../widgets/desktop/secondary_button.dart'; +import '../../../widgets/rounded_white_container.dart'; +import '../../desktop_menu.dart'; +import '../../settings/settings_menu.dart'; + +class DesktopShopInBitView extends ConsumerStatefulWidget { + const DesktopShopInBitView({super.key}); + + static const String routeName = "/desktopShopInBitView"; + + @override + ConsumerState createState() => + _DesktopServicesViewState(); +} + +class _DesktopServicesViewState extends ConsumerState { + Future _showOpenBrowserWarning(BuildContext context, String url) async { + final uri = Uri.parse(url); + final shouldContinue = await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => DesktopDialog( + maxWidth: 550, + maxHeight: 250, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 20), + child: Column( + children: [ + Text("Attention", style: STextStyles.desktopH2(context)), + const SizedBox(height: 16), + Text( + "You are about to open " + "${uri.scheme}://${uri.host} " + "in your browser.", + style: STextStyles.desktopTextSmall(context), + ), + const SizedBox(height: 35), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 200, + buttonHeight: ButtonHeight.l, + label: "Cancel", + onPressed: () { + Navigator.of(context, rootNavigator: true).pop(false); + }, + ), + const SizedBox(width: 20), + PrimaryButton( + width: 200, + buttonHeight: ButtonHeight.l, + label: "Continue", + onPressed: () { + Navigator.of(context, rootNavigator: true).pop(true); + }, + ), + ], + ), + ], + ), + ), + ), + ); + return shouldContinue ?? false; + } + + void _showShopDialog(BuildContext context) async { + final service = ShopInBitService.instance; + final model = ShopInBitOrderModel(); + + if (!service.loadSetupComplete()) { + // First-time user: show setup. + final completed = await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => _ShopInBitDesktopSetupDialog(model: model), + ); + if (completed != true) return; // user cancelled + } else { + // Returning user: restore display name. + final savedName = service.loadDisplayName(); + if (savedName != null && savedName.isNotEmpty) { + model.displayName = savedName; + } + } + + // Show warning dialog. + if (!mounted) return; + showDialog( + context: context, + barrierDismissible: false, + builder: (dialogContext) => DesktopDialog( + maxWidth: 550, + maxHeight: 300, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("ShopinBit", style: STextStyles.desktopH2(dialogContext)), + const SizedBox(height: 16), + RichText( + text: TextSpan( + style: STextStyles.desktopTextSmall(dialogContext), + children: [ + const TextSpan( + text: + "Please note the following before proceeding:" + "\n\n\u2022 Minimum order amount: 1,000 EUR" + "\n\u2022 Service fee: 10% of the order total", + ), + ], + ), + ), + const Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 200, + buttonHeight: ButtonHeight.l, + label: "Cancel", + onPressed: () { + Navigator.of(dialogContext, rootNavigator: true).pop(); + }, + ), + const SizedBox(width: 20), + PrimaryButton( + width: 200, + buttonHeight: ButtonHeight.l, + label: "Continue", + onPressed: () async { + Navigator.of(dialogContext, rootNavigator: true).pop(); + await showDialog( + context: context, + barrierDismissible: false, + builder: (_) => ShopInBitStep1(model: model), + ); + if (mounted) setState(() {}); + }, + ), + ], + ), + ], + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.only(right: 30), + child: RoundedWhiteContainer( + radiusMultiplier: 2, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + Assets.svg.circleSliders, + width: 48, + height: 48, + ), + ), + Padding( + padding: const EdgeInsets.all(10), + child: RichText( + textAlign: TextAlign.start, + text: TextSpan( + style: STextStyles.desktopTextExtraExtraSmall(context), + children: [ + TextSpan( + text: "ShopinBit", + style: STextStyles.desktopTextSmall(context), + ), + const TextSpan( + text: + "\n\nTurn your crypto into Electronics, Flights, Hotel, " + "Cars or any other legal product or service... " + "ShopinBit is a concierge shopping service that helps " + "you 'live the good life with crypto'..." + "\n\n" + "Minimum order value of 1,000 EUR. " + "A 10% service fee applies to all orders.\n\n" + "By using ShopinBit, you agree to their ", + ), + TextSpan( + text: "Terms & Conditions", + style: STextStyles.richLink( + context, + ).copyWith(fontSize: 14), + recognizer: TapGestureRecognizer() + ..onTap = () async { + const url = + "https://api.shopinbit.com/static/policy/terms.html"; + final shouldOpen = await _showOpenBrowserWarning( + context, + url, + ); + if (shouldOpen) { + await launchUrl( + Uri.parse(url), + mode: LaunchMode.externalApplication, + ); + } + }, + ), + const TextSpan(text: " and "), + TextSpan( + text: "Privacy Policy", + style: STextStyles.richLink( + context, + ).copyWith(fontSize: 14), + recognizer: TapGestureRecognizer() + ..onTap = () async { + const url = + "https://api.shopinbit.com/static/policy/privacy.html"; + final shouldOpen = await _showOpenBrowserWarning( + context, + url, + ); + if (shouldOpen) { + await launchUrl( + Uri.parse(url), + mode: LaunchMode.externalApplication, + ); + } + }, + ), + const TextSpan(text: "."), + ], + ), + ), + ), + Padding( + padding: const EdgeInsets.all(10), + child: Row( + children: [ + PrimaryButton( + width: 250, + buttonHeight: ButtonHeight.m, + enabled: true, + label: "Shop with ShopinBit", + onPressed: () => _showShopDialog(context), + ), + const SizedBox(width: 16), + Builder( + builder: (context) { + final count = MainDB.instance + .getShopInBitTickets() + .length; + return SecondaryButton( + width: 200, + buttonHeight: ButtonHeight.m, + label: count > 0 + ? "My requests ($count)" + : "My requests", + onPressed: () async { + await showDialog( + context: context, + builder: (_) => const ShopInBitTicketsView(), + ); + if (mounted) setState(() {}); + }, + ); + }, + ), + const SizedBox(width: 16), + SecondaryButton( + width: 140, + buttonHeight: ButtonHeight.m, + label: "Settings", + onPressed: () { + // ShopInBit is the last settings menu item. + var idx = 8; + if (AppConfig.hasFeature(AppFeature.themeSelection)) { + idx++; + } + ref + .read( + selectedSettingsMenuItemStateProvider.state, + ) + .state = + idx; + ref.read(currentDesktopMenuItemProvider.state).state = + DesktopMenuItemId.settings; + }, + ), + ], + ), + ), + ], + ), + ), + ), + ], + ); + } +} + +class _ShopInBitDesktopSetupDialog extends StatefulWidget { + const _ShopInBitDesktopSetupDialog({required this.model}); + + final ShopInBitOrderModel model; + + @override + State<_ShopInBitDesktopSetupDialog> createState() => + _ShopInBitDesktopSetupDialogState(); +} + +class _ShopInBitDesktopSetupDialogState + extends State<_ShopInBitDesktopSetupDialog> { + late final Future _keyFuture; + late final TextEditingController _nameController; + late final FocusNode _nameFocusNode; + + bool get _canContinue => _nameController.text.trim().isNotEmpty; + + @override + void initState() { + super.initState(); + _keyFuture = ShopInBitService.instance.ensureCustomerKey(); + final existingName = ShopInBitService.instance.loadDisplayName(); + _nameController = TextEditingController(text: existingName ?? ''); + _nameFocusNode = FocusNode(); + + _nameFocusNode.addListener(() { + setState(() {}); + }); + } + + @override + void dispose() { + _nameController.dispose(); + _nameFocusNode.dispose(); + super.dispose(); + } + + Future _completeSetup() async { + final name = _nameController.text.trim(); + widget.model.displayName = name; + await ShopInBitService.instance.setDisplayName(name); + await ShopInBitService.instance.setSetupComplete(true); + if (mounted) { + Navigator.of(context, rootNavigator: true).pop(true); + } + } + + @override + Widget build(BuildContext context) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 500, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "ShopinBit Setup", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Your Customer Key", + style: STextStyles.desktopTextSmall( + context, + ).copyWith(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + Text( + "This is your ShopinBit customer key: save it " + "somewhere safe, you'll need it to recover " + "your ShopinBit account on a new device.", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + const SizedBox(height: 12), + FutureBuilder( + future: _keyFuture, + builder: (context, snapshot) { + if (snapshot.connectionState != ConnectionState.done) { + return const Center(child: CircularProgressIndicator()); + } + if (snapshot.hasError) { + return Text( + "Failed to generate key. Please try again.", + style: STextStyles.desktopTextSmall(context).copyWith( + color: Theme.of( + context, + ).extension()!.textError, + ), + ); + } + final key = snapshot.data!; + return RoundedWhiteContainer( + child: Row( + children: [ + Expanded( + child: SelectableText( + key, + style: STextStyles.desktopTextExtraExtraSmall( + context, + ), + ), + ), + IconButton( + icon: const Icon(Icons.copy, size: 20), + onPressed: () { + Clipboard.setData(ClipboardData(text: key)); + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard!", + context: context, + ); + }, + ), + ], + ), + ); + }, + ), + const SizedBox(height: 24), + Text( + "Display Name", + style: STextStyles.desktopTextSmall( + context, + ).copyWith(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + TextField( + controller: _nameController, + focusNode: _nameFocusNode, + onChanged: (_) => setState(() {}), + style: STextStyles.desktopTextSmall(context), + decoration: const InputDecoration(hintText: "Display name"), + ), + const Spacer(), + PrimaryButton( + label: "Complete Setup", + enabled: _canContinue, + onPressed: _canContinue ? _completeSetup : null, + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart index c38b33a61b..242e71e8d1 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart @@ -26,12 +26,10 @@ import '../../../../widgets/loading_indicator.dart'; import '../../../../widgets/stack_text_field.dart'; class DesktopAuthSend extends ConsumerStatefulWidget { - const DesktopAuthSend({ - super.key, - required this.coin, - }); + const DesktopAuthSend({super.key, required this.coin, this.tokenTicker}); final CryptoCurrency coin; + final String? tokenTicker; @override ConsumerState createState() => _DesktopAuthSendState(); @@ -59,12 +57,7 @@ class _DesktopAuthSendState extends ConsumerState { builder: (context) => const Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, - children: [ - LoadingIndicator( - width: 200, - height: 200, - ), - ], + children: [LoadingIndicator(width: 200, height: 200)], ), ), ); @@ -77,15 +70,8 @@ class _DesktopAuthSendState extends ConsumerState { if (mounted) { Navigator.of(context).pop(); - Navigator.of( - context, - rootNavigator: true, - ).pop(passwordIsValid); - await Future.delayed( - const Duration( - milliseconds: 100, - ), - ); + Navigator.of(context, rootNavigator: true).pop(passwordIsValid); + await Future.delayed(const Duration(milliseconds: 100)); } } finally { _lock = false; @@ -113,29 +99,17 @@ class _DesktopAuthSendState extends ConsumerState { return Column( mainAxisSize: MainAxisSize.min, children: [ - SvgPicture.asset( - Assets.svg.keys, - width: 100, - ), - const SizedBox( - height: 56, - ), + SvgPicture.asset(Assets.svg.keys, width: 100), + const SizedBox(height: 56), + Text("Confirm transaction", style: STextStyles.desktopH3(context)), + const SizedBox(height: 16), Text( - "Confirm transaction", - style: STextStyles.desktopH3(context), - ), - const SizedBox( - height: 16, - ), - Text( - "Enter your wallet password to send ${widget.coin.ticker.toUpperCase()}", + "Enter your wallet password to send ${widget.tokenTicker?.toUpperCase() ?? widget.coin.ticker.toUpperCase()}", style: STextStyles.desktopTextMedium(context).copyWith( color: Theme.of(context).extension()!.textDark3, ), ), - const SizedBox( - height: 24, - ), + const SizedBox(height: 24), ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -144,9 +118,7 @@ class _DesktopAuthSendState extends ConsumerState { key: const Key("desktopLoginPasswordFieldKey"), focusNode: passwordFocusNode, controller: passwordController, - style: STextStyles.desktopTextMedium(context).copyWith( - height: 2, - ), + style: STextStyles.desktopTextMedium(context).copyWith(height: 2), obscureText: hidePassword, enableSuggestions: false, autocorrect: false, @@ -156,45 +128,44 @@ class _DesktopAuthSendState extends ConsumerState { _confirmPressed(); } }, - decoration: standardInputDecoration( - "Enter password", - passwordFocusNode, - context, - ).copyWith( - suffixIcon: UnconstrainedBox( - child: SizedBox( - height: 70, - child: Row( - children: [ - const SizedBox( - width: 24, - ), - GestureDetector( - key: const Key( - "restoreFromFilePasswordFieldShowPasswordButtonKey", - ), - onTap: () async { - setState(() { - hidePassword = !hidePassword; - }); - }, - child: SvgPicture.asset( - hidePassword ? Assets.svg.eye : Assets.svg.eyeSlash, - color: Theme.of(context) - .extension()! - .textDark3, - width: 24, - height: 24, - ), + decoration: + standardInputDecoration( + "Enter password", + passwordFocusNode, + context, + ).copyWith( + suffixIcon: UnconstrainedBox( + child: SizedBox( + height: 70, + child: Row( + children: [ + const SizedBox(width: 24), + GestureDetector( + key: const Key( + "restoreFromFilePasswordFieldShowPasswordButtonKey", + ), + onTap: () async { + setState(() { + hidePassword = !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of( + context, + ).extension()!.textDark3, + width: 24, + height: 24, + ), + ), + const SizedBox(width: 12), + ], ), - const SizedBox( - width: 12, - ), - ], + ), ), ), - ), - ), onChanged: (newValue) { setState(() { _confirmEnabled = passwordController.text.isNotEmpty; @@ -202,9 +173,7 @@ class _DesktopAuthSendState extends ConsumerState { }, ), ), - const SizedBox( - height: 48, - ), + const SizedBox(height: 48), Row( children: [ Expanded( @@ -214,9 +183,7 @@ class _DesktopAuthSendState extends ConsumerState { onPressed: Navigator.of(context).pop, ), ), - const SizedBox( - width: 16, - ), + const SizedBox(width: 16), Expanded( child: PrimaryButton( enabled: _confirmEnabled, diff --git a/lib/pages_desktop_specific/ordinals/desktop_ordinal_details_view.dart b/lib/pages_desktop_specific/ordinals/desktop_ordinal_details_view.dart index f503d0bee3..600ebc9c52 100644 --- a/lib/pages_desktop_specific/ordinals/desktop_ordinal_details_view.dart +++ b/lib/pages_desktop_specific/ordinals/desktop_ordinal_details_view.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; @@ -11,10 +12,13 @@ import '../../models/isar/models/blockchain_data/utxo.dart'; import '../../models/isar/ordinal.dart'; import '../../networking/http.dart'; import '../../notifications/show_flush_bar.dart'; +import '../../pages/ordinals/widgets/dialogs.dart'; +import '../../pages/send_view/confirm_transaction_view.dart'; import '../../pages/wallet_view/transaction_views/transaction_details_view.dart'; import '../../providers/db/main_db_provider.dart'; import '../../providers/global/wallets_provider.dart'; import '../../services/tor_service.dart'; +import '../desktop_home_view.dart'; import '../../themes/stack_colors.dart'; import '../../utilities/amount/amount.dart'; import '../../utilities/amount/amount_formatter.dart'; @@ -23,10 +27,14 @@ import '../../utilities/constants.dart'; import '../../utilities/prefs.dart'; import '../../utilities/show_loading.dart'; import '../../utilities/text_styles.dart'; +import '../../wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart'; import '../../widgets/custom_buttons/app_bar_icon_button.dart'; import '../../widgets/desktop/desktop_app_bar.dart'; +import '../../widgets/desktop/desktop_dialog.dart'; import '../../widgets/desktop/desktop_scaffold.dart'; +import '../../widgets/desktop/primary_button.dart'; import '../../widgets/desktop/secondary_button.dart'; +import '../../widgets/ordinal_image.dart'; import '../../widgets/rounded_white_container.dart'; class DesktopOrdinalDetailsView extends ConsumerStatefulWidget { @@ -141,14 +149,7 @@ class _DesktopOrdinalDetailsViewState borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, ), - child: Image.network( - widget - .ordinal - .content, // Use the preview URL as the image source - fit: BoxFit.cover, - filterQuality: - FilterQuality.none, // Set the filter mode to nearest - ), + child: OrdinalImage(url: widget.ordinal.content), ), ), const SizedBox(width: 16), @@ -175,33 +176,140 @@ class _DesktopOrdinalDetailsViewState ), ), const SizedBox(width: 16), - // PrimaryButton( - // width: 150, - // label: "Send", - // icon: SvgPicture.asset( - // Assets.svg.send, - // width: 18, - // height: 18, - // color: Theme.of(context) - // .extension()! - // .buttonTextPrimary, - // ), - // buttonHeight: ButtonHeight.l, - // iconSpacing: 8, - // onPressed: () async { - // final response = await showDialog( - // context: context, - // builder: (_) => - // const SendOrdinalUnfreezeDialog(), - // ); - // if (response == "unfreeze") { - // // TODO: unfreeze and go to send ord screen - // } - // }, - // ), - // const SizedBox( - // width: 16, - // ), + PrimaryButton( + width: 150, + label: "Send", + icon: SvgPicture.asset( + Assets.svg.send, + width: 18, + height: 18, + color: Theme.of( + context, + ).extension()!.buttonTextPrimary, + ), + buttonHeight: ButtonHeight.l, + iconSpacing: 8, + onPressed: () async { + final utxo = widget.ordinal.getUTXO( + ref.read(mainDBProvider), + ); + if (utxo == null) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Could not find ordinal UTXO", + context: context, + ), + ); + return; + } + + if (utxo.isBlocked) { + final unfreezeResponse = + await showDialog( + context: context, + builder: (_) => + const SendOrdinalUnfreezeDialog(), + ); + if (unfreezeResponse != "unfreeze") return; + } + + if (!context.mounted) return; + + final address = await showDialog( + context: context, + builder: (_) => OrdinalRecipientAddressDialog( + inscriptionNumber: + widget.ordinal.inscriptionNumber, + ), + ); + if (address == null || address.isEmpty) return; + + final wallet = ref + .read(pWallets) + .getWallet(widget.walletId); + if (!wallet.cryptoCurrency.validateAddress( + address, + )) { + if (context.mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Invalid address", + context: context, + ), + ); + } + return; + } + + if (!context.mounted) return; + + final OrdinalsInterface? ordinalsWallet = + wallet is OrdinalsInterface ? wallet : null; + if (ordinalsWallet == null) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: + "Wallet does not support ordinals", + context: context, + ), + ); + return; + } + + bool didError = false; + final txData = await showLoading( + whileFuture: ordinalsWallet + .prepareOrdinalSend( + ordinalUtxo: utxo, + recipientAddress: address, + ), + context: context, + rootNavigator: true, + message: "Preparing transaction...", + onException: (e) { + didError = true; + String msg = e.toString(); + while (msg.isNotEmpty && + msg.startsWith("Exception:")) { + msg = msg.substring(10).trim(); + } + if (context.mounted) { + showFloatingFlushBar( + type: FlushBarType.warning, + message: msg, + context: context, + ); + } + }, + ); + + if (didError || + txData == null || + !context.mounted) { + return; + } + + await showDialog( + context: context, + builder: (context) => DesktopDialog( + maxHeight: + MediaQuery.of(context).size.height - 64, + maxWidth: 580, + child: ConfirmTransactionView( + walletId: widget.walletId, + txData: txData, + routeOnSuccessName: + DesktopHomeView.routeName, + onSuccess: () {}, + ), + ), + ); + }, + ), + const SizedBox(width: 16), SecondaryButton( width: 150, label: "Download", diff --git a/lib/pages_desktop_specific/settings/desktop_settings_view.dart b/lib/pages_desktop_specific/settings/desktop_settings_view.dart index d0747f7b63..4247186964 100644 --- a/lib/pages_desktop_specific/settings/desktop_settings_view.dart +++ b/lib/pages_desktop_specific/settings/desktop_settings_view.dart @@ -12,6 +12,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../app_config.dart'; +import '../../providers/providers.dart'; import '../../route_generator.dart'; import '../../themes/stack_colors.dart'; import '../../utilities/text_styles.dart'; @@ -25,6 +26,7 @@ import 'settings_menu/currency_settings/currency_settings.dart'; import 'settings_menu/language_settings/language_settings.dart'; import 'settings_menu/nodes_settings.dart'; import 'settings_menu/security_settings.dart'; +import 'settings_menu/shopinbit_settings.dart'; import 'settings_menu/syncing_preferences_settings.dart'; import 'settings_menu/tor_settings/tor_settings.dart'; @@ -39,69 +41,72 @@ class DesktopSettingsView extends ConsumerStatefulWidget { } class _DesktopSettingsViewState extends ConsumerState { - final List contentViews = [ - const Navigator( - key: Key("settingsBackupRestoreDesktopKey"), - onGenerateRoute: RouteGenerator.generateRoute, - initialRoute: BackupRestoreSettings.routeName, - ), //b+r - const Navigator( - key: Key("settingsSecurityDesktopKey"), - onGenerateRoute: RouteGenerator.generateRoute, - initialRoute: SecuritySettings.routeName, - ), //security - const Navigator( - key: Key("settingsCurrencyDesktopKey"), - onGenerateRoute: RouteGenerator.generateRoute, - initialRoute: CurrencySettings.routeName, - ), //currency - const Navigator( - key: Key("settingsLanguageDesktopKey"), - onGenerateRoute: RouteGenerator.generateRoute, - initialRoute: LanguageOptionSettings.routeName, - ), - const Navigator( - key: Key("settingsTorDesktopKey"), - onGenerateRoute: RouteGenerator.generateRoute, - initialRoute: TorSettings.routeName, - ), //tor - const Navigator( - key: Key("settingsNodesDesktopKey"), - onGenerateRoute: RouteGenerator.generateRoute, - initialRoute: NodesSettings.routeName, - ), //nodes - const Navigator( - key: Key("settingsSyncingPreferencesDesktopKey"), - onGenerateRoute: RouteGenerator.generateRoute, - initialRoute: SyncingPreferencesSettings.routeName, - ), //syncing prefs - if (AppConfig.hasFeature(AppFeature.themeSelection)) - const Navigator( - key: Key("settingsAppearanceDesktopKey"), - onGenerateRoute: RouteGenerator.generateRoute, - initialRoute: AppearanceOptionSettings.routeName, - ), //appearance - const Navigator( - key: Key("settingsAdvancedDesktopKey"), - onGenerateRoute: RouteGenerator.generateRoute, - initialRoute: AdvancedSettings.routeName, - ), //advanced - ]; - @override Widget build(BuildContext context) { + final familiarity = ref.watch( + prefsChangeNotifierProvider.select((v) => v.familiarity), + ); + + final List contentViews = [ + const Navigator( + key: Key("settingsBackupRestoreDesktopKey"), + onGenerateRoute: RouteGenerator.generateRoute, + initialRoute: BackupRestoreSettings.routeName, + ), //b+r + const Navigator( + key: Key("settingsSecurityDesktopKey"), + onGenerateRoute: RouteGenerator.generateRoute, + initialRoute: SecuritySettings.routeName, + ), //security + const Navigator( + key: Key("settingsCurrencyDesktopKey"), + onGenerateRoute: RouteGenerator.generateRoute, + initialRoute: CurrencySettings.routeName, + ), //currency + const Navigator( + key: Key("settingsLanguageDesktopKey"), + onGenerateRoute: RouteGenerator.generateRoute, + initialRoute: LanguageOptionSettings.routeName, + ), + const Navigator( + key: Key("settingsTorDesktopKey"), + onGenerateRoute: RouteGenerator.generateRoute, + initialRoute: TorSettings.routeName, + ), //tor + const Navigator( + key: Key("settingsNodesDesktopKey"), + onGenerateRoute: RouteGenerator.generateRoute, + initialRoute: NodesSettings.routeName, + ), //nodes + const Navigator( + key: Key("settingsSyncingPreferencesDesktopKey"), + onGenerateRoute: RouteGenerator.generateRoute, + initialRoute: SyncingPreferencesSettings.routeName, + ), //syncing prefs + if (AppConfig.hasFeature(AppFeature.themeSelection)) + const Navigator( + key: Key("settingsAppearanceDesktopKey"), + onGenerateRoute: RouteGenerator.generateRoute, + initialRoute: AppearanceOptionSettings.routeName, + ), //appearance + const Navigator( + key: Key("settingsAdvancedDesktopKey"), + onGenerateRoute: RouteGenerator.generateRoute, + initialRoute: AdvancedSettings.routeName, + ), //advanced + if (familiarity >= 6) + const Navigator( + key: Key("settingsShopInBitDesktopKey"), + onGenerateRoute: RouteGenerator.generateRoute, + initialRoute: ShopInBitDesktopSettings.routeName, + ), //shopinbit + ]; return DesktopScaffold( background: Theme.of(context).extension()!.background, appBar: const DesktopAppBar( isCompactHeight: true, leading: Row( - children: [ - SizedBox( - width: 24, - height: 24, - ), - DesktopSettingsTitle(), - ], + children: [SizedBox(width: 24, height: 24), DesktopSettingsTitle()], ), ), body: Row( @@ -110,14 +115,14 @@ class _DesktopSettingsViewState extends ConsumerState { padding: EdgeInsets.all(15.0), child: Align( alignment: Alignment.topLeft, - child: SingleChildScrollView( - child: SettingsMenu(), - ), + child: SingleChildScrollView(child: SettingsMenu()), ), ), Expanded( - child: contentViews[ - ref.watch(selectedSettingsMenuItemStateProvider.state).state], + child: + contentViews[ref + .watch(selectedSettingsMenuItemStateProvider.state) + .state], ), ], ), @@ -130,9 +135,6 @@ class DesktopSettingsTitle extends StatelessWidget { @override Widget build(BuildContext context) { - return Text( - "Settings", - style: STextStyles.desktopH3(context), - ); + return Text("Settings", style: STextStyles.desktopH3(context)); } } diff --git a/lib/pages_desktop_specific/settings/settings_menu.dart b/lib/pages_desktop_specific/settings/settings_menu.dart index 1ec12e5f65..a7f5129c1a 100644 --- a/lib/pages_desktop_specific/settings/settings_menu.dart +++ b/lib/pages_desktop_specific/settings/settings_menu.dart @@ -13,6 +13,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import '../../app_config.dart'; +import '../../providers/providers.dart'; import '../../themes/stack_colors.dart'; import '../../utilities/assets.dart'; import 'settings_menu_item.dart'; @@ -20,31 +21,34 @@ import 'settings_menu_item.dart'; final selectedSettingsMenuItemStateProvider = StateProvider((_) => 0); class SettingsMenu extends ConsumerStatefulWidget { - const SettingsMenu({ - super.key, - }); + const SettingsMenu({super.key}); @override ConsumerState createState() => _SettingsMenuState(); } class _SettingsMenuState extends ConsumerState { - final List labels = [ - "Backup and restore", - "Security", - "Currency", - "Language", - "Tor settings", - "Nodes", - "Syncing preferences", - if (AppConfig.hasFeature(AppFeature.themeSelection)) "Appearance", - "Advanced", - ]; - @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); + final familiarity = ref.watch( + prefsChangeNotifierProvider.select((v) => v.familiarity), + ); + + final List labels = [ + "Backup and restore", + "Security", + "Currency", + "Language", + "Tor settings", + "Nodes", + "Syncing preferences", + if (AppConfig.hasFeature(AppFeature.themeSelection)) "Appearance", + "Advanced", + if (familiarity >= 6) "ShopinBit", + ]; + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -57,25 +61,23 @@ class _SettingsMenuState extends ConsumerState { Column( mainAxisSize: MainAxisSize.min, children: [ - if (i > 0) - const SizedBox( - height: 2, - ), + if (i > 0) const SizedBox(height: 2), SettingsMenuItem( icon: SvgPicture.asset( Assets.svg.polygon, width: 11, height: 11, - color: ref + color: + ref .watch( selectedSettingsMenuItemStateProvider .state, ) .state == i - ? Theme.of(context) - .extension()! - .accentColorBlue + ? Theme.of( + context, + ).extension()!.accentColorBlue : Colors.transparent, ), label: labels[i], @@ -83,9 +85,13 @@ class _SettingsMenuState extends ConsumerState { group: ref .watch(selectedSettingsMenuItemStateProvider.state) .state, - onChanged: (newValue) => ref - .read(selectedSettingsMenuItemStateProvider.state) - .state = newValue, + onChanged: (newValue) => + ref + .read( + selectedSettingsMenuItemStateProvider.state, + ) + .state = + newValue, ), ], ), diff --git a/lib/pages_desktop_specific/settings/settings_menu/shopinbit_settings.dart b/lib/pages_desktop_specific/settings/settings_menu/shopinbit_settings.dart new file mode 100644 index 0000000000..146243c96e --- /dev/null +++ b/lib/pages_desktop_specific/settings/settings_menu/shopinbit_settings.dart @@ -0,0 +1,550 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../../notifications/show_flush_bar.dart'; +import '../../../services/shopinbit/shopinbit_service.dart'; +import '../../../themes/stack_colors.dart'; +import '../../../utilities/assets.dart'; +import '../../../utilities/constants.dart'; +import '../../../utilities/text_styles.dart'; +import '../../../widgets/desktop/desktop_dialog.dart'; +import '../../../widgets/desktop/desktop_dialog_close_button.dart'; +import '../../../widgets/desktop/primary_button.dart'; +import '../../../widgets/desktop/secondary_button.dart'; +import '../../../widgets/rounded_white_container.dart'; +import '../../../widgets/stack_text_field.dart'; + +class ShopInBitDesktopSettings extends ConsumerStatefulWidget { + const ShopInBitDesktopSettings({super.key}); + + static const String routeName = "/settingsMenuShopInBit"; + + @override + ConsumerState createState() => + _ShopInBitDesktopSettingsState(); +} + +class _ShopInBitDesktopSettingsState + extends ConsumerState { + final _manualKeyController = TextEditingController(); + final _manualKeyFocusNode = FocusNode(); + final _verifyKeyController = TextEditingController(); + final _verifyKeyFocusNode = FocusNode(); + late final TextEditingController _displayNameController; + late final FocusNode _displayNameFocusNode; + + String? _currentKey; + bool _loading = false; + bool _savingName = false; + + @override + void initState() { + super.initState(); + _currentKey = ShopInBitService.instance.loadCustomerKey(); + final savedName = ShopInBitService.instance.loadDisplayName(); + _displayNameController = TextEditingController(text: savedName ?? ''); + _displayNameFocusNode = FocusNode(); + } + + @override + void dispose() { + _manualKeyController.dispose(); + _manualKeyFocusNode.dispose(); + _verifyKeyController.dispose(); + _verifyKeyFocusNode.dispose(); + _displayNameController.dispose(); + _displayNameFocusNode.dispose(); + super.dispose(); + } + + Future _saveDisplayName() async { + final name = _displayNameController.text.trim(); + if (name.isEmpty) return; + setState(() => _savingName = true); + try { + await ShopInBitService.instance.setDisplayName(name); + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "Display name updated", + context: context, + ), + ); + } + } finally { + if (mounted) setState(() => _savingName = false); + } + } + + Future _generate() async { + if (_currentKey != null) { + final proceed = await _showChangeWarning(); + if (proceed != true) return; + } + + setState(() => _loading = true); + try { + final String key; + if (_currentKey != null) { + final resp = await ShopInBitService.instance.client.generateKey(); + key = resp.valueOrThrow; + await ShopInBitService.instance.setCustomerKey(key); + } else { + key = await ShopInBitService.instance.ensureCustomerKey(); + } + setState(() => _currentKey = key); + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "Customer key generated", + context: context, + ), + ); + } + } catch (e) { + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Failed to generate key: $e", + context: context, + ), + ); + } + } finally { + setState(() => _loading = false); + } + } + + Future _setManualKey() async { + final newKey = _manualKeyController.text.trim(); + if (newKey.isEmpty) return; + + if (_currentKey != null) { + final proceed = await _showChangeWarning(); + if (proceed != true) return; + } + + setState(() => _loading = true); + try { + await ShopInBitService.instance.setCustomerKey(newKey); + setState(() { + _currentKey = newKey; + _manualKeyController.clear(); + }); + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "Customer key set", + context: context, + ), + ); + } + } catch (e) { + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Failed to set key: $e", + context: context, + ), + ); + } + } finally { + setState(() => _loading = false); + } + } + + Future _showChangeWarning() async { + final result = await showDialog( + context: context, + barrierDismissible: true, + builder: (ctx) => DesktopDialog( + maxWidth: 550, + maxHeight: double.infinity, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.all(32), + child: Text( + "Save your current key", + style: STextStyles.desktopH3(ctx), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Padding( + padding: const EdgeInsets.only(left: 32, right: 32, bottom: 32), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Your current customer key is:", + style: STextStyles.desktopTextExtraExtraSmall(ctx), + ), + const SizedBox(height: 8), + RoundedWhiteContainer( + borderColor: Theme.of( + ctx, + ).extension()!.textSubtitle6, + child: SelectableText( + _currentKey!, + style: STextStyles.desktopTextSmall(ctx), + ), + ), + const SizedBox(height: 16), + Text( + "Changing your key will disconnect you from " + "existing ShopinBit requests. Make sure " + "you have saved your current key before " + "proceeding.", + style: STextStyles.desktopTextExtraExtraSmall(ctx), + ), + const SizedBox(height: 32), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: ButtonHeight.l, + onPressed: () => + Navigator.of(ctx, rootNavigator: true).pop(false), + ), + ), + const SizedBox(width: 16), + Expanded( + child: PrimaryButton( + label: "I saved my key", + buttonHeight: ButtonHeight.l, + onPressed: () => + Navigator.of(ctx, rootNavigator: true).pop(null), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ); + + if (result == false || !mounted) return false; + + return _showVerifyDialog(); + } + + Future _showVerifyDialog() async { + _verifyKeyController.clear(); + return showDialog( + context: context, + barrierDismissible: true, + builder: (ctx) { + return StatefulBuilder( + builder: (ctx, setDialogState) { + final matches = _verifyKeyController.text.trim() == _currentKey; + return DesktopDialog( + maxWidth: 550, + maxHeight: double.infinity, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.all(32), + child: Text( + "Verify your key", + style: STextStyles.desktopH3(ctx), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Enter your current customer key to " + "confirm you have saved it.", + style: STextStyles.desktopTextExtraExtraSmall(ctx), + ), + const SizedBox(height: 16), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + controller: _verifyKeyController, + focusNode: _verifyKeyFocusNode, + style: STextStyles.field(ctx), + decoration: standardInputDecoration( + "Enter current key", + _verifyKeyFocusNode, + ctx, + ), + onChanged: (_) => setDialogState(() {}), + ), + ), + const SizedBox(height: 32), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: ButtonHeight.l, + onPressed: () => Navigator.of( + ctx, + rootNavigator: true, + ).pop(false), + ), + ), + const SizedBox(width: 16), + Expanded( + child: PrimaryButton( + label: "Confirm", + buttonHeight: ButtonHeight.l, + enabled: matches, + onPressed: () => Navigator.of( + ctx, + rootNavigator: true, + ).pop(true), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ); + }, + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(right: 30), + child: RoundedWhiteContainer( + radiusMultiplier: 2, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + Assets.svg.key, + width: 48, + height: 48, + ), + ), + Padding( + padding: const EdgeInsets.all(10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Customer Key", + style: STextStyles.desktopTextSmall(context), + ), + const SizedBox(height: 16), + Text( + "Your customer key identifies you to ShopinBit. " + "Save it to restore access to your conversations " + "on another device. If you change it, you will " + "lose access to existing conversations.", + style: STextStyles.desktopTextExtraExtraSmall( + context, + ), + ), + const SizedBox(height: 20), + if (_currentKey != null) ...[ + Text( + "Current key", + style: + STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: Theme.of( + context, + ).extension()!.textDark3, + ), + ), + const SizedBox(height: 8), + Row( + children: [ + SelectableText( + _currentKey!, + style: STextStyles.desktopTextSmall(context), + ), + const SizedBox(width: 12), + GestureDetector( + onTap: () async { + await Clipboard.setData( + ClipboardData(text: _currentKey!), + ); + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Key copied to clipboard", + context: context, + ), + ); + } + }, + child: SvgPicture.asset( + Assets.svg.copy, + width: 20, + height: 20, + color: Theme.of( + context, + ).extension()!.textDark3, + ), + ), + ], + ), + const SizedBox(height: 20), + ] else + Padding( + padding: const EdgeInsets.only(bottom: 20), + child: Text( + "No key set", + style: STextStyles.desktopTextExtraExtraSmall( + context, + ), + ), + ), + PrimaryButton( + width: 210, + buttonHeight: ButtonHeight.m, + enabled: !_loading, + label: _currentKey == null + ? "Generate key" + : "Generate new key", + onPressed: _generate, + ), + const SizedBox(height: 20), + Text( + "Restore key", + style: STextStyles.desktopTextSmall(context), + ), + const SizedBox(height: 8), + Text( + "Enter a previously saved customer key to " + "restore access to your ShopinBit " + "conversations.", + style: STextStyles.desktopTextExtraExtraSmall( + context, + ), + ), + const SizedBox(height: 16), + SizedBox( + width: 512, + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + controller: _manualKeyController, + focusNode: _manualKeyFocusNode, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Enter customer key", + _manualKeyFocusNode, + context, + ), + onChanged: (_) => setState(() {}), + ), + ), + ), + const SizedBox(height: 16), + PrimaryButton( + width: 210, + buttonHeight: ButtonHeight.m, + enabled: + !_loading && + _manualKeyController.text.trim().isNotEmpty, + label: "Set key", + onPressed: _setManualKey, + ), + const SizedBox(height: 20), + Text( + "Display Name", + style: STextStyles.desktopTextSmall(context), + ), + const SizedBox(height: 8), + Text( + "The name ShopinBit staff will see " + "when communicating with you.", + style: STextStyles.desktopTextExtraExtraSmall( + context, + ), + ), + const SizedBox(height: 16), + SizedBox( + width: 512, + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + controller: _displayNameController, + focusNode: _displayNameFocusNode, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Display name", + _displayNameFocusNode, + context, + ), + onChanged: (_) => setState(() {}), + ), + ), + ), + const SizedBox(height: 16), + PrimaryButton( + width: 210, + buttonHeight: ButtonHeight.m, + enabled: + !_savingName && + _displayNameController.text.trim().isNotEmpty, + label: "Save", + onPressed: _saveDisplayName, + ), + ], + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/route_generator.dart b/lib/route_generator.dart index cad05cbcdb..83696c01af 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -29,6 +29,7 @@ import 'models/keys/key_data_interface.dart'; import 'models/keys/view_only_wallet_data.dart'; import 'models/paynym/paynym_account_lite.dart'; import 'models/send_view_auto_fill_data.dart'; +import 'models/shopinbit/shopinbit_order_model.dart'; import 'pages/add_wallet_views/add_token_view/add_custom_solana_token_view.dart'; import 'pages/add_wallet_views/add_token_view/add_custom_token_view.dart'; import 'pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart'; @@ -57,6 +58,12 @@ import 'pages/address_book_views/subviews/edit_contact_name_emoji_view.dart'; import 'pages/buy_view/buy_in_wallet_view.dart'; import 'pages/buy_view/buy_quote_preview.dart'; import 'pages/buy_view/buy_view.dart'; +import 'pages/cakepay/cakepay_card_detail_view.dart'; +import 'pages/cakepay/cakepay_confirm_send_view.dart'; +import 'pages/cakepay/cakepay_order_view.dart'; +import 'pages/cakepay/cakepay_orders_view.dart'; +import 'pages/cakepay/cakepay_send_from_view.dart'; +import 'pages/cakepay/cakepay_vendors_view.dart'; import 'pages/cashfusion/cashfusion_view.dart'; import 'pages/cashfusion/fusion_progress_view.dart'; import 'pages/churning/churning_progress_view.dart'; @@ -82,6 +89,15 @@ import 'pages/masternodes/create_masternode_view.dart'; import 'pages/masternodes/masternode_details_view.dart'; import 'pages/masternodes/masternodes_home_view.dart'; import 'pages/monkey/monkey_view.dart'; +import 'pages/cakepay/cakepay_card_detail_view.dart'; +import 'services/cakepay/src/models/card.dart'; +import 'pages/cakepay/cakepay_confirm_send_view.dart'; +import 'pages/cakepay/cakepay_order_view.dart'; +import 'pages/cakepay/cakepay_orders_view.dart'; +import 'pages/cakepay/cakepay_send_from_view.dart'; +import 'pages/cakepay/cakepay_vendors_view.dart'; +import 'pages/more_view/gift_cards_view.dart'; +import 'pages/more_view/services_view.dart'; import 'pages/namecoin_names/buy_domain_view.dart'; import 'pages/namecoin_names/confirm_name_transaction_view.dart'; import 'pages/namecoin_names/manage_domain_view.dart'; @@ -164,6 +180,21 @@ import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_setting import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/spark_view_key_view.dart'; import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart'; import 'pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/xpub_view.dart'; +import 'pages/shopinbit/shopinbit_car_fee_view.dart'; +import 'pages/shopinbit/shopinbit_car_research_payment_view.dart'; +import 'pages/shopinbit/shopinbit_offer_view.dart'; +import 'pages/shopinbit/shopinbit_order_created.dart'; +import 'pages/shopinbit/shopinbit_payment_view.dart'; +import 'pages/shopinbit/shopinbit_send_from_view.dart'; +import 'pages/shopinbit/shopinbit_settings_view.dart'; +import 'pages/shopinbit/shopinbit_setup_view.dart'; +import 'pages/shopinbit/shopinbit_shipping_view.dart'; +import 'pages/shopinbit/shopinbit_step_1.dart'; +import 'pages/shopinbit/shopinbit_step_2.dart'; +import 'pages/shopinbit/shopinbit_step_3.dart'; +import 'pages/shopinbit/shopinbit_step_4.dart'; +import 'pages/shopinbit/shopinbit_ticket_detail.dart'; +import 'pages/shopinbit/shopinbit_tickets_view.dart'; import 'pages/signing/signing_view.dart'; import 'pages/signing/sub_widgets/address_list.dart'; import 'pages/spark_names/buy_spark_name_view.dart'; @@ -199,6 +230,9 @@ import 'pages_desktop_specific/desktop_buy/desktop_buy_view.dart'; import 'pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart'; import 'pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart'; import 'pages_desktop_specific/desktop_home_view.dart'; +import 'pages_desktop_specific/more_view/sub_widgets/desktop_gift_cards_view.dart'; +import 'pages_desktop_specific/more_view/sub_widgets/desktop_services_view.dart'; +import 'pages_desktop_specific/more_view/sub_widgets/desktop_shopinbit_view.dart'; import 'pages_desktop_specific/mweb_utxos_view.dart'; import 'pages_desktop_specific/my_stack_view/my_stack_view.dart'; import 'pages_desktop_specific/my_stack_view/wallet_view/desktop_sol_token_view.dart'; @@ -227,11 +261,14 @@ import 'pages_desktop_specific/settings/settings_menu/desktop_support_view.dart' import 'pages_desktop_specific/settings/settings_menu/language_settings/language_settings.dart'; import 'pages_desktop_specific/settings/settings_menu/nodes_settings.dart'; import 'pages_desktop_specific/settings/settings_menu/security_settings.dart'; +import 'pages_desktop_specific/settings/settings_menu/shopinbit_settings.dart'; import 'pages_desktop_specific/settings/settings_menu/syncing_preferences_settings.dart'; import 'pages_desktop_specific/settings/settings_menu/tor_settings/tor_settings.dart'; import 'pages_desktop_specific/spark_coins/spark_coins_view.dart'; +import 'services/cakepay/src/models/card.dart'; import 'services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +import 'services/shopinbit/src/models/car_research.dart'; import 'utilities/amount/amount.dart'; import 'utilities/enums/add_wallet_type_enum.dart'; import 'wallets/crypto_currency/crypto_currency.dart'; @@ -1029,6 +1066,225 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case ServicesView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const ServicesView(), + settings: RouteSettings(name: settings.name), + ); + + case GiftCardsView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const GiftCardsView(), + settings: RouteSettings(name: settings.name), + ); + + case ShopInBitSetupView.routeName: + if (args is ShopInBitOrderModel) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ShopInBitSetupView(model: args), + settings: RouteSettings(name: settings.name), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case CakePayVendorsView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const CakePayVendorsView(), + settings: RouteSettings(name: settings.name), + ); + + case CakePayCardDetailView.routeName: + if (args is CakePayCard) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => CakePayCardDetailView(card: args), + settings: RouteSettings(name: settings.name), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case CakePayOrderView.routeName: + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => CakePayOrderView(orderId: args), + settings: RouteSettings(name: settings.name), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case CakePayOrdersView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const CakePayOrdersView(), + settings: RouteSettings(name: settings.name), + ); + + case CakePaySendFromView.routeName: + if (args is Map) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => CakePaySendFromView( + address: args['address'] as String, + orderId: args['orderId'] as String, + coin: args['coin'] as CryptoCurrency?, + amount: args['amount'] as Amount?, + ), + settings: RouteSettings(name: settings.name), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case CakePayConfirmSendView.routeName: + return _routeError("${settings.name} should be pushed directly"); + + case ShopInBitStep1.routeName: + if (args is ShopInBitOrderModel) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ShopInBitStep1(model: args), + settings: RouteSettings(name: settings.name), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case ShopInBitStep2.routeName: + if (args is ShopInBitOrderModel) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ShopInBitStep2(model: args), + settings: RouteSettings(name: settings.name), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case ShopInBitStep3.routeName: + if (args is ShopInBitOrderModel) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ShopInBitStep3(model: args), + settings: RouteSettings(name: settings.name), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case ShopInBitStep4.routeName: + if (args is ShopInBitOrderModel) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ShopInBitStep4(model: args), + settings: RouteSettings(name: settings.name), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case ShopInBitOrderCreated.routeName: + if (args is ShopInBitOrderModel) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ShopInBitOrderCreated(model: args), + settings: RouteSettings(name: settings.name), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case ShopInBitTicketsView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const ShopInBitTicketsView(), + settings: RouteSettings(name: settings.name), + ); + + case ShopInBitSettingsView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const ShopInBitSettingsView(), + settings: RouteSettings(name: settings.name), + ); + + case ShopInBitTicketDetail.routeName: + if (args is ShopInBitOrderModel) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ShopInBitTicketDetail(model: args), + settings: RouteSettings(name: settings.name), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case ShopInBitOfferView.routeName: + if (args is ShopInBitOrderModel) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ShopInBitOfferView(model: args), + settings: RouteSettings(name: settings.name), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case ShopInBitShippingView.routeName: + if (args is ShopInBitOrderModel) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ShopInBitShippingView(model: args), + settings: RouteSettings(name: settings.name), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case ShopInBitCarFeeView.routeName: + if (args is ShopInBitOrderModel) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ShopInBitCarFeeView(model: args), + settings: RouteSettings(name: settings.name), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case ShopInBitCarResearchPaymentView.routeName: + if (args is (ShopInBitOrderModel, CarResearchInvoice)) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ShopInBitCarResearchPaymentView( + model: args.$1, + invoice: args.$2, + ), + settings: RouteSettings(name: settings.name), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case ShopInBitPaymentView.routeName: + if (args is ShopInBitOrderModel) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ShopInBitPaymentView(model: args), + settings: RouteSettings(name: settings.name), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case ShopInBitSendFromView.routeName: + if (args + is Tuple4) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ShopInBitSendFromView( + coin: args.item1, + amount: args.item2, + address: args.item3, + model: args.item4, + ), + settings: RouteSettings(name: settings.name), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case GlobalSettingsView.routeName: return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, @@ -2332,6 +2588,27 @@ class RouteGenerator { settings: RouteSettings(name: settings.name), ); + case DesktopServicesView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const DesktopServicesView(), + settings: RouteSettings(name: settings.name), + ); + + case DesktopShopInBitView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const DesktopShopInBitView(), + settings: RouteSettings(name: settings.name), + ); + + case DesktopGiftCardsView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const DesktopGiftCardsView(), + settings: RouteSettings(name: settings.name), + ); + case MyStackView.routeName: return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, @@ -2462,6 +2739,13 @@ class RouteGenerator { settings: RouteSettings(name: settings.name), ); + case ShopInBitDesktopSettings.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const ShopInBitDesktopSettings(), + settings: RouteSettings(name: settings.name), + ); + case DesktopSupportView.routeName: return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, diff --git a/lib/services/cakepay/cakepay_api.dart b/lib/services/cakepay/cakepay_api.dart new file mode 100644 index 0000000000..c3af35a833 --- /dev/null +++ b/lib/services/cakepay/cakepay_api.dart @@ -0,0 +1,10 @@ +export 'src/client.dart'; +export 'src/api_response.dart'; +export 'src/api_exception.dart'; +export 'src/endpoints.dart'; +export 'src/models/vendor.dart'; +export 'src/models/card.dart'; +export 'src/models/country.dart'; +export 'src/models/order.dart'; +export 'src/models/order_item.dart'; +export 'src/models/category.dart'; diff --git a/lib/services/cakepay/cakepay_service.dart b/lib/services/cakepay/cakepay_service.dart new file mode 100644 index 0000000000..1016bc4b77 --- /dev/null +++ b/lib/services/cakepay/cakepay_service.dart @@ -0,0 +1,51 @@ +import '../../db/hive/db.dart'; +import '../../external_api_keys.dart'; +import 'src/client.dart'; +import 'src/models/order.dart'; + +class CakePayService { + static final instance = CakePayService._(); + CakePayService._(); + + /// Dev-only: override order statuses for local UI testing. + /// Keys are order IDs, values are the status to pretend the API returned. + static final Map devStatusOverrides = {}; + + CakePayClient? _client; + + CakePayClient get client { + return _client ??= CakePayClient(apiToken: kCakePayApiToken); + } + + // Mirrors ShopInBit's local ticket storage pattern but uses lightweight + // Hive prefs instead of a full Isar collection, since CakePay orders can + // be fetched individually via getOrder() with the seller key. + + static const _kCakePayOrderIds = "cakePayOrderIds"; + + /// Persist a newly-created order ID so the orders list view can find it + /// later without requiring Knox user auth. + void addOrderId(String orderId) { + final ids = getOrderIds(); + if (!ids.contains(orderId)) { + ids.insert(0, orderId); + DB.instance.put( + boxName: DB.boxNamePrefs, + key: _kCakePayOrderIds, + value: ids, + ); + } + } + + /// Return locally-tracked order IDs (most recent first). + List getOrderIds() { + final raw = DB.instance.get( + boxName: DB.boxNamePrefs, + key: _kCakePayOrderIds, + ); + if (raw is List) { + return raw.cast().toList(); + } + return []; + } +} diff --git a/lib/services/cakepay/src/api_exception.dart b/lib/services/cakepay/src/api_exception.dart new file mode 100644 index 0000000000..6e35192572 --- /dev/null +++ b/lib/services/cakepay/src/api_exception.dart @@ -0,0 +1,24 @@ +class ApiException implements Exception { + final String message; + final int? statusCode; + final String? responseBody; + + ApiException(this.message, {this.statusCode, this.responseBody}); + + factory ApiException.fromResponse(int statusCode, String body) { + return ApiException( + 'HTTP $statusCode', + statusCode: statusCode, + responseBody: body, + ); + } + + factory ApiException.network(Object error) { + return ApiException('Network error: $error'); + } + + @override + String toString() => + 'ApiException: $message' + '${statusCode != null ? ' (status: $statusCode)' : ''}'; +} diff --git a/lib/services/cakepay/src/api_response.dart b/lib/services/cakepay/src/api_response.dart new file mode 100644 index 0000000000..27fd26d3e4 --- /dev/null +++ b/lib/services/cakepay/src/api_response.dart @@ -0,0 +1,19 @@ +import 'api_exception.dart'; + +class ApiResponse { + final T? value; + final ApiException? exception; + + ApiResponse({this.value, this.exception}); + + bool get hasError => exception != null; + + T get valueOrThrow { + if (exception != null) throw exception!; + if (value == null) throw ApiException('Response has no value'); + return value as T; + } + + @override + String toString() => '{error: $exception, value: $value}'; +} diff --git a/lib/services/cakepay/src/client.dart b/lib/services/cakepay/src/client.dart new file mode 100644 index 0000000000..daaf7f0440 --- /dev/null +++ b/lib/services/cakepay/src/client.dart @@ -0,0 +1,567 @@ +import 'dart:convert'; +import 'dart:io'; + +import '../../../app_config.dart'; +import '../../../networking/http.dart'; +import '../../../utilities/logger.dart'; +import '../../../utilities/prefs.dart'; +import '../../tor_service.dart'; +import 'api_exception.dart'; +import 'api_response.dart'; +import 'endpoints.dart'; +import 'models/card.dart'; +import 'models/country.dart'; +import 'models/order.dart'; +import 'models/vendor.dart'; + +const _kTag = "CakePayClient"; + +class CakePayClient { + final String baseUrl; + final String apiToken; + final HTTP _httpClient; + + CakePayClient({ + this.baseUrl = Endpoints.base, + required this.apiToken, + HTTP? httpClient, + }) : _httpClient = httpClient ?? const HTTP(); + + Map _headers() => { + 'Authorization': 'Bearer $apiToken', + 'Content-Type': 'application/json', + }; + + ({InternetAddress host, int port})? get _proxyInfo => + !AppConfig.hasFeature(AppFeature.tor) + ? null + : Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null; + + // -- Marketplace -- + + Future>> getVendors({ + String? country, + String? countryCode, + String? search, + int? page, + int? pageSize, + bool? all, + bool? giftCards, + bool? prepaidCards, + bool? onDemand, + bool? custom, + }) async { + final query = {}; + if (country != null) query['country'] = country; + if (countryCode != null) query['country_code'] = countryCode; + if (search != null) query['search'] = search; + if (page != null) query['page'] = page.toString(); + if (pageSize != null) query['page_size'] = pageSize.toString(); + if (all != null) query['all'] = all.toString(); + if (giftCards != null) query['gift_cards'] = giftCards.toString(); + if (prepaidCards != null) query['prepaid_cards'] = prepaidCards.toString(); + if (onDemand != null) query['on_demand'] = onDemand.toString(); + if (custom != null) query['custom'] = custom.toString(); + + return _requestRaw( + 'GET', + '/marketplace/vendors/', + query: query, + parse: (body) { + final decoded = jsonDecode(body); + if (decoded is List) { + return decoded + .whereType>() + .map(CakePayVendor.fromJson) + .toList(); + } + if (decoded is Map) { + final results = decoded['results']; + if (results is List) { + return results + .whereType>() + .map(CakePayVendor.fromJson) + .toList(); + } + } + return []; + }, + ); + } + + Future> getCard(int id) async { + return _request( + 'GET', + '/marketplace/cards/$id/', + parse: CakePayCard.fromJson, + ); + } + + Future>> searchCards({ + String? query, + String? category, + String? country, + double? minPrice, + double? maxPrice, + bool? availableOnly, + int? page, + }) async { + final params = {}; + if (query != null) params['query'] = query; + if (category != null) params['category'] = category; + if (country != null) params['country'] = country; + if (minPrice != null) params['min_price'] = minPrice.toString(); + if (maxPrice != null) params['max_price'] = maxPrice.toString(); + if (availableOnly != null) { + params['available_only'] = availableOnly.toString(); + } + if (page != null) params['page'] = page.toString(); + + return _requestRaw( + 'GET', + '/marketplace/cards/search/', + query: params, + parse: (body) { + final decoded = jsonDecode(body); + if (decoded is List) { + return decoded + .whereType>() + .map(CakePayCard.fromJson) + .toList(); + } + if (decoded is Map) { + final results = decoded['results']; + if (results is List) { + return results + .whereType>() + .map(CakePayCard.fromJson) + .toList(); + } + } + return []; + }, + ); + } + + Future>> getFeaturedCards({int? page}) async { + final query = {}; + if (page != null) query['page'] = page.toString(); + + return _requestRaw( + 'GET', + '/marketplace/cards/featured/', + query: query, + parse: (body) { + final decoded = jsonDecode(body); + if (decoded is List) { + return decoded + .whereType>() + .map(CakePayCard.fromJson) + .toList(); + } + if (decoded is Map) { + final results = decoded['results']; + if (results is List) { + return results + .whereType>() + .map(CakePayCard.fromJson) + .toList(); + } + } + return []; + }, + ); + } + + Future>> getCountries({ + int? page, + int? pageSize, + }) async { + final query = {}; + if (page != null) query['page'] = page.toString(); + if (pageSize != null) query['page_size'] = pageSize.toString(); + + return _requestRaw( + 'GET', + '/marketplace/countries/', + query: query, + parse: (body) { + final decoded = jsonDecode(body); + if (decoded is List) { + return decoded + .whereType>() + .map(CakePayCountry.fromJson) + .toList(); + } + if (decoded is Map) { + final results = decoded['results']; + if (results is List) { + return results + .whereType>() + .map(CakePayCountry.fromJson) + .toList(); + } + } + return []; + }, + ); + } + + /// Fetches all countries by following pagination til last page. + Future>> getAllCountries({ + int pageSize = 250, + }) async { + try { + final allCountries = []; + int page = 1; + + while (true) { + final response = await _send( + 'GET', + '/marketplace/countries/', + query: {'page': page.toString(), 'page_size': pageSize.toString()}, + ); + + if (response.code < 200 || response.code >= 300) { + Logging.instance.w( + "$_kTag GET /marketplace/countries/ HTTP:${response.code} " + "body: ${response.body}", + ); + return ApiResponse( + exception: ApiException.fromResponse(response.code, response.body), + ); + } + + final decoded = jsonDecode(response.body); + + // Handle non-paginated response (plain list). + if (decoded is List) { + return ApiResponse( + value: decoded + .whereType>() + .map(CakePayCountry.fromJson) + .toList(), + ); + } + + if (decoded is Map) { + final results = decoded['results']; + if (results is List) { + allCountries.addAll( + results.whereType>().map( + CakePayCountry.fromJson, + ), + ); + } + + // If there is no next page we're done. + if (decoded['next'] == null) break; + } else { + break; + } + + page++; + } + + return ApiResponse(value: allCountries); + } on ApiException catch (e) { + Logging.instance.e("$_kTag getAllCountries threw: ", error: e); + return ApiResponse(exception: e); + } catch (e, s) { + Logging.instance.e( + "$_kTag getAllCountries threw: ", + error: e, + stackTrace: s, + ); + return ApiResponse(exception: ApiException.network(e)); + } + } + + /// List cards from the marketplace with optional pagination. + Future>> getCards({ + int? page, + int? pageSize, + }) async { + final query = {}; + if (page != null) query['page'] = page.toString(); + if (pageSize != null) query['page_size'] = pageSize.toString(); + + return _requestRaw( + 'GET', + '/marketplace/cards/', + query: query, + parse: (body) { + final decoded = jsonDecode(body); + if (decoded is List) { + return decoded + .whereType>() + .map(CakePayCard.fromJson) + .toList(); + } + if (decoded is Map) { + final results = decoded['results']; + if (results is List) { + return results + .whereType>() + .map(CakePayCard.fromJson) + .toList(); + } + } + return []; + }, + ); + } + + /// Fetches the list of marketplace providers. + /// + /// Endpoint: GET `/marketplace/providers/` + Future>>> getProviders() async { + return _requestRaw( + 'GET', + '/marketplace/providers/', + parse: (body) { + final decoded = jsonDecode(body); + if (decoded is List) { + return decoded.whereType>().toList(); + } + if (decoded is Map) { + final results = decoded['results']; + if (results is List) { + return results.whereType>().toList(); + } + } + return []; + }, + ); + } + + /// Fetches marketplace statistics. + /// + /// Endpoint: GET `/marketplace/stats/` + Future>> getStats() async { + return _request('GET', '/marketplace/stats/', parse: (json) => json); + } + + Future>> getBannedCountries() async { + return _requestRaw( + 'GET', + '/core/banned_countries/', + parse: (body) { + final decoded = jsonDecode(body); + if (decoded is List) { + return decoded.whereType().toList(); + } + return []; + }, + ); + } + + // -- Orders -- + + /// Create an order via the seller API. + /// + /// Posts to `/orders/seller/create/`. The response wraps the order object + /// in `{"message": "...", "order": {...}}`, so we extract `json['order']` + /// before parsing. + Future> createOrder({ + required int cardId, + required String price, + int? quantity, + String? userEmail, + bool? sendEmail, + String? externalOrderId, + String? markupPercent, + bool? confirmsNoVpn, + bool? confirmsVoidedRefund, + bool? confirmsTermsAgreed, + }) async { + final body = {'card_id': cardId, 'price': price}; + if (quantity != null) body['quantity'] = quantity; + if (userEmail != null) body['user_email'] = userEmail; + if (sendEmail != null) body['send_email'] = sendEmail; + if (externalOrderId != null) body['external_order_id'] = externalOrderId; + if (markupPercent != null) body['markup_percent'] = markupPercent; + if (confirmsNoVpn != null) body['confirms_no_vpn'] = confirmsNoVpn; + if (confirmsVoidedRefund != null) { + body['confirms_voided_refund'] = confirmsVoidedRefund; + } + if (confirmsTermsAgreed != null) { + body['confirms_terms_agreed'] = confirmsTermsAgreed; + } + + return _requestRaw( + 'POST', + '/orders/seller/create/', + body: body, + parse: (responseBody) { + final decoded = jsonDecode(responseBody); + if (decoded is Map) { + final orderData = decoded['order']; + if (orderData is Map) { + return CakePayOrder.fromJson(orderData); + } + return CakePayOrder.fromJson(decoded); + } + return CakePayOrder.fromJson({}); + }, + ); + } + + /// Fetch a single order via the seller API. + Future> getOrder(String orderId) async { + return _request( + 'GET', + '/orders/seller/order/$orderId/', + parse: CakePayOrder.fromJson, + ); + } + + /// Fetch the current user's orders. + /// + /// **Note:** This endpoint requires Knox user authentication (email OTP + /// flow), not the seller API key. It will fail when called with only the + /// seller bearer token. + Future>> getMyOrders({ + int? page, + List? orderIds, + }) async { + final query = {}; + if (page != null) query['page'] = page.toString(); + if (orderIds != null && orderIds.isNotEmpty) { + query['order_ids'] = orderIds.join(','); + } + + return _requestRaw( + 'GET', + '/orders/my_orders/', + query: query, + parse: (body) { + final decoded = jsonDecode(body); + if (decoded is List) { + return decoded + .whereType>() + .map(CakePayOrder.fromJson) + .toList(); + } + if (decoded is Map) { + final results = decoded['results']; + if (results is List) { + return results + .whereType>() + .map(CakePayOrder.fromJson) + .toList(); + } + } + return []; + }, + ); + } + + // -- Internal -- + + Future _send( + String method, + String path, { + Map? body, + Map? query, + }) async { + var uri = Uri.parse('$baseUrl$path'); + if (query != null && query.isNotEmpty) { + uri = uri.replace(queryParameters: query); + } + final headers = _headers(); + final proxy = _proxyInfo; + + Logging.instance.t("$_kTag $method $uri"); + + switch (method) { + case 'GET': + return _httpClient.get(url: uri, headers: headers, proxyInfo: proxy); + case 'POST': + return _httpClient.post( + url: uri, + headers: headers, + body: body != null ? jsonEncode(body) : null, + proxyInfo: proxy, + ); + default: + throw ApiException('Unsupported method: $method'); + } + } + + Future> _request( + String method, + String path, { + Map? body, + Map? query, + required T Function(Map) parse, + }) async { + try { + final response = await _send(method, path, body: body, query: query); + + if (response.code >= 200 && response.code < 300) { + Logging.instance.t("$_kTag $method $path HTTP:${response.code}"); + if (response.body.isEmpty) { + return ApiResponse(value: parse({})); + } + final json = jsonDecode(response.body) as Map; + return ApiResponse(value: parse(json)); + } else { + Logging.instance.w( + "$_kTag $method $path HTTP:${response.code} " + "body: ${response.body}", + ); + return ApiResponse( + exception: ApiException.fromResponse(response.code, response.body), + ); + } + } on ApiException catch (e) { + Logging.instance.e("$_kTag _request($method $path) threw: ", error: e); + return ApiResponse(exception: e); + } catch (e, s) { + Logging.instance.e( + "$_kTag _request($method $path) threw: ", + error: e, + stackTrace: s, + ); + return ApiResponse(exception: ApiException.network(e)); + } + } + + Future> _requestRaw( + String method, + String path, { + Map? body, + Map? query, + required T Function(String) parse, + }) async { + try { + final response = await _send(method, path, body: body, query: query); + + if (response.code >= 200 && response.code < 300) { + Logging.instance.t("$_kTag $method $path HTTP:${response.code}"); + return ApiResponse(value: parse(response.body)); + } else { + Logging.instance.w( + "$_kTag $method $path HTTP:${response.code} " + "body: ${response.body}", + ); + return ApiResponse( + exception: ApiException.fromResponse(response.code, response.body), + ); + } + } on ApiException catch (e) { + Logging.instance.e("$_kTag _requestRaw($method $path) threw: ", error: e); + return ApiResponse(exception: e); + } catch (e, s) { + Logging.instance.e( + "$_kTag _requestRaw($method $path) threw: ", + error: e, + stackTrace: s, + ); + return ApiResponse(exception: ApiException.network(e)); + } + } +} diff --git a/lib/services/cakepay/src/endpoints.dart b/lib/services/cakepay/src/endpoints.dart new file mode 100644 index 0000000000..340128c09a --- /dev/null +++ b/lib/services/cakepay/src/endpoints.dart @@ -0,0 +1,3 @@ +class Endpoints { + static const base = 'https://api-prod.cakepay.com/api'; +} diff --git a/lib/services/cakepay/src/models/card.dart b/lib/services/cakepay/src/models/card.dart new file mode 100644 index 0000000000..2fed2f47e0 --- /dev/null +++ b/lib/services/cakepay/src/models/card.dart @@ -0,0 +1,109 @@ +class CakePayCard { + final int id; + final String name; + final String? type; + final String? description; + final String? termsAndConditions; + final String? howToUse; + final String? expiryAndValidity; + final String? cardImageUrl; + final String? country; + final String? currencyCode; + final List denominations; + final double? minValue; + final double? maxValue; + final double? minValueUsd; + final double? maxValueUsd; + final bool available; + final String? lastUpdated; + + CakePayCard({ + required this.id, + required this.name, + this.type, + this.description, + this.termsAndConditions, + this.howToUse, + this.expiryAndValidity, + this.cardImageUrl, + this.country, + this.currencyCode, + required this.denominations, + this.minValue, + this.maxValue, + this.minValueUsd, + this.maxValueUsd, + required this.available, + this.lastUpdated, + }); + + factory CakePayCard.fromJson(Map json) { + final rawDenoms = json['denominations'] ?? json['denominations_list']; + final denominations = []; + if (rawDenoms is List) { + for (final d in rawDenoms) { + if (d is num) { + denominations.add(d.toDouble()); + } else if (d is String) { + final parsed = double.tryParse(d); + if (parsed != null) denominations.add(parsed); + } else if (d is Map) { + final v = d['value']; + if (v is num) { + denominations.add(v.toDouble()); + } else if (v is String) { + final parsed = double.tryParse(v); + if (parsed != null) denominations.add(parsed); + } + } + } + } + + return CakePayCard( + id: json['id'] as int? ?? 0, + name: (json['name'] ?? '') as String, + type: json['type'] as String?, + description: json['description'] as String?, + termsAndConditions: json['terms_and_conditions'] as String?, + howToUse: json['how_to_use'] as String?, + expiryAndValidity: json['expiry_and_validity'] as String?, + cardImageUrl: json['card_image_url'] as String?, + country: json['country'] is Map + ? (json['country'] as Map)['name'] as String? + : json['country'] as String?, + currencyCode: json['currency_code'] as String?, + denominations: denominations, + minValue: _toDouble(json['min_value']), + maxValue: _toDouble(json['max_value']), + minValueUsd: _toDouble(json['min_value_usd']), + maxValueUsd: _toDouble(json['max_value_usd']), + available: json['available'] as bool? ?? true, + lastUpdated: json['last_updated'] as String?, + ); + } + + bool get isFixedDenomination => denominations.isNotEmpty; + bool get isRangeDenomination => + denominations.isEmpty && minValue != null && maxValue != null; + + String get denominationRange { + if (isFixedDenomination) { + return denominations.map((d) => d.toStringAsFixed(0)).join(', '); + } + if (isRangeDenomination) { + return '${minValue!.toStringAsFixed(0)} - ${maxValue!.toStringAsFixed(0)}'; + } + return ''; + } + + @override + String toString() => 'CakePayCard($id, $name)'; +} + +double? _toDouble(dynamic v) { + if (v == null) return null; + if (v is double) return v; + if (v is int) return v.toDouble(); + if (v is String) return double.tryParse(v); + return null; +} diff --git a/lib/services/cakepay/src/models/category.dart b/lib/services/cakepay/src/models/category.dart new file mode 100644 index 0000000000..d097d03197 --- /dev/null +++ b/lib/services/cakepay/src/models/category.dart @@ -0,0 +1,31 @@ +class CakePayCategory { + final int id; + final String name; + final String? emoji; + final String? slug; + final bool isActive; + final int sortOrder; + + CakePayCategory({ + required this.id, + required this.name, + this.emoji, + this.slug, + required this.isActive, + required this.sortOrder, + }); + + factory CakePayCategory.fromJson(Map json) { + return CakePayCategory( + id: json['id'] as int? ?? 0, + name: (json['name'] ?? '') as String, + emoji: json['emoji'] as String?, + slug: json['slug'] as String?, + isActive: json['is_active'] as bool? ?? true, + sortOrder: json['sort_order'] as int? ?? 0, + ); + } + + @override + String toString() => 'CakePayCategory($id, $name)'; +} diff --git a/lib/services/cakepay/src/models/country.dart b/lib/services/cakepay/src/models/country.dart new file mode 100644 index 0000000000..36a65960ae --- /dev/null +++ b/lib/services/cakepay/src/models/country.dart @@ -0,0 +1,28 @@ +class CakePayCountry { + final String name; + final String countryCode; + final String currencyCode; + final String? image; + final bool available; + + CakePayCountry({ + required this.name, + required this.countryCode, + required this.currencyCode, + this.image, + required this.available, + }); + + factory CakePayCountry.fromJson(Map json) { + return CakePayCountry( + name: (json['name'] ?? '') as String, + countryCode: (json['country_code'] ?? '') as String, + currencyCode: (json['currency_code'] ?? '') as String, + image: json['image'] as String?, + available: json['available'] as bool? ?? true, + ); + } + + @override + String toString() => 'CakePayCountry($countryCode, $name)'; +} diff --git a/lib/services/cakepay/src/models/order.dart b/lib/services/cakepay/src/models/order.dart new file mode 100644 index 0000000000..4e88c3f09b --- /dev/null +++ b/lib/services/cakepay/src/models/order.dart @@ -0,0 +1,182 @@ +import 'order_item.dart'; + +enum CakePayOrderStatus { + new_('new'), + expiredButStillPending('expired_but_still_pending'), + expired('expired'), + failed('failed'), + paid('paid'), + paidPartial('paid_partial'), + pendingPurchase('pending_purchase'), + purchaseProcessing('purchase_processing'), + purchased('purchased'), + pendingEmail('pending_email'), + complete('complete'), + pendingRefund('pending_refund'), + refunded('refunded'); + + final String value; + const CakePayOrderStatus(this.value); + + static CakePayOrderStatus fromString(String s) { + return CakePayOrderStatus.values.firstWhere( + (e) => e.value == s, + orElse: () => CakePayOrderStatus.new_, + ); + } +} + +/// A single crypto payment option within [CakePayOrder.paymentOptions]. +/// +/// The API returns `payment_data` as a map whose keys are crypto tickers +/// (e.g. `"BTC"`, `"XMR"`) each mapping to an object with `amount_from` +/// and `address`. +class CakePayPaymentOption { + final String ticker; + final double amountFrom; + final String address; + + CakePayPaymentOption({ + required this.ticker, + required this.amountFrom, + required this.address, + }); + + @override + String toString() => 'CakePayPaymentOption($ticker, $amountFrom, $address)'; +} + +class CakePayOrder { + final String orderId; + final CakePayOrderStatus status; + final String? amountUsd; + final List? cards; + + /// Raw `payment_data` map preserved for backward compatibility. + /// + /// Prefer [paymentOptions] for structured access to crypto payment + /// methods. + final Map? paymentData; + + /// Structured crypto payment options parsed from `payment_data`. + /// + /// Keys are crypto tickers (e.g. `"BTC"`, `"XMR"`, `"BTC_LN"`). + final Map? paymentOptions; + + /// Unix-millis timestamp when the payment window expires. + final int? expirationTime; + + /// Unix-millis timestamp when the invoice was created. + final int? invoiceTime; + + final String? commission; + final double? markupPercent; + final String? createdAt; + final String? externalOrderId; + + CakePayOrder({ + required this.orderId, + required this.status, + this.amountUsd, + this.cards, + this.paymentData, + this.paymentOptions, + this.expirationTime, + this.invoiceTime, + this.commission, + this.markupPercent, + this.createdAt, + this.externalOrderId, + }); + + factory CakePayOrder.fromJson(Map json) { + final rawCards = json['cards']; + List? cards; + if (rawCards is List) { + cards = rawCards + .whereType>() + .map(CakePayOrderItem.fromJson) + .toList(); + } + + // ---- payment_data parsing ---- + final rawPayment = json['payment_data']; + Map? paymentData; + Map? paymentOptions; + int? expirationTime; + int? invoiceTime; + + if (rawPayment is Map) { + paymentData = rawPayment; + + // Extract top-level timing fields. + expirationTime = rawPayment['expiration_time'] as int?; + invoiceTime = rawPayment['invoice_time'] as int?; + + // Each remaining key whose value is a Map is a crypto payment option. + paymentOptions = {}; + for (final entry in rawPayment.entries) { + final v = entry.value; + if (v is Map) { + final amountFrom = _toDouble(v['amount_from']); + final address = v['address']?.toString(); + if (amountFrom != null && address != null) { + paymentOptions[entry.key] = CakePayPaymentOption( + ticker: entry.key, + amountFrom: amountFrom, + address: address, + ); + } + } + } + if (paymentOptions.isEmpty) { + paymentOptions = null; + } + } + + return CakePayOrder( + orderId: (json['order_id'] ?? json['id'])?.toString() ?? '', + status: CakePayOrderStatus.fromString( + (json['status'] ?? 'new') as String, + ), + amountUsd: json['amount_usd']?.toString(), + cards: cards, + paymentData: paymentData, + paymentOptions: paymentOptions, + expirationTime: expirationTime, + invoiceTime: invoiceTime, + commission: json['commission']?.toString(), + markupPercent: _toDouble(json['markup_percent']), + createdAt: json['created_at'] as String?, + externalOrderId: json['external_order_id'] as String?, + ); + } + + CakePayOrder copyWith({CakePayOrderStatus? status}) { + return CakePayOrder( + orderId: orderId, + status: status ?? this.status, + amountUsd: amountUsd, + cards: cards, + paymentData: paymentData, + paymentOptions: paymentOptions, + expirationTime: expirationTime, + invoiceTime: invoiceTime, + commission: commission, + markupPercent: markupPercent, + createdAt: createdAt, + externalOrderId: externalOrderId, + ); + } + + @override + String toString() => 'CakePayOrder($orderId, ${status.value})'; +} + +double? _toDouble(dynamic v) { + if (v == null) return null; + if (v is double) return v; + if (v is int) return v.toDouble(); + if (v is String) return double.tryParse(v); + return null; +} diff --git a/lib/services/cakepay/src/models/order_item.dart b/lib/services/cakepay/src/models/order_item.dart new file mode 100644 index 0000000000..b15386e019 --- /dev/null +++ b/lib/services/cakepay/src/models/order_item.dart @@ -0,0 +1,59 @@ +class CakePayOrderItem { + final int? cardId; + final String? name; + + /// The price string as returned by the API. + /// + /// May be a bare number (`"20.00"`) or include the currency + /// (`"20.00 EUR"`). Use [priceValue] when you need only the numeric + /// portion and [currencyCode] for the currency. + final String? price; + + /// The numeric portion of [price] (e.g. `"20.00"`). + final String? priceValue; + + /// Price expressed in USD, as returned by the API (e.g. `"$24.12"`). + final String? priceUsd; + + final int? quantity; + final String? currencyCode; + final String? cardImageUrl; + + CakePayOrderItem({ + this.cardId, + this.name, + this.price, + this.priceValue, + this.priceUsd, + this.quantity, + this.currencyCode, + this.cardImageUrl, + }); + + factory CakePayOrderItem.fromJson(Map json) { + final rawPrice = json['price']?.toString(); + + // The API may return price as "20.00 EUR" (with currency) or just + // "20.00". Extract the leading numeric portion so the UI can display + // it without duplicating the currency code. + String? priceValue; + if (rawPrice != null) { + final match = RegExp(r'^[\d.]+').firstMatch(rawPrice); + priceValue = match?.group(0) ?? rawPrice; + } + + return CakePayOrderItem( + cardId: json['card_id'] as int?, + name: json['name'] as String?, + price: rawPrice, + priceValue: priceValue, + priceUsd: json['price_usd']?.toString(), + quantity: json['quantity'] as int?, + currencyCode: json['currency_code'] as String?, + cardImageUrl: json['card_image_url'] as String?, + ); + } + + @override + String toString() => 'CakePayOrderItem($cardId, $name)'; +} diff --git a/lib/services/cakepay/src/models/vendor.dart b/lib/services/cakepay/src/models/vendor.dart new file mode 100644 index 0000000000..33f80035ae --- /dev/null +++ b/lib/services/cakepay/src/models/vendor.dart @@ -0,0 +1,43 @@ +import 'card.dart'; + +class CakePayVendor { + final int id; + final String name; + final bool available; + final String? cakeWarnings; + final String? country; + final List cards; + + CakePayVendor({ + required this.id, + required this.name, + required this.available, + this.cakeWarnings, + this.country, + required this.cards, + }); + + factory CakePayVendor.fromJson(Map json) { + final rawCards = json['cards']; + final cards = []; + if (rawCards is List) { + for (final c in rawCards) { + if (c is Map) { + cards.add(CakePayCard.fromJson(c)); + } + } + } + + return CakePayVendor( + id: json['id'] as int? ?? 0, + name: (json['name'] ?? '') as String, + available: json['available'] as bool? ?? true, + cakeWarnings: json['cake_warnings'] as String?, + country: json['country'] as String?, + cards: cards, + ); + } + + @override + String toString() => 'CakePayVendor($id, $name)'; +} diff --git a/lib/services/ord_api.dart b/lib/services/ord_api.dart new file mode 100644 index 0000000000..79800860fd --- /dev/null +++ b/lib/services/ord_api.dart @@ -0,0 +1,70 @@ +import 'dart:convert'; +import 'dart:io'; + +import '../app_config.dart'; +import '../networking/http.dart'; +import '../utilities/prefs.dart'; +import 'tor_service.dart'; + +class OrdAPI { + final String baseUrl; + final HTTP _client = const HTTP(); + + OrdAPI({required this.baseUrl}); + + static const _jsonHeaders = {'Accept': 'application/json'}; + + ({InternetAddress host, int port})? get _proxyInfo => + !AppConfig.hasFeature(AppFeature.tor) + ? null + : Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null; + + /// Check an output for inscriptions. + /// Returns the list of inscription IDs found on the output, or empty list. + Future> getInscriptionIdsForOutput(String txid, int vout) async { + final response = await _client.get( + url: Uri.parse('$baseUrl/output/$txid:$vout'), + headers: _jsonHeaders, + proxyInfo: _proxyInfo, + ); + + if (response.code != 200) { + throw Exception( + 'OrdAPI getInscriptionIdsForOutput failed: ' + 'status=${response.code}', + ); + } + + final json = jsonDecode(response.body) as Map; + final inscriptions = json['inscriptions'] as List?; + + if (inscriptions == null || inscriptions.isEmpty) { + return []; + } + + return inscriptions.cast(); + } + + /// Fetch full inscription metadata by ID. + Future> getInscriptionData(String inscriptionId) async { + final response = await _client.get( + url: Uri.parse('$baseUrl/inscription/$inscriptionId'), + headers: _jsonHeaders, + proxyInfo: _proxyInfo, + ); + + if (response.code != 200) { + throw Exception( + 'OrdAPI getInscriptionData failed: ' + 'status=${response.code}', + ); + } + + return jsonDecode(response.body) as Map; + } + + /// Build the content URL for an inscription. + String contentUrl(String inscriptionId) => '$baseUrl/content/$inscriptionId'; +} diff --git a/lib/services/shopinbit/shopinbit_api.dart b/lib/services/shopinbit/shopinbit_api.dart new file mode 100644 index 0000000000..fd1f12c47c --- /dev/null +++ b/lib/services/shopinbit/shopinbit_api.dart @@ -0,0 +1,7 @@ +export 'src/client.dart'; +export 'src/token_manager.dart'; +export 'src/api_response.dart'; +export 'src/api_exception.dart'; +export 'src/webhook_verifier.dart'; +export 'src/endpoints.dart'; +export 'src/models/models.dart'; diff --git a/lib/services/shopinbit/shopinbit_service.dart b/lib/services/shopinbit/shopinbit_service.dart new file mode 100644 index 0000000000..b0669433a4 --- /dev/null +++ b/lib/services/shopinbit/shopinbit_service.dart @@ -0,0 +1,159 @@ +import '../../db/hive/db.dart'; +import '../../external_api_keys.dart'; +import '../../utilities/logger.dart'; +import 'src/client.dart'; + +class ShopInBitService { + static final instance = ShopInBitService._(); + ShopInBitService._(); + + ShopInBitClient? _client; + String? _customerKey; + bool? _guidelinesAccepted; + bool? _setupComplete; + String? _displayName; + + ShopInBitClient get client { + if (_client == null) { + _client = ShopInBitClient( + accessKey: kShopInBitAccessKey, + partnerSecret: kShopInBitPartnerSecret, + sandbox: true, + ); + // Pre-load customer key for ticket detail API calls. + loadCustomerKey(); + } + return _client!; + } + + String? get customerKey => _customerKey; + + String? loadCustomerKey() { + if (_customerKey != null) return _customerKey; + _customerKey = + DB.instance.get( + boxName: DB.boxNamePrefs, + key: "shopInBitCustomerKey", + ) + as String?; + if (_customerKey != null) { + client.externalCustomerKey = _customerKey; + } + return _customerKey; + } + + Future ensureCustomerKey() async { + if (_customerKey != null) return _customerKey!; + _customerKey = + DB.instance.get( + boxName: DB.boxNamePrefs, + key: "shopInBitCustomerKey", + ) + as String?; + if (_customerKey != null) { + Logging.instance.t("ShopInBitService: loaded customer key from DB"); + client.externalCustomerKey = _customerKey; + return _customerKey!; + } + Logging.instance.i("ShopInBitService: generating new customer key"); + final resp = await client.generateKey(); + _customerKey = resp.valueOrThrow; + client.externalCustomerKey = _customerKey; + await DB.instance.put( + boxName: DB.boxNamePrefs, + key: "shopInBitCustomerKey", + value: _customerKey, + ); + Logging.instance.i("ShopInBitService: customer key stored"); + return _customerKey!; + } + + Future setCustomerKey(String key) async { + _customerKey = key; + client.externalCustomerKey = key; + await DB.instance.put( + boxName: DB.boxNamePrefs, + key: "shopInBitCustomerKey", + value: key, + ); + Logging.instance.i("ShopInBitService: customer key manually set"); + } + + Future clearCustomerKey() async { + _customerKey = null; + client.externalCustomerKey = null; + await DB.instance.put( + boxName: DB.boxNamePrefs, + key: "shopInBitCustomerKey", + value: null, + ); + Logging.instance.i("ShopInBitService: customer key cleared"); + } + + bool loadGuidelinesAccepted() { + if (_guidelinesAccepted != null) return _guidelinesAccepted!; + _guidelinesAccepted = + DB.instance.get( + boxName: DB.boxNamePrefs, + key: "shopInBitGuidelinesAccepted", + ) + as bool? ?? + false; + return _guidelinesAccepted!; + } + + Future setGuidelinesAccepted(bool accepted) async { + _guidelinesAccepted = accepted; + await DB.instance.put( + boxName: DB.boxNamePrefs, + key: "shopInBitGuidelinesAccepted", + value: accepted, + ); + Logging.instance.i( + "ShopInBitService: guidelines accepted set to $accepted", + ); + } + + bool loadSetupComplete() { + if (_setupComplete != null) return _setupComplete!; + _setupComplete = + DB.instance.get( + boxName: DB.boxNamePrefs, + key: "shopInBitSetupComplete", + ) + as bool? ?? + false; + return _setupComplete!; + } + + Future setSetupComplete(bool complete) async { + _setupComplete = complete; + await DB.instance.put( + boxName: DB.boxNamePrefs, + key: "shopInBitSetupComplete", + value: complete, + ); + Logging.instance.i("ShopInBitService: setup complete set to $complete"); + } + + String? loadDisplayName() { + if (_displayName != null) return _displayName; + _displayName = + DB.instance.get( + boxName: DB.boxNamePrefs, + key: "shopInBitDisplayName", + ) + as String?; + return _displayName; + } + + Future setDisplayName(String name) async { + _displayName = name; + await DB.instance.put( + boxName: DB.boxNamePrefs, + key: "shopInBitDisplayName", + value: name, + ); + Logging.instance.i("ShopInBitService: display name set"); + } +} diff --git a/lib/services/shopinbit/src/api_exception.dart b/lib/services/shopinbit/src/api_exception.dart new file mode 100644 index 0000000000..6e35192572 --- /dev/null +++ b/lib/services/shopinbit/src/api_exception.dart @@ -0,0 +1,24 @@ +class ApiException implements Exception { + final String message; + final int? statusCode; + final String? responseBody; + + ApiException(this.message, {this.statusCode, this.responseBody}); + + factory ApiException.fromResponse(int statusCode, String body) { + return ApiException( + 'HTTP $statusCode', + statusCode: statusCode, + responseBody: body, + ); + } + + factory ApiException.network(Object error) { + return ApiException('Network error: $error'); + } + + @override + String toString() => + 'ApiException: $message' + '${statusCode != null ? ' (status: $statusCode)' : ''}'; +} diff --git a/lib/services/shopinbit/src/api_response.dart b/lib/services/shopinbit/src/api_response.dart new file mode 100644 index 0000000000..a1e9135063 --- /dev/null +++ b/lib/services/shopinbit/src/api_response.dart @@ -0,0 +1,18 @@ +import 'api_exception.dart'; + +class ApiResponse { + final T? value; + final ApiException? exception; + + ApiResponse({this.value, this.exception}); + + bool get hasError => exception != null; + + T get valueOrThrow { + if (exception != null) throw exception!; + return value as T; + } + + @override + String toString() => '{error: $exception, value: $value}'; +} diff --git a/lib/services/shopinbit/src/client.dart b/lib/services/shopinbit/src/client.dart new file mode 100644 index 0000000000..fe48184129 --- /dev/null +++ b/lib/services/shopinbit/src/client.dart @@ -0,0 +1,659 @@ +import 'dart:convert'; +import 'dart:io'; + +import '../../../app_config.dart'; +import '../../../networking/http.dart'; +import '../../../utilities/logger.dart'; +import '../../../utilities/prefs.dart'; +import '../../tor_service.dart'; +import 'api_exception.dart'; +import 'api_response.dart'; +import 'endpoints.dart'; +import 'token_manager.dart'; +import 'models/address.dart'; +import 'models/car_research.dart'; +import 'models/message.dart'; +import 'models/payment.dart'; +import 'models/ticket.dart'; +import 'models/voucher.dart'; + +const _kTag = "ShopInBitClient"; + +class ShopInBitClient { + final String accessKey; + final String partnerSecret; + final String baseUrl; + final bool sandbox; + final HTTP _httpClient; + final TokenManager _tokenManager; + + String? _externalCustomerKey; + + String? get externalCustomerKey => _externalCustomerKey; + set externalCustomerKey(String? key) => _externalCustomerKey = key; + + ShopInBitClient({ + required this.accessKey, + required this.partnerSecret, + this.baseUrl = Endpoints.production, + this.sandbox = false, + String? externalCustomerKey, + HTTP? httpClient, + }) : _externalCustomerKey = externalCustomerKey, + _httpClient = httpClient ?? const HTTP(), + _tokenManager = TokenManager( + accessKey: accessKey, + partnerSecret: partnerSecret, + baseUrl: baseUrl, + httpClient: httpClient, + ); + + // -- Auth -- + + Future> authenticate() async { + try { + await _tokenManager.getValidToken(); + return ApiResponse(); + } on ApiException catch (e) { + return ApiResponse(exception: e); + } catch (e) { + return ApiResponse(exception: ApiException('Authentication failed: $e')); + } + } + + // -- Utility -- + + Future> generateKey() async { + return _request( + 'GET', + '/generate-key', + needsCustomerKey: false, + parse: (json) { + return json['external_customer_key'] as String; + }, + ); + } + + Future>> getHealth() async { + return _request( + 'GET', + '/health', + needsCustomerKey: false, + parse: (json) => json, + ); + } + + Future>>> getCountries() async { + return _requestRaw( + 'GET', + '/meta/countries', + needsCustomerKey: false, + needsAuth: false, + parse: (body) { + final decoded = jsonDecode(body); + if (decoded is List) { + return decoded.cast>(); + } + return [decoded as Map]; + }, + ); + } + + // -- Tickets -- + + Future> createRequest({ + required String customerPseudonym, + required String externalCustomerKey, + required String serviceType, + required String comment, + required String deliveryCountry, + String? voucherCode, + }) async { + return _request( + 'POST', + '/requests', + body: { + 'customer_pseudonym': customerPseudonym, + 'external_customer_key': externalCustomerKey, + 'service_type': serviceType, + 'comment': comment, + 'delivery_country': deliveryCountry, + if (voucherCode != null) 'voucher_code': voucherCode, + }, + parse: (json) { + return TicketRef( + id: json['ticket_id'] is int + ? json['ticket_id'] as int + : int.parse(json['ticket_id'].toString()), + number: json['ticket_number'].toString(), + ); + }, + ); + } + + Future> getTicketStatus(int ticketId) async { + return _request( + 'GET', + '/tickets/$ticketId/status', + parse: TicketStatus.fromJson, + ); + } + + Future> getTicketFull(int ticketId) async { + return _request( + 'GET', + '/tickets/$ticketId/full', + parse: TicketFull.fromJson, + ); + } + + Future>> getTicketsByCustomer( + String customerKey, + ) async { + return _request( + 'GET', + '/tickets/by-customer/$customerKey', + parse: (json) { + final list = json['tickets'] as List; + return list + .map((e) => TicketRef.fromJson(e as Map)) + .toList(); + }, + ); + } + + // -- Messages -- + + Future>> sendMessage( + int ticketId, + String message, + ) async { + return _request( + 'POST', + '/tickets/$ticketId/messages', + body: {'message': message}, + parse: (json) => json, + ); + } + + Future>> getMessages(int ticketId) async { + return _request( + 'GET', + '/tickets/$ticketId/messages', + parse: (json) { + final list = json['messages'] as List; + return list + .map((e) => TicketMessage.fromJson(e as Map)) + .toList(); + }, + ); + } + + // -- Attachments -- + + Future>> sendAttachments( + int ticketId, { + required String message, + required List> attachments, + }) async { + return _request( + 'POST', + '/tickets/$ticketId/attachments', + body: {'message': message, 'attachments': attachments}, + parse: (json) => json, + ); + } + + /// Build a URL for fetching an attachment via `/attachment-proxy/`. + /// + /// For use in HTTP clients that can set headers, use the returned URL with + /// the standard Authorization + External-Customer-Key headers. + /// For inline images (e.g. in HTML where headers can't be set), pass + /// [useQueryAuth] = true to append token and customer_key as query params. + Future> getAttachmentUrl( + String attachmentPath, { + bool useQueryAuth = false, + }) async { + try { + final token = await _tokenManager.getValidToken(); + final resolved = _resolvePath('/attachment-proxy/$attachmentPath'); + var uri = Uri.parse('$baseUrl$resolved'); + if (useQueryAuth) { + uri = uri.replace( + queryParameters: { + 'token': token, + if (_externalCustomerKey != null) + 'customer_key': _externalCustomerKey!, + }, + ); + } + return ApiResponse(value: uri); + } on ApiException catch (e) { + return ApiResponse(exception: e); + } catch (e) { + return ApiResponse(exception: ApiException.network(e)); + } + } + + /// Download an attachment from `/attachment-proxy/`. + Future> getAttachment(String attachmentPath) async { + try { + final token = await _tokenManager.getValidToken(); + final resolved = _resolvePath('/attachment-proxy/$attachmentPath'); + final uri = Uri.parse('$baseUrl$resolved'); + Logging.instance.t("$_kTag GET $uri"); + final headers = _headers(token); + final response = await _httpClient.get( + url: uri, + headers: headers, + proxyInfo: _proxyInfo, + ); + if (response.code >= 200 && response.code < 300) { + return ApiResponse(value: response); + } else { + Logging.instance.w( + "$_kTag GET $resolved HTTP:${response.code} " + "body: ${response.body}", + ); + return ApiResponse( + exception: ApiException.fromResponse(response.code, response.body), + ); + } + } on ApiException catch (e) { + Logging.instance.e( + "$_kTag getAttachment($attachmentPath) threw: ", + error: e, + ); + return ApiResponse(exception: e); + } catch (e, s) { + Logging.instance.e( + "$_kTag getAttachment($attachmentPath) threw: ", + error: e, + stackTrace: s, + ); + return ApiResponse(exception: ApiException.network(e)); + } + } + + // -- Address -- + + Future>> submitAddress( + int ticketId, { + required Address shipping, + Address? billing, + }) async { + return _request( + 'POST', + '/tickets/$ticketId/address', + body: {'shipping': shipping.toJson(), 'billing': billing?.toJson()}, + parse: (json) => json, + ); + } + + // -- Payment -- + + Future> getPayment( + int ticketId, { + bool retry = false, + }) async { + final path = '/tickets/$ticketId/payment'; + final query = retry ? {'retry': 'true'} : null; + return _request('GET', path, query: query, parse: PaymentInfo.fromJson); + } + + // -- Vouchers -- + + /// Pre-check a voucher code (does not consume usage or create a ticket). + Future> checkVoucher(String code) async { + return _request( + 'GET', + '/vouchers/validate', + query: {'code': code}, + parse: VoucherInfo.fromJson, + ); + } + + /// Redeem a VIP voucher (creates ticket in one call). VIP/VIP_PRIORITY only. + Future> redeemVipVoucher({ + required String voucherCode, + required String customerPseudonym, + required String serviceType, + required String comment, + String? deliveryCountry, + }) async { + return _request( + 'POST', + '/vouchers/validate', + body: { + 'voucher_code': voucherCode, + 'customer_pseudonym': customerPseudonym, + 'service_type': serviceType, + 'comment': comment, + if (deliveryCountry != null) 'delivery_country': deliveryCountry, + }, + parse: VipRedemptionResult.fromJson, + ); + } + + // -- Car Research Fee -- + + Future> createCarResearchInvoice({ + required Address billing, + }) async { + return _request( + 'POST', + '/car-research/invoice', + body: { + 'billing': billing.toJson(), + if (_externalCustomerKey != null) + 'external_customer_key': _externalCustomerKey, + }, + parse: CarResearchInvoice.fromJson, + ); + } + + Future>> getCarResearchInvoiceStatus( + String invoiceId, + ) async { + return _request( + 'GET', + '/car-research/invoice/$invoiceId/status', + parse: (json) => json, + ); + } + + Future> logCarResearchPayment( + String invoiceId, + ) async { + return _request( + 'POST', + '/car-research/log-payment', + body: { + 'invoice_id': invoiceId, + if (_externalCustomerKey != null) + 'external_customer_key': _externalCustomerKey, + }, + parse: CarResearchPaymentResult.fromJson, + ); + } + + // -- Push Notifications -- + + Future>> registerPushSubscription({ + String? deviceToken, + String? endpoint, + Map? keys, + String? platform, + String? environment, + String? expirationTime, + int? ticketId, + }) async { + return _request( + 'POST', + '/notifications/push-subscriptions', + body: { + if (deviceToken != null) 'deviceToken': deviceToken, + if (endpoint != null) 'endpoint': endpoint, + if (keys != null) 'keys': keys, + if (platform != null) 'platform': platform, + if (environment != null) 'environment': environment, + if (expirationTime != null) 'expirationTime': expirationTime, + if (ticketId != null) 'ticketId': ticketId, + }, + parse: (json) => json, + ); + } + + // -- Webhooks -- + + Future>>> listWebhooks() async { + return _request( + 'GET', + '/partners/webhooks', + needsCustomerKey: false, + parse: (json) { + if (json.containsKey('webhooks')) { + return (json['webhooks'] as List) + .cast>(); + } + return [json]; + }, + ); + } + + Future>> createWebhook({ + required String webhookUrl, + required List eventTypes, + }) async { + return _request( + 'POST', + '/partners/webhooks', + needsCustomerKey: false, + body: {'webhook_url': webhookUrl, 'event_types': eventTypes}, + parse: (json) => json, + ); + } + + Future>> rotateWebhookSecret( + String webhookId, + ) async { + return _request( + 'POST', + '/partners/webhooks/$webhookId/rotate', + needsCustomerKey: false, + parse: (json) => json, + ); + } + + Future> deleteWebhook(String webhookId) async { + return _request( + 'DELETE', + '/partners/webhooks/$webhookId', + needsCustomerKey: false, + parse: (_) => null, + ); + } + + // -- Sandbox -- + + Future>> sandboxSetState( + int ticketId, + String state, + ) async { + return _request( + 'POST', + '/sandbox/state/$ticketId/$state', + parse: (json) => json, + ); + } + + Future>> sandboxSetPayment( + int ticketId, + String status, + ) async { + return _request( + 'POST', + '/sandbox/payment/$ticketId/$status', + parse: (json) => json, + ); + } + + // -- Internals -- + + ({InternetAddress host, int port})? get _proxyInfo => + !AppConfig.hasFeature(AppFeature.tor) + ? null + : Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null; + + /// Prepend /sandbox to paths when in sandbox mode, except for paths that + /// already start with /sandbox, /meta, /health, or /token. + String _resolvePath(String path) { + if (!sandbox) return path; + if (path.startsWith('/sandbox') || + path.startsWith('/meta') || + path.startsWith('/health') || + path.startsWith('/token') || + path.startsWith('/partners')) { + return path; + } + return '/sandbox$path'; + } + + Map _headers(String token, {bool needsCustomerKey = true}) { + final h = { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }; + if (needsCustomerKey && _externalCustomerKey != null) { + h['External-Customer-Key'] = _externalCustomerKey!; + } + return h; + } + + Future _send( + String method, + String path, { + Map? body, + Map? query, + bool needsCustomerKey = true, + bool needsAuth = true, + }) async { + final resolved = _resolvePath(path); + var uri = Uri.parse('$baseUrl$resolved'); + if (query != null && query.isNotEmpty) { + uri = uri.replace(queryParameters: query); + } + final Map headers; + if (needsAuth) { + final token = await _tokenManager.getValidToken(); + headers = _headers(token, needsCustomerKey: needsCustomerKey); + } else { + headers = {'Accept': 'application/json'}; + } + final proxy = _proxyInfo; + + Logging.instance.t("$_kTag $method $uri"); + + switch (method) { + case 'GET': + return _httpClient.get(url: uri, headers: headers, proxyInfo: proxy); + case 'POST': + return _httpClient.post( + url: uri, + headers: headers, + body: body != null ? jsonEncode(body) : null, + proxyInfo: proxy, + ); + case 'PATCH': + return _httpClient.patch( + url: uri, + headers: headers, + body: body != null ? jsonEncode(body) : null, + proxyInfo: proxy, + ); + case 'DELETE': + return _httpClient.delete(url: uri, headers: headers, proxyInfo: proxy); + default: + throw ApiException('Unsupported method: $method'); + } + } + + Future> _request( + String method, + String path, { + Map? body, + Map? query, + bool needsCustomerKey = true, + required T Function(Map) parse, + }) async { + try { + final response = await _send( + method, + path, + body: body, + query: query, + needsCustomerKey: needsCustomerKey, + ); + + final resolved = _resolvePath(path); + + if (response.code >= 200 && response.code < 300) { + Logging.instance.t("$_kTag $method $resolved HTTP:${response.code}"); + if (response.body.isEmpty) { + return ApiResponse(value: parse({})); + } + final json = jsonDecode(response.body) as Map; + return ApiResponse(value: parse(json)); + } else { + Logging.instance.w( + "$_kTag $method $resolved HTTP:${response.code} " + "body: ${response.body}", + ); + return ApiResponse( + exception: ApiException.fromResponse(response.code, response.body), + ); + } + } on ApiException catch (e) { + Logging.instance.e("$_kTag _request($method $path) threw: ", error: e); + return ApiResponse(exception: e); + } catch (e, s) { + Logging.instance.e( + "$_kTag _request($method $path) threw: ", + error: e, + stackTrace: s, + ); + return ApiResponse(exception: ApiException.network(e)); + } + } + + /// Like [_request] but gives the parse function the raw response body + /// string, for endpoints that return non-object JSON (e.g. arrays). + Future> _requestRaw( + String method, + String path, { + Map? body, + Map? query, + bool needsCustomerKey = true, + bool needsAuth = true, + required T Function(String) parse, + }) async { + try { + final response = await _send( + method, + path, + body: body, + query: query, + needsCustomerKey: needsCustomerKey, + needsAuth: needsAuth, + ); + + final resolved = _resolvePath(path); + + if (response.code >= 200 && response.code < 300) { + Logging.instance.t("$_kTag $method $resolved HTTP:${response.code}"); + return ApiResponse(value: parse(response.body)); + } else { + Logging.instance.w( + "$_kTag $method $resolved HTTP:${response.code} " + "body: ${response.body}", + ); + return ApiResponse( + exception: ApiException.fromResponse(response.code, response.body), + ); + } + } on ApiException catch (e) { + Logging.instance.e("$_kTag _requestRaw($method $path) threw: ", error: e); + return ApiResponse(exception: e); + } catch (e, s) { + Logging.instance.e( + "$_kTag _requestRaw($method $path) threw: ", + error: e, + stackTrace: s, + ); + return ApiResponse(exception: ApiException.network(e)); + } + } +} diff --git a/lib/services/shopinbit/src/endpoints.dart b/lib/services/shopinbit/src/endpoints.dart new file mode 100644 index 0000000000..00a18f669b --- /dev/null +++ b/lib/services/shopinbit/src/endpoints.dart @@ -0,0 +1,3 @@ +class Endpoints { + static const production = 'https://api.shopinbit.com'; +} diff --git a/lib/services/shopinbit/src/models/address.dart b/lib/services/shopinbit/src/models/address.dart new file mode 100644 index 0000000000..5371a37d06 --- /dev/null +++ b/lib/services/shopinbit/src/models/address.dart @@ -0,0 +1,49 @@ +class Address { + final String? company; + final String? vat; + final String firstName; + final String lastName; + final String street; + final String zip; + final String city; + final String country; + final String? state; + + Address({ + this.company, + this.vat, + required this.firstName, + required this.lastName, + required this.street, + required this.zip, + required this.city, + required this.country, + this.state, + }); + + Map toJson() => { + 'company': company, + 'vat': vat, + 'firstName': firstName, + 'lastName': lastName, + 'street': street, + 'zip': zip, + 'city': city, + 'country': country, + 'state': state, + }; + + factory Address.fromJson(Map json) { + return Address( + company: json['company'] as String?, + vat: json['vat'] as String?, + firstName: json['firstName'] as String, + lastName: json['lastName'] as String, + street: json['street'] as String, + zip: json['zip'] as String, + city: json['city'] as String, + country: json['country'] as String, + state: json['state'] as String?, + ); + } +} diff --git a/lib/services/shopinbit/src/models/auth_token.dart b/lib/services/shopinbit/src/models/auth_token.dart new file mode 100644 index 0000000000..af7816aadd --- /dev/null +++ b/lib/services/shopinbit/src/models/auth_token.dart @@ -0,0 +1,25 @@ +class AuthToken { + final String accessToken; + final String tokenType; + final DateTime expiresAt; + + AuthToken({ + required this.accessToken, + required this.tokenType, + required this.expiresAt, + }); + + factory AuthToken.fromJson(Map json) { + return AuthToken( + accessToken: json['access_token'] as String, + tokenType: json['token_type'] as String, + // Tokens valid for 10 minutes per API docs. + expiresAt: DateTime.now().add(const Duration(minutes: 10)), + ); + } + + bool get isExpired => DateTime.now().isAfter(expiresAt); + + bool get expiresSoon => + DateTime.now().isAfter(expiresAt.subtract(const Duration(minutes: 1))); +} diff --git a/lib/services/shopinbit/src/models/car_research.dart b/lib/services/shopinbit/src/models/car_research.dart new file mode 100644 index 0000000000..ea1eceb0d2 --- /dev/null +++ b/lib/services/shopinbit/src/models/car_research.dart @@ -0,0 +1,43 @@ +class CarResearchInvoice { + final String btcpayInvoice; + final DateTime expiresAt; + final Map paymentLinks; + + CarResearchInvoice({ + required this.btcpayInvoice, + required this.expiresAt, + required this.paymentLinks, + }); + + factory CarResearchInvoice.fromJson(Map json) { + final linksRaw = json['payment_links'] as Map? ?? {}; + return CarResearchInvoice( + btcpayInvoice: json['btcpay_invoice'] as String, + expiresAt: DateTime.parse(json['expires_at'] as String), + paymentLinks: linksRaw.map((k, v) => MapEntry(k, v as String)), + ); + } +} + +class CarResearchPaymentResult { + final String status; + final int ticketId; + final String ticketNumber; + final String externalCustomerKey; + + CarResearchPaymentResult({ + required this.status, + required this.ticketId, + required this.ticketNumber, + required this.externalCustomerKey, + }); + + factory CarResearchPaymentResult.fromJson(Map json) { + return CarResearchPaymentResult( + status: json['status'] as String, + ticketId: json['ticket_id'] as int, + ticketNumber: json['ticket_number'] as String, + externalCustomerKey: json['external_customer_key'] as String, + ); + } +} diff --git a/lib/services/shopinbit/src/models/message.dart b/lib/services/shopinbit/src/models/message.dart new file mode 100644 index 0000000000..2048b72c27 --- /dev/null +++ b/lib/services/shopinbit/src/models/message.dart @@ -0,0 +1,19 @@ +class TicketMessage { + final DateTime timestamp; + final bool fromAgent; + final String content; + + TicketMessage({ + required this.timestamp, + required this.fromAgent, + required this.content, + }); + + factory TicketMessage.fromJson(Map json) { + return TicketMessage( + timestamp: DateTime.parse(json['timestamp'] as String), + fromAgent: json['from_agent'] as bool, + content: json['content'] as String, + ); + } +} diff --git a/lib/services/shopinbit/src/models/models.dart b/lib/services/shopinbit/src/models/models.dart new file mode 100644 index 0000000000..7d4208c2fc --- /dev/null +++ b/lib/services/shopinbit/src/models/models.dart @@ -0,0 +1,8 @@ +export 'auth_token.dart'; +export 'ticket.dart'; +export 'message.dart'; +export 'address.dart'; +export 'payment.dart'; +export 'car_research.dart'; +export 'voucher.dart'; +export 'webhook_event.dart'; diff --git a/lib/services/shopinbit/src/models/payment.dart b/lib/services/shopinbit/src/models/payment.dart new file mode 100644 index 0000000000..bd0938da29 --- /dev/null +++ b/lib/services/shopinbit/src/models/payment.dart @@ -0,0 +1,44 @@ +class PaymentInfo { + final String status; + final String customerPrice; + final String partnerPrice; + final int vatRate; + final String currency; + final DateTime? rateLockedUntil; + final Map paymentLinks; + final String? due; + + PaymentInfo({ + required this.status, + required this.customerPrice, + required this.partnerPrice, + required this.vatRate, + required this.currency, + this.rateLockedUntil, + required this.paymentLinks, + this.due, + }); + + factory PaymentInfo.fromJson(Map json) { + final linksRaw = json['payment_links'] as Map? ?? {}; + return PaymentInfo( + status: json['status'] as String, + customerPrice: (json['customer_price'] ?? '') as String, + partnerPrice: (json['partner_price'] ?? '') as String, + vatRate: _toInt(json['vat_rate']), + currency: (json['currency'] ?? 'EUR') as String, + rateLockedUntil: json['rate_locked_until'] != null + ? DateTime.parse(json['rate_locked_until'] as String) + : null, + paymentLinks: linksRaw.map((k, v) => MapEntry(k, v as String)), + due: json['due'] as String?, + ); + } +} + +int _toInt(dynamic v) { + if (v is int) return v; + if (v is String) return int.parse(v); + if (v is double) return v.toInt(); + return 0; +} diff --git a/lib/services/shopinbit/src/models/ticket.dart b/lib/services/shopinbit/src/models/ticket.dart new file mode 100644 index 0000000000..eec6dd3604 --- /dev/null +++ b/lib/services/shopinbit/src/models/ticket.dart @@ -0,0 +1,112 @@ +enum TicketState { + newTicket('NEW'), + checking('CHECKING'), + inProgress('IN PROGRESS'), + offerAvailable('OFFER AVAILABLE'), + clearing('CLEARING'), + shipped('SHIPPED'), + refunded('REFUNDED'), + fulfilled('FULFILLED'), + pendingClose('PENDING CLOSE'), + replyNeeded('REPLY NEEDED'), + closed('CLOSED'), + closedCancelled('CLOSED/CANCELLED'), + merged('MERGED'); + + final String value; + const TicketState(this.value); + + static TicketState fromString(String s) { + return TicketState.values.firstWhere( + (e) => e.value == s, + orElse: () => TicketState.newTicket, + ); + } +} + +class TicketRef { + final int id; + final String number; + + TicketRef({required this.id, required this.number}); + + factory TicketRef.fromJson(Map json) { + return TicketRef(id: _toInt(json['id']), number: json['number'].toString()); + } +} + +class TicketStatus { + final int ticketId; + final TicketState state; + final DateTime updatedAt; + final DateTime? lastAgentMessageAt; + final String? paymentInvoiceStatus; + final String? trackingLink; + + TicketStatus({ + required this.ticketId, + required this.state, + required this.updatedAt, + this.lastAgentMessageAt, + this.paymentInvoiceStatus, + this.trackingLink, + }); + + factory TicketStatus.fromJson(Map json) { + return TicketStatus( + ticketId: _toInt(json['ticket_id']), + state: TicketState.fromString(json['state'] as String), + updatedAt: DateTime.parse(json['updated_at'] as String), + lastAgentMessageAt: json['last_agent_message_at'] != null + ? DateTime.parse(json['last_agent_message_at'] as String) + : null, + paymentInvoiceStatus: json['payment_invoice_status'] as String?, + trackingLink: json['tracking_link'] as String?, + ); + } +} + +class TicketFull { + final int id; + final String number; + final String productName; + final String customerPrice; + final String partnerPrice; + final String partnerCommission; + final String netPurchasePrice; + final String netShippingCosts; + final int vatRate; + + TicketFull({ + required this.id, + required this.number, + required this.productName, + required this.customerPrice, + required this.partnerPrice, + required this.partnerCommission, + required this.netPurchasePrice, + required this.netShippingCosts, + required this.vatRate, + }); + + factory TicketFull.fromJson(Map json) { + return TicketFull( + id: _toInt(json['id']), + number: json['number'].toString(), + productName: (json['product_name'] ?? '').toString(), + customerPrice: (json['customer_price'] ?? '').toString(), + partnerPrice: (json['partner_price'] ?? '').toString(), + partnerCommission: (json['partner_commission'] ?? '').toString(), + netPurchasePrice: (json['net_purchase_price'] ?? '').toString(), + netShippingCosts: (json['net_shipping_costs'] ?? '').toString(), + vatRate: _toInt(json['vat_rate']), + ); + } +} + +int _toInt(dynamic v) { + if (v is int) return v; + if (v is String) return int.parse(v); + if (v is double) return v.toInt(); + return 0; +} diff --git a/lib/services/shopinbit/src/models/voucher.dart b/lib/services/shopinbit/src/models/voucher.dart new file mode 100644 index 0000000000..97d048a8b4 --- /dev/null +++ b/lib/services/shopinbit/src/models/voucher.dart @@ -0,0 +1,71 @@ +class VoucherInfo { + final bool valid; + final String? voucherCode; + final double? discountAmount; + final String? voucherType; + final int? priorityLevel; + final int? usageCount; + final int? maxUsage; + final bool? isUnlimited; + final int? remainingUses; + final String? validFrom; + final String? validUntil; + final String? error; + + VoucherInfo({ + required this.valid, + this.voucherCode, + this.discountAmount, + this.voucherType, + this.priorityLevel, + this.usageCount, + this.maxUsage, + this.isUnlimited, + this.remainingUses, + this.validFrom, + this.validUntil, + this.error, + }); + + factory VoucherInfo.fromJson(Map json) { + return VoucherInfo( + valid: json['valid'] as bool? ?? false, + voucherCode: json['voucher_code'] as String?, + discountAmount: (json['discount_amount'] as num?)?.toDouble(), + voucherType: json['voucher_type'] as String?, + priorityLevel: json['priority_level'] as int?, + usageCount: json['usage_count'] as int?, + maxUsage: json['max_usage'] as int?, + isUnlimited: json['is_unlimited'] as bool?, + remainingUses: json['remaining_uses'] as int?, + validFrom: json['valid_from'] as String?, + validUntil: json['valid_until'] as String?, + error: json['error'] as String?, + ); + } +} + +class VipRedemptionResult { + final int ticketId; + final String ticketNumber; + final String externalCustomerKey; + final String voucherCode; + + VipRedemptionResult({ + required this.ticketId, + required this.ticketNumber, + required this.externalCustomerKey, + required this.voucherCode, + }); + + factory VipRedemptionResult.fromJson(Map json) { + return VipRedemptionResult( + ticketId: json['ticket_id'] is int + ? json['ticket_id'] as int + : int.parse(json['ticket_id'].toString()), + ticketNumber: json['ticket_number'] as String, + externalCustomerKey: json['external_customer_key'] as String, + voucherCode: json['voucher_code'] as String, + ); + } +} diff --git a/lib/services/shopinbit/src/models/webhook_event.dart b/lib/services/shopinbit/src/models/webhook_event.dart new file mode 100644 index 0000000000..7bf41694e8 --- /dev/null +++ b/lib/services/shopinbit/src/models/webhook_event.dart @@ -0,0 +1,28 @@ +enum WebhookEventType { + ticketStateChanged('ticket.state_changed'), + ticketMessageCreated('ticket.message_created'); + + final String value; + const WebhookEventType(this.value); + + static WebhookEventType fromString(String s) { + return WebhookEventType.values.firstWhere( + (e) => e.value == s, + orElse: () => WebhookEventType.ticketStateChanged, + ); + } +} + +class WebhookEvent { + final WebhookEventType eventType; + final Map data; + + WebhookEvent({required this.eventType, required this.data}); + + factory WebhookEvent.fromJson(Map json) { + return WebhookEvent( + eventType: WebhookEventType.fromString(json['event_type'] as String), + data: json['data'] as Map, + ); + } +} diff --git a/lib/services/shopinbit/src/token_manager.dart b/lib/services/shopinbit/src/token_manager.dart new file mode 100644 index 0000000000..a72d8135a0 --- /dev/null +++ b/lib/services/shopinbit/src/token_manager.dart @@ -0,0 +1,99 @@ +import 'dart:async'; +import 'dart:convert'; + +import '../../../app_config.dart'; +import '../../../networking/http.dart'; +import '../../../utilities/logger.dart'; +import '../../../utilities/prefs.dart'; +import '../../tor_service.dart'; +import 'api_exception.dart'; +import 'models/auth_token.dart'; + +class TokenManager { + final String accessKey; + final String partnerSecret; + final String baseUrl; + final HTTP _httpClient; + + AuthToken? _token; + Completer? _refreshCompleter; + + TokenManager({ + required this.accessKey, + required this.partnerSecret, + required this.baseUrl, + HTTP? httpClient, + }) : _httpClient = httpClient ?? const HTTP(); + + Future getValidToken() { + if (_token != null && !_token!.expiresSoon) { + return Future.value(_token!.accessToken); + } + + if (_refreshCompleter != null) { + return _refreshCompleter!.future; + } + + final completer = Completer(); + _refreshCompleter = completer; + + _authenticate() + .then((token) { + _token = token; + completer.complete(token.accessToken); + }) + .catchError((Object e) { + completer.completeError(e); + }) + .whenComplete(() { + _refreshCompleter = null; + }); + + return completer.future; + } + + Future _authenticate() async { + final uri = Uri.parse('$baseUrl/token'); + Logging.instance.t("ShopInBitClient POST $uri (authenticate)"); + + final Response response; + try { + final formBody = Uri( + queryParameters: {'username': accessKey, 'password': partnerSecret}, + ).query; + response = await _httpClient.post( + url: uri, + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + body: formBody, + proxyInfo: !AppConfig.hasFeature(AppFeature.tor) + ? null + : Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, + ); + } catch (e, s) { + Logging.instance.e( + "ShopInBitClient authenticate() network error: ", + error: e, + stackTrace: s, + ); + throw ApiException.network(e); + } + + if (response.code != 200) { + Logging.instance.w( + "ShopInBitClient authenticate() HTTP:${response.code} " + "body: ${response.body}", + ); + throw ApiException.fromResponse(response.code, response.body); + } + + Logging.instance.t("ShopInBitClient authenticate() success"); + final json = jsonDecode(response.body) as Map; + return AuthToken.fromJson(json); + } + + void invalidate() { + _token = null; + } +} diff --git a/lib/services/shopinbit/src/webhook_verifier.dart b/lib/services/shopinbit/src/webhook_verifier.dart new file mode 100644 index 0000000000..a596a3f398 --- /dev/null +++ b/lib/services/shopinbit/src/webhook_verifier.dart @@ -0,0 +1,53 @@ +import 'dart:convert'; + +import 'package:crypto/crypto.dart'; + +class WebhookVerifier { + /// Verify a webhook delivery from ShopInBit. + /// + /// [body] is the raw request body. + /// [signatureHeader] is the `X-Concierge-Signature` header value, + /// formatted as `t=,v1=`. + /// [secret] is the subscription secret. + /// [toleranceSeconds] is the max age of the timestamp (default 300 = 5 min). + static bool verify( + String body, + String signatureHeader, + String secret, { + int toleranceSeconds = 300, + }) { + final parts = {}; + for (final segment in signatureHeader.split(',')) { + final idx = segment.indexOf('='); + if (idx == -1) continue; + parts[segment.substring(0, idx)] = segment.substring(idx + 1); + } + + final timestampStr = parts['t']; + final v1 = parts['v1']; + if (timestampStr == null || v1 == null) return false; + + final timestamp = int.tryParse(timestampStr); + if (timestamp == null) return false; + + // Check timestamp freshness. + final now = DateTime.now().millisecondsSinceEpoch ~/ 1000; + if ((now - timestamp).abs() > toleranceSeconds) return false; + + // Compute HMAC-SHA256 of ".". + final payload = '$timestampStr.$body'; + final key = utf8.encode(secret); + final bytes = utf8.encode(payload); + final hmac = Hmac(sha256, key); + final digest = hmac.convert(bytes); + final expected = digest.toString(); + + // Constant-time comparison. + if (expected.length != v1.length) return false; + var result = 0; + for (var i = 0; i < expected.length; i++) { + result |= expected.codeUnitAt(i) ^ v1.codeUnitAt(i); + } + return result == 0; + } +} diff --git a/lib/utilities/default_sol_tokens.dart b/lib/utilities/default_sol_tokens.dart index fbc69a7ac7..65f11bd362 100644 --- a/lib/utilities/default_sol_tokens.dart +++ b/lib/utilities/default_sol_tokens.dart @@ -20,12 +20,12 @@ abstract class DefaultSolTokens { "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v/logo.png", ), SolContract( - address: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenEst", + address: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", name: "Tether", symbol: "USDT", decimals: 6, logoUri: - "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenEst/logo.svg", + "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB/logo.svg", ), SolContract( address: "MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac", @@ -36,20 +36,20 @@ abstract class DefaultSolTokens { "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac/logo.png", ), SolContract( - address: "SRMuApVgqbCmmp3uVrwpad5p4stLBUq3nSoSnqQQXmk", + address: "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt", name: "Serum", symbol: "SRM", decimals: 6, logoUri: - "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/SRMuApVgqbCmmp3uVrwpad5p4stLBUq3nSoSnqQQXmk/logo.png", + "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt/logo.png", ), SolContract( - address: "orca8TvxvggsCKvVPXSHXDvKgJ3bNroWusDawg461mpD", + address: "orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE", name: "Orca", symbol: "ORCA", decimals: 6, logoUri: - "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/orcaEKTdK7LKz57chYcSKdBI6qrE5dS1zG4FqHWGcKc/logo.svg", + "https://raw.githubusercontent.com/solana-labs/token-list/main/assets/mainnet/orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE/logo.png", ), ]; } diff --git a/lib/utilities/logger.dart b/lib/utilities/logger.dart index b5ae5f00dc..44c537c871 100644 --- a/lib/utilities/logger.dart +++ b/lib/utilities/logger.dart @@ -137,8 +137,8 @@ class Logging { ), toFile, )); - } catch (e, s) { - t("Isolates suck", error: e, stackTrace: s); + } catch (_) { + // swallow: logger not initialized (e.g. tests); avoid recursive logging } } diff --git a/lib/utilities/prefs.dart b/lib/utilities/prefs.dart index 2f057de12c..20308539ea 100644 --- a/lib/utilities/prefs.dart +++ b/lib/utilities/prefs.dart @@ -728,7 +728,7 @@ class Prefs extends ChangeNotifier { Future _getLastAutoBackup() async { return await DB.instance.get( boxName: DB.boxNamePrefs, - key: "autoBackupFileUri", + key: "lastAutoBackup", ) as DateTime?; } diff --git a/lib/utilities/test_node_connection.dart b/lib/utilities/test_node_connection.dart index c6b5ee00dd..1a94e23f39 100644 --- a/lib/utilities/test_node_connection.dart +++ b/lib/utilities/test_node_connection.dart @@ -29,6 +29,31 @@ import 'test_mwcmqs_connection.dart'; import 'test_stellar_node_connection.dart'; import 'tor_plain_net_option_enum.dart'; +typedef TestNodeConnectionCallback = + Future Function({ + required BuildContext context, + required NodeFormData nodeFormData, + required CryptoCurrency cryptoCurrency, + void Function(NodeFormData)? onSuccess, + }); + +final testNodeConnectionProvider = Provider((ref) { + return ({ + required BuildContext context, + required NodeFormData nodeFormData, + required CryptoCurrency cryptoCurrency, + void Function(NodeFormData)? onSuccess, + }) { + return testNodeConnection( + context: context, + nodeFormData: nodeFormData, + cryptoCurrency: cryptoCurrency, + read: ref.read, + onSuccess: onSuccess, + ); + }; +}); + Future _xmrHelper( NodeFormData nodeFormData, BuildContext context, @@ -93,12 +118,12 @@ Future testNodeConnection({ required BuildContext context, required NodeFormData nodeFormData, required CryptoCurrency cryptoCurrency, - required WidgetRef ref, + required Reader read, void Function(NodeFormData)? onSuccess, }) async { final formData = nodeFormData; - if (ref.read(prefsChangeNotifierProvider).useTor) { + if (read(prefsChangeNotifierProvider).useTor) { if (formData.netOption! == TorPlainNetworkOption.clear) { Logging.instance.w( "This node is configured for non-TOR only but TOR is enabled", @@ -147,8 +172,8 @@ Future testNodeConnection({ try { final proxyInfo = !AppConfig.hasFeature(AppFeature.tor) ? null - : ref.read(prefsChangeNotifierProvider).useTor - ? ref.read(pTorService).getProxyInfo() + : read(prefsChangeNotifierProvider).useTor + ? read(pTorService).getProxyInfo() : null; final url = formData.host!; @@ -200,8 +225,8 @@ Future testNodeConnection({ host: formData.host!, port: formData.port!, useSSL: formData.useSSL!, - overridePrefs: ref.read(prefsChangeNotifierProvider), - overrideTorService: ref.read(pTorService), + overridePrefs: read(prefsChangeNotifierProvider), + overrideTorService: read(pTorService), ); } catch (_) { testPassed = false; @@ -236,8 +261,8 @@ Future testNodeConnection({ body: jsonEncode({"action": "version"}), proxyInfo: !AppConfig.hasFeature(AppFeature.tor) ? null - : ref.read(prefsChangeNotifierProvider).useTor - ? ref.read(pTorService).getProxyInfo() + : read(prefsChangeNotifierProvider).useTor + ? read(pTorService).getProxyInfo() : null, ); @@ -259,8 +284,8 @@ Future testNodeConnection({ formData.host!, formData.port!, formData.useSSL ?? false, - ref.read(prefsChangeNotifierProvider), - ref.read(pTorService), + read(prefsChangeNotifierProvider), + read(pTorService), ); final health = await rpcClient.getHealth(); @@ -275,7 +300,7 @@ Future testNodeConnection({ try { final client = HttpClient(); if (AppConfig.hasFeature(AppFeature.tor) && - ref.read(prefsChangeNotifierProvider).useTor) { + read(prefsChangeNotifierProvider).useTor) { final proxyInfo = TorService.sharedInstance.getProxyInfo(); final proxySettings = ProxySettings(proxyInfo.host, proxyInfo.port); SocksTCPClient.assignToHttpClient(client, [proxySettings]); diff --git a/lib/wallets/crypto_currency/coins/particl.dart b/lib/wallets/crypto_currency/coins/particl.dart index 2b07aad7ea..a631e94e4f 100644 --- a/lib/wallets/crypto_currency/coins/particl.dart +++ b/lib/wallets/crypto_currency/coins/particl.dart @@ -233,7 +233,7 @@ class Particl extends Bip39HDCurrency with ElectrumXCurrencyInterface { } @override - int get transactionVersion => 1; + int get transactionVersion => 160; @override BigInt get defaultFeeRate => BigInt.from(20000); diff --git a/lib/wallets/wallet/impl/litecoin_wallet.dart b/lib/wallets/wallet/impl/litecoin_wallet.dart index c9fa52a330..32cfe8fe6b 100644 --- a/lib/wallets/wallet/impl/litecoin_wallet.dart +++ b/lib/wallets/wallet/impl/litecoin_wallet.dart @@ -35,6 +35,9 @@ class LitecoinWallet @override int get isarTransactionVersion => 2; + @override + String get ordServerBaseUrl => 'https://ord-litecoin.stackwallet.com'; + LitecoinWallet(CryptoCurrencyNetwork network) : super(Litecoin(network) as T); @override @@ -86,9 +89,7 @@ class LitecoinWallet // Remove duplicates. final allAddressesSet = {...receivingAddresses, ...changeAddresses}; - final updateInscriptionsFuture = refreshInscriptions( - overrideAddressesToCheck: allAddressesSet.toList(), - ); + final updateInscriptionsFuture = refreshInscriptions(); // Fetch history from ElectrumX. final List> allTxHashes = await fetchHistory( diff --git a/lib/wallets/wallet/impl/particl_wallet.dart b/lib/wallets/wallet/impl/particl_wallet.dart index 6f2b9764ea..65bc9c4c74 100644 --- a/lib/wallets/wallet/impl/particl_wallet.dart +++ b/lib/wallets/wallet/impl/particl_wallet.dart @@ -73,34 +73,40 @@ class ParticlWallet String? blockedReason; String? utxoLabel; + // Only check the specific output this UTXO corresponds to, not all outputs. + final vout = jsonUTXO["tx_pos"] as int; final outputs = jsonTX["vout"] as List? ?? []; - for (final output in outputs) { - if (output is Map) { - if (output['ct_fee'] != null) { - // Blind output, ignore for now. - blocked = true; - blockedReason = "Blind output."; - utxoLabel = "Unsupported output type."; - } else if (output['rangeproof'] != null) { - // Private RingCT output, ignore for now. - blocked = true; - blockedReason = "Confidential output."; - utxoLabel = "Unsupported output type."; - } else if (output['data_hex'] != null) { - // Data output, ignore for now. + // Use Map? because ElectrumX returns _Map. + Map? output; + for (final o in outputs) { + if (o is Map && o["n"] == vout) { + output = o; + break; + } + } + + if (output != null) { + if (output['ct_fee'] != null) { + blocked = true; + blockedReason = "Blind output."; + utxoLabel = "Unsupported output type."; + } else if (output['rangeproof'] != null) { + blocked = true; + blockedReason = "Confidential output."; + utxoLabel = "Unsupported output type."; + } else if (output['data_hex'] != null) { + blocked = true; + blockedReason = "Data output."; + utxoLabel = "Unsupported output type."; + } else if (output['scriptPubKey'] != null) { + if (output['scriptPubKey']?['asm'] is String && + (output['scriptPubKey']['asm'] as String).contains( + "OP_ISCOINSTAKE", + )) { blocked = true; - blockedReason = "Data output."; + blockedReason = "Spending staking"; utxoLabel = "Unsupported output type."; - } else if (output['scriptPubKey'] != null) { - if (output['scriptPubKey']?['asm'] is String && - (output['scriptPubKey']['asm'] as String).contains( - "OP_ISCOINSTAKE", - )) { - blocked = true; - blockedReason = "Spending staking"; - utxoLabel = "Unsupported output type."; - } } } } @@ -237,17 +243,12 @@ class ParticlWallet addresses.addAll(prevOut.addresses); } - InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor( - scriptSigHex: map["scriptSig"]?["hex"] as String?, - scriptSigAsm: map["scriptSig"]?["asm"] as String?, - sequence: map["sequence"] as int?, + InputV2 input = InputV2.fromElectrumxJson( + json: map, outpoint: outpoint, - valueStringSats: valueStringSats, addresses: addresses, - witness: map["witness"] as String?, + valueStringSats: valueStringSats, coinbase: coinbase, - innerRedeemScriptAsm: map["innerRedeemscriptAsm"] as String?, - // Need addresses before we can know if the wallet owns this input. walletOwns: false, ); @@ -454,7 +455,7 @@ class ParticlWallet tempInputs.add( InputV2.isarCantDoRequiredInDefaultConstructor( - scriptSigHex: txb.inputs.first.script?.toHex, + scriptSigHex: txb.inputs[i].script?.toHex, scriptSigAsm: null, sequence: 0xffffffff - 1, outpoint: OutpointV2.isarCantDoRequiredInDefaultConstructor( @@ -511,6 +512,7 @@ class ParticlWallet ), witnessValue: insAndKeys[i].utxo.value, redeemScript: extraData[i].redeem, + isParticl: true, overridePrefix: cryptoCurrency.networkParams.bech32Hrp, ); } @@ -526,30 +528,8 @@ class ParticlWallet final builtTx = txb.build(cryptoCurrency.networkParams.bech32Hrp); final vSize = builtTx.virtualSize(); - // Strip trailing 0x00 bytes from hex. - // - // This is done to match the previous particl_wallet implementation. - // TODO: [prio=low] Rework Particl tx construction so as to obviate this. - String hexString = builtTx.toHex(isParticl: true).toString(); - if (hexString.length % 2 != 0) { - // Ensure the string has an even length. - Logging.instance.e( - "Hex string has odd length, which is unexpected.", - stackTrace: StackTrace.current, - ); - throw Exception("Invalid hex string length."); - } - // int maxStrips = 3; // Strip up to 3 0x00s (match previous particl_wallet). - while (hexString.endsWith('00') && hexString.length > 2) { - hexString = hexString.substring(0, hexString.length - 2); - // maxStrips--; - // if (maxStrips <= 0) { - // break; - // } - } - return txData.copyWith( - raw: hexString, + raw: builtTx.toHex(isParticl: true), vSize: vSize, tempTx: null, // builtTx.getId() requires an isParticl flag as well but the lib does not support that yet diff --git a/lib/wallets/wallet/impl/sub_wallets/solana_token_wallet.dart b/lib/wallets/wallet/impl/sub_wallets/solana_token_wallet.dart index a311e05fd4..5fb99a19a6 100644 --- a/lib/wallets/wallet/impl/sub_wallets/solana_token_wallet.dart +++ b/lib/wallets/wallet/impl/sub_wallets/solana_token_wallet.dart @@ -510,7 +510,8 @@ class SolanaTokenWallet extends Wallet { (e) => e.containsKey("parsed") && e["program"] == "spl-token" && - e["parsed"]["type"] == "transferChecked", + (e["parsed"]["type"] == "transferChecked" || + e["parsed"]["type"] == "transfer"), ); if (splTransfers.length != 1) { @@ -522,9 +523,17 @@ class SolanaTokenWallet extends Wallet { continue; } final transfer = splTransfers.first; - final lamports = BigInt.parse( - transfer["parsed"]["info"]["tokenAmount"]["amount"].toString(), - ); + final transferType = transfer["parsed"]["type"] as String; + final BigInt lamports; + if (transferType == "transferChecked") { + lamports = BigInt.parse( + transfer["parsed"]["info"]["tokenAmount"]["amount"].toString(), + ); + } else { + lamports = BigInt.parse( + transfer["parsed"]["info"]["amount"].toString(), + ); + } final senderAddress = transfer["parsed"]["info"]["source"] as String; final receiverAddress = transfer["parsed"]["info"]["destination"] as String; diff --git a/lib/wallets/wallet/intermediate/lib_xelis_wallet.dart b/lib/wallets/wallet/intermediate/lib_xelis_wallet.dart index 0cfac6d1bb..eb818021a6 100644 --- a/lib/wallets/wallet/intermediate/lib_xelis_wallet.dart +++ b/lib/wallets/wallet/intermediate/lib_xelis_wallet.dart @@ -169,7 +169,7 @@ abstract class LibXelisWallet await _eventSubscription?.cancel(); _eventSubscription = null; - if (wallet != null) { + if (wallet != null && await libXelis.isOnline(wallet!)) { await libXelis.offlineMode(wallet!); } await super.exit(); diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/mweb_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/mweb_interface.dart index e183be63b6..bfeb24e72f 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/mweb_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/mweb_interface.dart @@ -14,6 +14,7 @@ import '../../../models/input.dart'; import '../../../models/isar/models/blockchain_data/v2/output_v2.dart'; import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart'; import '../../../models/isar/models/isar_models.dart'; +import '../../../models/isar/ordinal.dart'; import '../../../services/event_bus/events/global/blocks_remaining_event.dart'; import '../../../services/event_bus/events/global/refresh_percent_changed_event.dart'; import '../../../services/event_bus/events/global/wallet_sync_status_changed_event.dart'; @@ -649,6 +650,20 @@ mixin MwebInterface ), ); + // Never peg ordinal UTXOs into MWEB. + spendableUtxos.removeWhere((e) { + final ord = mainDB.isar.ordinals + .where() + .filter() + .walletIdEqualTo(walletId) + .and() + .utxoTXIDEqualTo(e.txid) + .and() + .utxoVOUTEqualTo(e.vout) + .findFirstSync(); + return ord != null; + }); + if (spendableUtxos.isEmpty) { throw Exception("No available UTXOs found to anonymize"); } diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart index 686d1f90a9..f9165fb697 100644 --- a/lib/wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart +++ b/lib/wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart @@ -1,53 +1,79 @@ import 'package:isar_community/isar.dart'; import '../../../dto/ordinals/inscription_data.dart'; +import '../../../models/input.dart'; +import '../../../models/isar/models/blockchain_data/address.dart'; import '../../../models/isar/models/blockchain_data/utxo.dart'; import '../../../models/isar/ordinal.dart'; -import '../../../services/litescribe_api.dart'; +import '../../../services/ord_api.dart'; +import '../../../utilities/amount/amount.dart'; +import '../../../utilities/enums/fee_rate_type_enum.dart'; import '../../../utilities/logger.dart'; import '../../crypto_currency/interfaces/electrumx_currency_interface.dart'; +import '../../models/tx_data.dart'; import 'electrumx_interface.dart'; mixin OrdinalsInterface on ElectrumXInterface { - final LitescribeAPI _litescribeAPI = LitescribeAPI( - baseUrl: 'https://litescribe.io/api', - ); + /// Subclasses must provide the base URL for their ord server. + /// e.g. 'https://ord-litecoin.stackwallet.com' + String get ordServerBaseUrl; - // check if an inscription is in a given output - Future _inscriptionInAddress(String address) async { + late final OrdAPI _ordAPI = OrdAPI(baseUrl: ordServerBaseUrl); + + /// Check whether a specific output contains inscriptions. + Future _inscriptionInOutput(String txid, int vout) async { try { - return (await _litescribeAPI.getInscriptionsByAddress( - address, - )).isNotEmpty; + final ids = await _ordAPI.getInscriptionIdsForOutput(txid, vout); + return ids.isNotEmpty; } catch (e, s) { - Logging.instance.e("Litescribe api failure!", error: e, stackTrace: s); - + Logging.instance.e( + "Ord API output check failure!", + error: e, + stackTrace: s, + ); return false; } } - Future refreshInscriptions({ - List? overrideAddressesToCheck, - }) async { + Future refreshInscriptions() async { try { - final uniqueAddresses = - overrideAddressesToCheck ?? - await mainDB - .getUTXOs(walletId) - .filter() - .addressIsNotNull() - .distinctByAddress() - .addressProperty() - .findAll(); - final inscriptions = await _getInscriptionDataFromAddresses( - uniqueAddresses.cast(), - ); + final utxos = await mainDB.getUTXOs(walletId).findAll(); + + final List allInscriptions = []; + + for (final utxo in utxos) { + try { + final ids = await _ordAPI.getInscriptionIdsForOutput( + utxo.txid, + utxo.vout, + ); + + for (final inscriptionId in ids) { + try { + final json = await _ordAPI.getInscriptionData(inscriptionId); + allInscriptions.add( + InscriptionData.fromOrdJson( + json, + _ordAPI.contentUrl(inscriptionId), + ), + ); + } catch (e) { + Logging.instance.w( + "Failed to fetch inscription $inscriptionId: $e", + ); + } + } + } catch (e) { + Logging.instance.w( + "Failed to check output ${utxo.txid}:${utxo.vout}: $e", + ); + } + } - final ords = - inscriptions - .map((e) => Ordinal.fromInscriptionData(e, walletId)) - .toList(); + final ords = allInscriptions + .map((e) => Ordinal.fromInscriptionData(e, walletId)) + .toList(); await mainDB.isar.writeTxn(() async { await mainDB.isar.ordinals @@ -65,6 +91,70 @@ mixin OrdinalsInterface ); } } + + /// Build a transaction that sends the ordinal UTXO to [recipientAddress]. + /// + /// Uses coin-control send-all from the single ordinal UTXO so the ordinal + /// (at input offset 0) lands on the only output (the recipient) via FIFO. + /// If the UTXO value can't cover the fee, an exception is thrown. + Future prepareOrdinalSend({ + required UTXO ordinalUtxo, + required String recipientAddress, + FeeRateType feeRateType = FeeRateType.average, + }) async { + // Temporarily unblock so coinSelection accepts it. + final wasBlocked = ordinalUtxo.isBlocked; + // utxoForTx is the in-memory object passed to coinSelection; it must have + // isBlocked=false or the spendable-outputs filter will reject it. + UTXO utxoForTx = ordinalUtxo; + if (wasBlocked) { + final unblocked = ordinalUtxo.copyWith( + isBlocked: false, + blockedReason: null, + ); + unblocked.id = ordinalUtxo.id; + await mainDB.putUTXO(unblocked); + utxoForTx = unblocked; + } + + try { + final utxoValue = Amount( + rawValue: BigInt.from(ordinalUtxo.value), + fractionDigits: cryptoCurrency.fractionDigits, + ); + + final txData = TxData( + feeRateType: feeRateType, + recipients: [ + TxRecipient( + address: recipientAddress, + amount: utxoValue, + isChange: false, + addressType: + cryptoCurrency.getAddressType(recipientAddress) ?? + AddressType.unknown, + ), + ], + utxos: {StandardInput(utxoForTx)}, + ignoreCachedBalanceChecks: true, + note: + "Send ordinal #${(await mainDB.isar.ordinals.where().filter().walletIdEqualTo(walletId).and().utxoTXIDEqualTo(ordinalUtxo.txid).and().utxoVOUTEqualTo(ordinalUtxo.vout).findFirst())?.inscriptionNumber ?? "unknown"}", + ); + + return await prepareSend(txData: txData); + } finally { + // Re-block regardless of success or failure. + if (wasBlocked) { + final reblocked = ordinalUtxo.copyWith( + isBlocked: true, + blockedReason: "Ordinal", + ); + reblocked.id = ordinalUtxo.id; + await mainDB.putUTXO(reblocked); + } + } + } + // =================== Overrides ============================================= @override @@ -79,58 +169,20 @@ mixin OrdinalsInterface String? blockReason; String? label; + final txid = jsonTX["txid"] as String; + final vout = jsonUTXO["tx_pos"] as int; final utxoAmount = jsonUTXO["value"] as int; - // TODO: [prio=med] check following 3 todos - - // TODO check the specific output, not just the address in general - // TODO optimize by freezing output in OrdinalsInterface, so one ordinal API calls is made (or at least many less) - if (utxoOwnerAddress != null && - await _inscriptionInAddress(utxoOwnerAddress)) { + if (await _inscriptionInOutput(txid, vout)) { shouldBlock = true; blockReason = "Ordinal"; - label = "Ordinal detected at address"; - } else { - // TODO implement inscriptionInOutput - if (utxoAmount <= 10000) { - shouldBlock = true; - blockReason = "May contain ordinal"; - label = "Possible ordinal"; - } + label = "Ordinal detected at output"; + } else if (utxoAmount <= 10000) { + shouldBlock = true; + blockReason = "May contain ordinal"; + label = "Possible ordinal"; } return (blockedReason: blockReason, blocked: shouldBlock, utxoLabel: label); } - - @override - Future updateUTXOs() async { - final newUtxosAdded = await super.updateUTXOs(); - if (newUtxosAdded) { - try { - await refreshInscriptions(); - } catch (_) { - // do nothing but do not block/fail this updateUTXOs call based on litescribe call failures - } - } - - return newUtxosAdded; - } - - // ===================== Private ============================================= - Future> _getInscriptionDataFromAddresses( - List addresses, - ) async { - final List allInscriptions = []; - for (final String address in addresses) { - try { - final inscriptions = await _litescribeAPI.getInscriptionsByAddress( - address, - ); - allInscriptions.addAll(inscriptions); - } catch (e) { - throw Exception("Error fetching inscriptions for address $address: $e"); - } - } - return allInscriptions; - } } diff --git a/lib/widgets/node_card.dart b/lib/widgets/node_card.dart index 85f16a1354..7d7a829698 100644 --- a/lib/widgets/node_card.dart +++ b/lib/widgets/node_card.dart @@ -59,8 +59,10 @@ class _NodeCardState extends ConsumerState { bool _advancedIsExpanded = false; Future _notifyWalletsOfUpdatedNode(WidgetRef ref) async { - final wallets = - ref.read(pWallets).wallets.where((e) => e.info.coin == widget.coin); + final wallets = ref + .read(pWallets) + .wallets + .where((e) => e.info.coin == widget.coin); final prefs = ref.read(prefsChangeNotifierProvider); switch (prefs.syncType) { @@ -100,12 +102,14 @@ class _NodeCardState extends ConsumerState { @override Widget build(BuildContext context) { final node = ref.watch( - nodeServiceChangeNotifierProvider - .select((value) => value.getPrimaryNodeFor(currency: widget.coin)), + nodeServiceChangeNotifierProvider.select( + (value) => value.getPrimaryNodeFor(currency: widget.coin), + ), ); final _node = ref.watch( - nodeServiceChangeNotifierProvider - .select((value) => value.getNodeById(id: nodeId)), + nodeServiceChangeNotifierProvider.select( + (value) => value.getNodeById(id: nodeId), + ), )!; if (node?.name == _node.name) { @@ -155,14 +159,10 @@ class _NodeCardState extends ConsumerState { }, header: child, body: Padding( - padding: const EdgeInsets.only( - bottom: 24, - ), + padding: const EdgeInsets.only(bottom: 24), child: Row( children: [ - const SizedBox( - width: 66, - ), + const SizedBox(width: 66), CustomTextButton( text: "Connect", enabled: _status == "Disconnected", @@ -190,12 +190,12 @@ class _NodeCardState extends ConsumerState { ); if (context.mounted) { - final canConnect = await testNodeConnection( - context: context, - nodeFormData: nodeFormData, - cryptoCurrency: widget.coin, - ref: ref, - ); + final canConnect = + await ref.read(testNodeConnectionProvider)( + context: context, + nodeFormData: nodeFormData, + cryptoCurrency: widget.coin, + ); if (!canConnect) { if (context.mounted) { @@ -223,9 +223,7 @@ class _NodeCardState extends ConsumerState { } }, ), - const SizedBox( - width: 48, - ), + const SizedBox(width: 48), CustomTextButton( text: "Details", onTap: () { @@ -253,13 +251,13 @@ class _NodeCardState extends ConsumerState { height: isDesktop ? 40 : 24, decoration: BoxDecoration( color: _node.id.startsWith(DefaultNodes.defaultNodeIdPrefix) - ? Theme.of(context) - .extension()! - .buttonBackSecondary + ? Theme.of( + context, + ).extension()!.buttonBackSecondary : Theme.of(context) - .extension()! - .infoItemIcons - .withOpacity(0.2), + .extension()! + .infoItemIcons + .withOpacity(0.2), borderRadius: BorderRadius.circular(100), ), child: Center( @@ -269,32 +267,22 @@ class _NodeCardState extends ConsumerState { width: isDesktop ? 20 : 14, color: _node.id.startsWith(DefaultNodes.defaultNodeIdPrefix) - ? Theme.of(context) - .extension()! - .accentColorDark - : Theme.of(context) - .extension()! - .infoItemIcons, + ? Theme.of( + context, + ).extension()!.accentColorDark + : Theme.of( + context, + ).extension()!.infoItemIcons, ), ), ), - const SizedBox( - width: 12, - ), + const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - _node.name, - style: STextStyles.titleBold12(context), - ), - const SizedBox( - height: 2, - ), - Text( - _status, - style: STextStyles.label(context), - ), + Text(_node.name, style: STextStyles.titleBold12(context)), + const SizedBox(height: 2), + Text(_status, style: STextStyles.label(context)), ], ), const Spacer(), @@ -302,12 +290,12 @@ class _NodeCardState extends ConsumerState { SvgPicture.asset( Assets.svg.network, color: _status == "Connected" - ? Theme.of(context) - .extension()! - .accentColorGreen - : Theme.of(context) - .extension()! - .buttonBackSecondary, + ? Theme.of( + context, + ).extension()!.accentColorGreen + : Theme.of( + context, + ).extension()!.buttonBackSecondary, width: 20, height: 20, ), @@ -318,9 +306,9 @@ class _NodeCardState extends ConsumerState { : Assets.svg.chevronDown, width: 12, height: 6, - color: Theme.of(context) - .extension()! - .textSubtitle1, + color: Theme.of( + context, + ).extension()!.textSubtitle1, ), ], ), diff --git a/lib/widgets/node_options_sheet.dart b/lib/widgets/node_options_sheet.dart index 511be23958..201b802cbe 100644 --- a/lib/widgets/node_options_sheet.dart +++ b/lib/widgets/node_options_sheet.dart @@ -44,8 +44,10 @@ class NodeOptionsSheet extends ConsumerWidget { final String popBackToRoute; Future _notifyWalletsOfUpdatedNode(WidgetRef ref) async { - final wallets = - ref.read(pWallets).wallets.where((e) => e.info.coin == coin); + final wallets = ref + .read(pWallets) + .wallets + .where((e) => e.info.coin == coin); final prefs = ref.read(prefsChangeNotifierProvider); switch (prefs.syncType) { @@ -80,11 +82,13 @@ class NodeOptionsSheet extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final maxHeight = MediaQuery.of(context).size.height * 0.60; final node = ref.watch( - nodeServiceChangeNotifierProvider - .select((value) => value.getNodeById(id: nodeId)), + nodeServiceChangeNotifierProvider.select( + (value) => value.getNodeById(id: nodeId), + ), )!; - final status = ref + final status = + ref .watch( nodeServiceChangeNotifierProvider.select( (value) => value.getPrimaryNodeFor(currency: coin), @@ -98,9 +102,7 @@ class NodeOptionsSheet extends ConsumerWidget { return Container( decoration: BoxDecoration( color: Theme.of(context).extension()!.popupBG, - borderRadius: const BorderRadius.vertical( - top: Radius.circular(20), - ), + borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), ), child: LimitedBox( maxHeight: maxHeight, @@ -119,9 +121,9 @@ class NodeOptionsSheet extends ConsumerWidget { Center( child: Container( decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, + color: Theme.of( + context, + ).extension()!.textFieldDefaultBG, borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, ), @@ -130,9 +132,7 @@ class NodeOptionsSheet extends ConsumerWidget { height: 4, ), ), - const SizedBox( - height: 36, - ), + const SizedBox(height: 36), Text( "Node options", style: STextStyles.pageTitleH2(context), @@ -146,15 +146,17 @@ class NodeOptionsSheet extends ConsumerWidget { width: 32, height: 32, decoration: BoxDecoration( - color: node.id - .startsWith(DefaultNodes.defaultNodeIdPrefix) - ? Theme.of(context) - .extension()! - .textSubtitle4 + color: + node.id.startsWith( + DefaultNodes.defaultNodeIdPrefix, + ) + ? Theme.of( + context, + ).extension()!.textSubtitle4 : Theme.of(context) - .extension()! - .infoItemIcons - .withOpacity(0.2), + .extension()! + .infoItemIcons + .withOpacity(0.2), borderRadius: BorderRadius.circular(100), ), child: Center( @@ -162,21 +164,20 @@ class NodeOptionsSheet extends ConsumerWidget { Assets.svg.node, height: 15, width: 19, - color: node.id.startsWith( - DefaultNodes.defaultNodeIdPrefix, - ) - ? Theme.of(context) - .extension()! - .accentColorDark - : Theme.of(context) - .extension()! - .infoItemIcons, + color: + node.id.startsWith( + DefaultNodes.defaultNodeIdPrefix, + ) + ? Theme.of( + context, + ).extension()!.accentColorDark + : Theme.of( + context, + ).extension()!.infoItemIcons, ), ), ), - const SizedBox( - width: 12, - ), + const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -184,25 +185,20 @@ class NodeOptionsSheet extends ConsumerWidget { node.name, style: STextStyles.titleBold12(context), ), - const SizedBox( - height: 2, - ), - Text( - status, - style: STextStyles.label(context), - ), + const SizedBox(height: 2), + Text(status, style: STextStyles.label(context)), ], ), const Spacer(), SvgPicture.asset( Assets.svg.network, color: status == "Connected" - ? Theme.of(context) - .extension()! - .accentColorGreen - : Theme.of(context) - .extension()! - .buttonBackSecondary, + ? Theme.of( + context, + ).extension()!.accentColorGreen + : Theme.of( + context, + ).extension()!.buttonBackSecondary, width: 18, ), ], @@ -220,36 +216,30 @@ class NodeOptionsSheet extends ConsumerWidget { Navigator.pop(context); Navigator.of(context).pushNamed( NodeDetailsView.routeName, - arguments: Tuple3( - coin, - node.id, - popBackToRoute, - ), + arguments: Tuple3(coin, node.id, popBackToRoute), ); }, child: Text( "Details", style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark, + color: Theme.of( + context, + ).extension()!.accentColorDark, ), ), ), ), // if (!node.id.startsWith("default")) - const SizedBox( - width: 12, - ), + const SizedBox(width: 12), Expanded( child: TextButton( style: status == "Connected" ? Theme.of(context) - .extension()! - .getPrimaryDisabledButtonStyle(context) + .extension()! + .getPrimaryDisabledButtonStyle(context) : Theme.of(context) - .extension()! - .getPrimaryEnabledButtonStyle(context), + .extension()! + .getPrimaryEnabledButtonStyle(context), onPressed: status == "Connected" ? null : () async { @@ -267,21 +257,23 @@ class NodeOptionsSheet extends ConsumerWidget { } else { netOption = TorPlainNetworkOption.both; } - final canConnect = await testNodeConnection( - context: context, - nodeFormData: NodeFormData() - ..name = node.name - ..host = node.host - ..login = node.loginName - ..password = pw - ..port = node.port - ..useSSL = node.useSSL - ..isFailover = node.isFailover - ..netOption = netOption - ..trusted = node.trusted, - cryptoCurrency: coin, - ref: ref, - ); + final canConnect = + await ref.read( + testNodeConnectionProvider, + )( + context: context, + nodeFormData: NodeFormData() + ..name = node.name + ..host = node.host + ..login = node.loginName + ..password = pw + ..port = node.port + ..useSSL = node.useSSL + ..isFailover = node.isFailover + ..netOption = netOption + ..trusted = node.trusted, + cryptoCurrency: coin, + ); if (!canConnect) { return; } @@ -306,9 +298,7 @@ class NodeOptionsSheet extends ConsumerWidget { ), ], ), - const SizedBox( - height: 24, - ), + const SizedBox(height: 24), ], ), ), diff --git a/lib/widgets/ordinal_image.dart b/lib/widgets/ordinal_image.dart new file mode 100644 index 0000000000..abad5bd0c1 --- /dev/null +++ b/lib/widgets/ordinal_image.dart @@ -0,0 +1,81 @@ +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; + +import '../app_config.dart'; +import '../networking/http.dart'; +import '../utilities/prefs.dart'; +import '../services/tor_service.dart'; + +/// Fetches and displays an image through the app's HTTP client, +/// respecting Tor proxy settings. Use this instead of [Image.network] +/// when the request must route through Tor. +class OrdinalImage extends StatefulWidget { + const OrdinalImage({ + super.key, + required this.url, + this.fit = BoxFit.cover, + this.filterQuality = FilterQuality.none, + }); + + final String url; + final BoxFit fit; + final FilterQuality filterQuality; + + @override + State createState() => _OrdinalImageState(); +} + +class _OrdinalImageState extends State { + late Future _future; + + @override + void initState() { + super.initState(); + _future = _fetchImage(); + } + + @override + void didUpdateWidget(OrdinalImage oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.url != widget.url) { + _future = _fetchImage(); + } + } + + Future _fetchImage() async { + final response = await const HTTP().get( + url: Uri.parse(widget.url), + proxyInfo: !AppConfig.hasFeature(AppFeature.tor) + ? null + : Prefs.instance.useTor + ? TorService.sharedInstance.getProxyInfo() + : null, + ); + + if (response.code != 200) { + throw Exception('Failed to load image: status=${response.code}'); + } + + return Uint8List.fromList(response.bodyBytes); + } + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: _future, + builder: (context, snapshot) { + if (snapshot.hasData) { + return Image.memory( + snapshot.data!, + fit: widget.fit, + filterQuality: widget.filterQuality, + ); + } else if (snapshot.hasError) { + return const Center(child: Icon(Icons.broken_image)); + } + return const Center(child: CircularProgressIndicator()); + }, + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 0aedf78678..9f4b4df5f0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d + sha256: "0eb33edbbe99a02e73b8bbeb6f2b65972023d902117ee8d1bf0ea1a79f83aa7b" url: "https://pub.dev" source: hosted - version: "91.0.0" + version: "90.0.0" analyzer: dependency: "direct dev" description: name: analyzer - sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08 + sha256: "711e3a890bb529bf55f07d73b8706f4b7504ad77e90d2f205626b116c048583f" url: "https://pub.dev" source: hosted - version: "8.4.1" + version: "8.3.0" another_flushbar: dependency: "direct main" description: @@ -37,10 +37,10 @@ packages: dependency: "direct main" description: name: archive - sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff url: "https://pub.dev" source: hosted - version: "4.0.7" + version: "4.0.9" args: dependency: transitive description: @@ -114,8 +114,8 @@ packages: dependency: "direct main" description: path: "." - ref: "7145be16bb88cffbd53326f7fa4570e414be09e4" - resolved-ref: "7145be16bb88cffbd53326f7fa4570e414be09e4" + ref: b02aaf6c6b40fd5a6b3d77f875324717103f2019 + resolved-ref: b02aaf6c6b40fd5a6b3d77f875324717103f2019 url: "https://github.com/cypherstack/bitcoindart.git" source: git version: "3.0.2" @@ -227,10 +227,10 @@ packages: dependency: transitive description: name: built_value - sha256: "426cf75afdb23aa74bd4e471704de3f9393f3c7b04c1e2d9c6f1073ae0b8b139" + sha256: "6ae8a6435a8c6520c7077b107e77f1fb4ba7009633259a4d49a8afd8e7efc5e9" url: "https://pub.dev" source: hosted - version: "8.12.1" + version: "8.12.4" calendar_date_picker2: dependency: "direct main" description: @@ -277,10 +277,10 @@ packages: dependency: "direct main" description: name: cbor - sha256: f5239dd6b6ad24df67d1449e87d7180727d6f43b87b3c9402e6398c7a2d9609b + sha256: "2c5c37650f0a2d25149f03e748ab7b2857787bde338f95fe947738b80d713da2" url: "https://pub.dev" source: hosted - version: "6.3.7" + version: "6.5.1" characters: dependency: transitive description: @@ -329,14 +329,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" + url: "https://pub.dev" + source: hosted + version: "1.0.0" code_builder: dependency: transitive description: name: code_builder - sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243" + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" url: "https://pub.dev" source: hosted - version: "4.11.0" + version: "4.11.1" coinlib: dependency: "direct overridden" description: @@ -408,10 +416,10 @@ packages: dependency: transitive description: name: cross_file - sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608" + sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" url: "https://pub.dev" source: hosted - version: "0.3.5+1" + version: "0.3.5+2" crypto: dependency: "direct main" description: @@ -734,7 +742,7 @@ packages: source: hosted version: "0.0.6" dart_style: - dependency: transitive + dependency: "direct overridden" description: name: dart_style sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b @@ -753,10 +761,10 @@ packages: dependency: transitive description: name: dbus - sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.12" decimal: dependency: "direct main" description: @@ -769,10 +777,10 @@ packages: dependency: "direct dev" description: name: dependency_validator - sha256: a5928c0e3773808027bdafeb13fb4be0e4fdd79819773ad3df34d0fcf42636f2 + sha256: d6084f8df7677843c8fd0e08b66c11d9c2ce9bae1bb1f18cc574bcb28ebe71b0 url: "https://pub.dev" source: hosted - version: "5.0.3" + version: "5.0.5" desktop_drop: dependency: "direct main" description: @@ -818,34 +826,34 @@ packages: dependency: transitive description: name: dio - sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 + sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c url: "https://pub.dev" source: hosted - version: "5.9.0" + version: "5.9.2" dio_web_adapter: dependency: transitive description: name: dio_web_adapter - sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" drift: dependency: "direct main" description: name: drift - sha256: "3669e1b68d7bffb60192ac6ba9fd2c0306804d7a00e5879f6364c69ecde53a7f" + sha256: "970cd188fddb111b26ea6a9b07a62bf5c2432d74147b8122c67044ae3b97e99e" url: "https://pub.dev" source: hosted - version: "2.30.0" + version: "2.31.0" drift_dev: dependency: "direct dev" description: name: drift_dev - sha256: afe4d1d2cfce6606c86f11a6196e974a2ddbfaa992956ce61e054c9b1899c769 + sha256: "917184b2fb867b70a548a83bf0d36268423b38d39968c06cce4905683da49587" url: "https://pub.dev" source: hosted - version: "2.30.0" + version: "2.31.0" drift_flutter: dependency: "direct main" description: @@ -907,10 +915,10 @@ packages: dependency: "direct main" description: name: equatable - sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.0.8" ethereum_addresses: dependency: "direct main" description: @@ -940,10 +948,10 @@ packages: dependency: "direct main" description: name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" file: dependency: transitive description: @@ -956,10 +964,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: f2d9f173c2c14635cc0e9b14c143c49ef30b4934e8d1d274d6206fcb0086a06f + sha256: "57d9a1dd5063f85fa3107fb42d1faffda52fdc948cefd5fe5ea85267a5fc7343" url: "https://pub.dev" source: hosted - version: "10.3.3" + version: "10.3.10" fixnum: dependency: "direct main" description: @@ -1069,10 +1077,10 @@ packages: dependency: "direct main" description: name: flutter_native_splash - sha256: "17d9671396fb8ec45ad10f4a975eb8a0f70bedf0fdaf0720b31ea9de6da8c4da" + sha256: "4fb9f4113350d3a80841ce05ebf1976a36de622af7d19aca0ca9a9911c7ff002" url: "https://pub.dev" source: hosted - version: "2.3.7" + version: "2.4.7" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -1149,10 +1157,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95" + sha256: "1ded017b39c8e15c8948ea855070a5ff8ff8b3d5e83f3446e02d6bb12add7ad9" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.2.4" flutter_test: dependency: "direct dev" description: flutter @@ -1167,10 +1175,10 @@ packages: dependency: "direct overridden" description: name: freezed - sha256: "03dd9b7423ff0e31b7e01b2204593e5e1ac5ee553b6ea9d8184dff4a26b9fb07" + sha256: f23ea33b3863f119b58ed1b586e881a46bd28715ddcc4dbc33104524e3434131 url: "https://pub.dev" source: hosted - version: "3.2.4" + version: "3.2.5" freezed_annotation: dependency: "direct overridden" description: @@ -1220,10 +1228,10 @@ packages: dependency: "direct main" description: name: google_fonts - sha256: "517b20870220c48752eafa0ba1a797a092fb22df0d89535fd9991e86ee2cdd9c" + sha256: ba03d03bcaa2f6cb7bd920e3b5027181db75ab524f8891c8bc3aa603885b8055 url: "https://pub.dev" source: hosted - version: "6.3.2" + version: "6.3.3" google_identity_services_web: dependency: transitive description: @@ -1252,10 +1260,10 @@ packages: dependency: transitive description: name: grpc - sha256: "2dde469ddd8bbd7a33a0765da417abe1ad2142813efce3a86c512041294e2b26" + sha256: "15227eeed339bd0ef5afe515cb791b2e4bec0711ab56f37cc44257bcfaedc4bf" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.2.0" hex: dependency: "direct main" description: @@ -1276,18 +1284,18 @@ packages: dependency: "direct main" description: name: hive_ce - sha256: "81d39a03c4c0ba5938260a8c3547d2e71af59defecea21793d57fc3551f0d230" + sha256: "8e9980e68643afb1e765d3af32b47996552a64e190d03faf622cea07c1294418" url: "https://pub.dev" source: hosted - version: "2.15.1" + version: "2.19.3" hive_ce_flutter: dependency: "direct main" description: name: hive_ce_flutter - sha256: "26d656c9e8974f0732f1d09020e2d7b08ba841b8961a02dbfb6caf01474b0e9a" + sha256: "2677e95a333ff15af43ccd06af7eb7abbf1a4f154ea071997f3de4346cae913a" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.3.4" hive_ce_generator: dependency: "direct dev" description: @@ -1304,6 +1312,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + hooks: + dependency: transitive + description: + name: hooks + sha256: e79ed1e8e1929bc6ecb6ec85f0cb519c887aa5b423705ded0d0f2d9226def388 + url: "https://pub.dev" + source: hosted + version: "1.0.2" html: dependency: transitive description: @@ -1344,22 +1360,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" - ieee754: - dependency: transitive - description: - name: ieee754 - sha256: "7d87451c164a56c156180d34a4e93779372edd191d2c219206100b976203128c" - url: "https://pub.dev" - source: hosted - version: "1.0.3" image: dependency: "direct main" description: name: image - sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" + sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce url: "https://pub.dev" source: hosted - version: "4.5.4" + version: "4.8.0" import_sorter: dependency: "direct dev" description: @@ -1417,10 +1425,10 @@ packages: dependency: transitive description: name: isolate_channel - sha256: f3d36f783b301e6b312c3450eeb2656b0e7d1db81331af2a151d9083a3f6b18d + sha256: a9d3d620695bc984244dafae00b95e4319d6974b2d77f4b9e1eb4f2efe099094 url: "https://pub.dev" source: hosted - version: "0.2.2+1" + version: "0.6.1" js: dependency: transitive description: @@ -1441,10 +1449,10 @@ packages: dependency: "direct overridden" description: name: json_rpc_2 - sha256: "3c46c2633aec07810c3d6a2eb08d575b5b4072980db08f1344e66aeb53d6e4a7" + sha256: "82dfd37d3b2e5030ae4729e1d7f5538cbc45eb1c73d618b9272931facac3bec1" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.1.0" json_serializable: dependency: transitive description: @@ -1627,10 +1635,10 @@ packages: dependency: "direct dev" description: name: mockito - sha256: dac24d461418d363778d53198d9ac0510b9d073869f078450f195766ec48d05e + sha256: a45d1aa065b796922db7b9e7e7e45f921aed17adf3a8318a1f47097e7e695566 url: "https://pub.dev" source: hosted - version: "5.6.1" + version: "5.6.3" mocktail: dependency: transitive description: @@ -1681,6 +1689,14 @@ packages: url: "https://github.com/cypherstack/nanodart" source: git version: "2.0.1" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" + url: "https://pub.dev" + source: hosted + version: "0.17.6" nm: dependency: transitive description: @@ -1697,6 +1713,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" + url: "https://pub.dev" + source: hosted + version: "9.3.0" on_chain: dependency: "direct main" description: @@ -1765,10 +1789,10 @@ packages: dependency: transitive description: name: path_provider_foundation - sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" url: "https://pub.dev" source: hosted - version: "2.5.1" + version: "2.6.0" path_provider_linux: dependency: transitive description: @@ -1845,10 +1869,10 @@ packages: dependency: transitive description: name: petitparser - sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "7.0.2" pinenacl: dependency: transitive description: @@ -1893,10 +1917,10 @@ packages: dependency: transitive description: name: posix - sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" + sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" url: "https://pub.dev" source: hosted - version: "6.0.3" + version: "6.5.0" pretty_dio_logger: dependency: transitive description: @@ -1949,10 +1973,10 @@ packages: dependency: "direct main" description: name: qr_code_scanner_plus - sha256: b764e5004251c58d9dee0c295e6006e05bd8d249e78ac3383abdb5afe0a996cd + sha256: dae0596b2763c2fd0294f5cfddb1d3a21577ae4dc7fc1449eb5aafc957872f61 url: "https://pub.dev" source: hosted - version: "2.0.14" + version: "2.1.1" qr_flutter: dependency: "direct main" description: @@ -2139,10 +2163,10 @@ packages: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" url: "https://pub.dev" source: hosted - version: "1.10.1" + version: "1.10.2" sqlite3: dependency: "direct main" description: @@ -2163,10 +2187,10 @@ packages: dependency: transitive description: name: sqlparser - sha256: "162435ede92bcc793ea939fdc0452eef0a73d11f8ed053b58a89792fba749da5" + sha256: "337e9997f7141ffdd054259128553c348635fa318f7ca492f07a4ab76f850d19" url: "https://pub.dev" source: hosted - version: "0.42.1" + version: "0.43.1" stack_trace: dependency: transitive description: @@ -2196,10 +2220,10 @@ packages: dependency: "direct main" description: name: stellar_flutter_sdk - sha256: eb07752e11c6365ee59a666f7a95964f761ec05250b0cecaf14698ebc66b09b0 + sha256: d3a7a38e262d7d96f2650a09d15fe831ef1686cb5b2f07feebbe0e3bfceceaf5 url: "https://pub.dev" source: hosted - version: "2.1.8" + version: "2.2.2" stream_channel: dependency: "direct main" description: @@ -2285,10 +2309,10 @@ packages: dependency: transitive description: name: time - sha256: "370572cf5d1e58adcb3e354c47515da3f7469dac3a95b447117e728e7be6f461" + sha256: "46187cf30bffdab28c56be9a63861b36e4ab7347bf403297595d6a97e10c789f" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.1.6" timezone: dependency: transitive description: @@ -2317,10 +2341,10 @@ packages: dependency: transitive description: name: toml - sha256: d968d149c8bd06dc14e09ea3a140f90a3f2ba71949e7a91df4a46f3107400e71 + sha256: "35cd2a1351c14bd213f130f8efcbd3e0c18181bff0c8ca7a08f6822a2bede786" url: "https://pub.dev" source: hosted - version: "0.16.0" + version: "0.17.0" tor_ffi_plugin: dependency: "direct main" description: @@ -2382,10 +2406,10 @@ packages: dependency: transitive description: name: url_launcher_ios - sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" url: "https://pub.dev" source: hosted - version: "6.3.6" + version: "6.4.1" url_launcher_linux: dependency: transitive description: @@ -2414,10 +2438,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" url_launcher_windows: dependency: transitive description: @@ -2430,18 +2454,18 @@ packages: dependency: "direct main" description: name: uuid - sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 + sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" url: "https://pub.dev" source: hosted - version: "4.5.2" + version: "4.5.3" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 + sha256: "7076216a10d5c390315fbe536a30f1254c341e7543e6c4c8a815e591307772b1" url: "https://pub.dev" source: hosted - version: "1.1.19" + version: "1.1.20" vector_graphics_codec: dependency: transitive description: @@ -2454,10 +2478,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc + sha256: "5a88dd14c0954a5398af544651c7fb51b457a2a556949bfb25369b210ef73a74" url: "https://pub.dev" source: hosted - version: "1.1.19" + version: "1.2.0" vector_math: dependency: transitive description: @@ -2470,10 +2494,10 @@ packages: dependency: transitive description: name: very_good_analysis - sha256: "96245839dbcc45dfab1af5fa551603b5c7a282028a64746c19c547d21a7f1e3a" + sha256: "27927d1140ce1b140f998b6340f730a626faa5b95110b3e34a238ff254d731d0" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.1.0" vm_service: dependency: transitive description: @@ -2502,10 +2526,10 @@ packages: dependency: transitive description: name: wakelock_plus_platform_interface - sha256: "036deb14cd62f558ca3b73006d52ce049fabcdcb2eddfe0bf0fe4e8a943b5cf2" + sha256: "24b84143787220a403491c2e5de0877fbbb87baf3f0b18a2a988973863db4b03" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" wakelock_windows: dependency: "direct overridden" description: @@ -2535,10 +2559,10 @@ packages: dependency: transitive description: name: watcher - sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a" + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" url: "https://pub.dev" source: hosted - version: "1.1.4" + version: "1.2.1" web: dependency: "direct overridden" description: @@ -2641,10 +2665,10 @@ packages: dependency: transitive description: name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" url: "https://pub.dev" source: hosted - version: "6.5.0" + version: "6.6.1" xxh3: dependency: transitive description: @@ -2686,5 +2710,5 @@ packages: source: hosted version: "0.2.4" sdks: - dart: ">=3.10.0 <4.0.0" - flutter: ">=3.38.1 <4.0.0" + dart: ">=3.10.3 <4.0.0" + flutter: ">=3.38.4 <4.0.0" diff --git a/scripts/app_config/templates/pubspec.template.yaml b/scripts/app_config/templates/pubspec.template.yaml index 6b551d4c48..535d7f5598 100644 --- a/scripts/app_config/templates/pubspec.template.yaml +++ b/scripts/app_config/templates/pubspec.template.yaml @@ -95,7 +95,7 @@ dependencies: bitcoindart: git: url: https://github.com/cypherstack/bitcoindart.git - ref: 7145be16bb88cffbd53326f7fa4570e414be09e4 + ref: b02aaf6c6b40fd5a6b3d77f875324717103f2019 stack_wallet_backup: git: @@ -325,6 +325,12 @@ dependency_overrides: url: https://github.com/cypherstack/bip47.git ref: 3ef6b94375d7b4d972b0bc0bd9597532381a88ec + # bip47 pins a different bitcoindart commit; override to ours + bitcoindart: + git: + url: https://github.com/cypherstack/bitcoindart.git + ref: b02aaf6c6b40fd5a6b3d77f875324717103f2019 + # required for dart 3, at least until a fix is merged upstream wakelock_windows: git: @@ -341,7 +347,8 @@ dependency_overrides: # required to override solana's lower version decimal: ^3.2.4 - analyzer: ^8.2.0 + # pin analyzer below 8.4.0 to avoid source_gen 3.1.0 incompatibility (getInvocation() removed in 8.4.0+) + analyzer: ">=8.2.0 <8.4.0" # xelis override json_rpc_2: ^4.0.0 diff --git a/scripts/ensure_test_app_config.sh b/scripts/ensure_test_app_config.sh new file mode 100755 index 0000000000..c9dccc3399 --- /dev/null +++ b/scripts/ensure_test_app_config.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +# shellcheck disable=SC1091 +source "${SCRIPT_DIR}/env.sh" + +APP_CONFIG_DART_FILE="${APP_PROJECT_ROOT_DIR}/lib/app_config.g.dart" + +if test -f "$APP_CONFIG_DART_FILE"; then + echo 'ensure_test_app_config.sh: verified lib/app_config.g.dart' + exit 0 +fi + +BUILT_COMMIT_HASH="$(git -C "${APP_PROJECT_ROOT_DIR}" log -1 --pretty=format:%H 2>/dev/null || true)" + +cat > "$APP_CONFIG_DART_FILE" < _features = { + AppFeature.themeSelection, + AppFeature.buy, + AppFeature.tor, + AppFeature.swap +}; + +const ({String light, String dark})? _appIconAsset = null; + +final List _supportedCoins = List.unmodifiable([ + Bitcoin(CryptoCurrencyNetwork.main), + Monero(CryptoCurrencyNetwork.main), + Banano(CryptoCurrencyNetwork.main), + Bitcoincash(CryptoCurrencyNetwork.main), + BitcoinFrost(CryptoCurrencyNetwork.main), + Cardano(CryptoCurrencyNetwork.main), + Dash(CryptoCurrencyNetwork.main), + Dogecoin(CryptoCurrencyNetwork.main), + Ecash(CryptoCurrencyNetwork.main), + Epiccash(CryptoCurrencyNetwork.main), + Ethereum(CryptoCurrencyNetwork.main), + Fact0rn(CryptoCurrencyNetwork.main), + Firo(CryptoCurrencyNetwork.main), + Litecoin(CryptoCurrencyNetwork.main), + if (!Platform.isMacOS) Mimblewimblecoin(CryptoCurrencyNetwork.main), + Nano(CryptoCurrencyNetwork.main), + Namecoin(CryptoCurrencyNetwork.main), + Particl(CryptoCurrencyNetwork.main), + Peercoin(CryptoCurrencyNetwork.main), + Salvium(CryptoCurrencyNetwork.main), + Solana(CryptoCurrencyNetwork.main), + Stellar(CryptoCurrencyNetwork.main), + Tezos(CryptoCurrencyNetwork.main), + Wownero(CryptoCurrencyNetwork.main), + Xelis(CryptoCurrencyNetwork.main), + Bitcoin(CryptoCurrencyNetwork.test), + Bitcoin(CryptoCurrencyNetwork.test4), + Bitcoincash(CryptoCurrencyNetwork.test), + BitcoinFrost(CryptoCurrencyNetwork.test), + BitcoinFrost(CryptoCurrencyNetwork.test4), + Dogecoin(CryptoCurrencyNetwork.test), + Firo(CryptoCurrencyNetwork.test), + Litecoin(CryptoCurrencyNetwork.test), + Peercoin(CryptoCurrencyNetwork.test), + Salvium(CryptoCurrencyNetwork.test), + Stellar(CryptoCurrencyNetwork.test), + Xelis(CryptoCurrencyNetwork.test), +]); + +final ({String from, String fromFuzzyNet, String to, String toFuzzyNet}) +_swapDefaults = ( + from: "BTC", + fromFuzzyNet: "btc", + to: "XMR", + toFuzzyNet: "xmr", +); +EOF + +echo 'ensure_test_app_config.sh: created lib/app_config.g.dart' diff --git a/scripts/prebuild.ps1 b/scripts/prebuild.ps1 index 5dd966d6cd..a3acbdede3 100644 --- a/scripts/prebuild.ps1 +++ b/scripts/prebuild.ps1 @@ -2,7 +2,7 @@ $KEYS = "..\lib\external_api_keys.dart" if (-not (Test-Path $KEYS)) { Write-Host "prebuild.ps1: creating template lib/external_api_keys.dart file" - "const kChangeNowApiKey = '';" + "`nconst kSimpleSwapApiKey = '';" + "`nconst kNanswapApiKey = '';" + "`nconst kNanoSwapRpcApiKey = '';" + "`nconst kWizSwapApiKey = '';" | Out-File $KEYS -Encoding UTF8 + "const kChangeNowApiKey = '';" + "`nconst kSimpleSwapApiKey = '';" + "`nconst kNanswapApiKey = '';" + "`nconst kNanoSwapRpcApiKey = '';" + "`nconst kWizSwapApiKey = '';" + "`nconst kShopInBitAccessKey = '';" + "`nconst kShopInBitPartnerSecret = '';" + "`nconst kCakePayApiToken = '';" | Out-File $KEYS -Encoding UTF8 } # Create template wallet test parameter files if they don't already exist diff --git a/scripts/prebuild.sh b/scripts/prebuild.sh index 44d4e0921c..0aa13ea223 100755 --- a/scripts/prebuild.sh +++ b/scripts/prebuild.sh @@ -4,7 +4,7 @@ KEYS=../lib/external_api_keys.dart if ! test -f "$KEYS"; then echo 'prebuild.sh: creating template lib/external_api_keys.dart file' - printf 'const kChangeNowApiKey = "";\nconst kSimpleSwapApiKey = "";\nconst kNanswapApiKey = "";\nconst kNanoSwapRpcApiKey = "";\nconst kWizSwapApiKey = "";\n' > $KEYS + printf 'const kChangeNowApiKey = "";\nconst kSimpleSwapApiKey = "";\nconst kNanswapApiKey = "";\nconst kNanoSwapRpcApiKey = "";\nconst kWizSwapApiKey = "";\nconst kShopInBitAccessKey = "";\nconst kShopInBitPartnerSecret = "";\nconst kCakePayApiToken = "";\n' > $KEYS fi # Create template wallet test parameter files if they don't already exist diff --git a/test/cached_electrumx_test.dart b/test/cached_electrumx_test.dart index 370e029727..0e5bf0b551 100644 --- a/test/cached_electrumx_test.dart +++ b/test/cached_electrumx_test.dart @@ -1,5 +1,4 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:hive_test/hive_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx_client.dart'; @@ -8,13 +7,14 @@ import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart'; import 'cached_electrumx_test.mocks.dart'; +import 'hive/hive_ce_test_utils.dart'; // import 'sample_data/get_anonymity_set_sample_data.dart'; @GenerateMocks([ElectrumXClient, Prefs]) void main() { group("tests using mock hive", () { setUp(() async { - await setUpTestHive(); + await setUpHiveCeTest(); // await Hive.openBox( // DB.instance.boxNameUsedSerialsCache(coin: Coin.firo)); // await Hive.openBox(DB.instance.boxNameSetCache(coin: Coin.firo)); @@ -117,24 +117,17 @@ void main() { test("getTransaction throws", () async { final client = MockElectrumXClient(); - when( - client.getTransaction( - txHash: "some hash", - ), - ).thenThrow(Exception()); + when(client.getTransaction(txHash: "some hash")).thenThrow(Exception()); - final cachedClient = CachedElectrumXClient( - electrumXClient: client, - ); + final cachedClient = CachedElectrumXClient(electrumXClient: client); expect( - () async => await cachedClient.getTransaction( - txHash: "some hash", - cryptoCurrency: Firo( - CryptoCurrencyNetwork.main, - ), - ), - throwsA(isA())); + () async => await cachedClient.getTransaction( + txHash: "some hash", + cryptoCurrency: Firo(CryptoCurrencyNetwork.main), + ), + throwsA(isA()), + ); }); test("clearSharedTransactionCache", () async { @@ -145,9 +138,7 @@ void main() { bool didThrow = false; try { await cachedClient.clearSharedTransactionCache( - cryptoCurrency: Firo( - CryptoCurrencyNetwork.main, - ), + cryptoCurrency: Firo(CryptoCurrencyNetwork.main), ); } catch (_) { didThrow = true; @@ -157,7 +148,7 @@ void main() { }); tearDown(() async { - await tearDownTestHive(); + await tearDownHiveCeTest(); }); }); @@ -172,8 +163,9 @@ void main() { clearnetEnabled: true, ); - final client = - CachedElectrumXClient.from(electrumXClient: MockElectrumXClient()); + final client = CachedElectrumXClient.from( + electrumXClient: MockElectrumXClient(), + ); expect(client, isA()); }); diff --git a/test/cached_electrumx_test.mocks.dart b/test/cached_electrumx_test.mocks.dart index 504934e682..a622b2f927 100644 --- a/test/cached_electrumx_test.mocks.dart +++ b/test/cached_electrumx_test.mocks.dart @@ -332,6 +332,22 @@ class MockElectrumXClient extends _i1.Mock implements _i6.ElectrumXClient { ) as _i9.Future>); + @override + _i9.Future>> getBatchTransactions({ + required List? txHashes, + String? requestID, + }) => + (super.noSuchMethod( + Invocation.method(#getBatchTransactions, [], { + #txHashes: txHashes, + #requestID: requestID, + }), + returnValue: _i9.Future>>.value( + >[], + ), + ) + as _i9.Future>>); + @override _i9.Future> getLelantusAnonymitySet({ String? groupId = '1', diff --git a/test/electrumx_test.dart b/test/electrumx_test.dart index 06ed6111d6..b8c82c8c18 100644 --- a/test/electrumx_test.dart +++ b/test/electrumx_test.dart @@ -1,1778 +1,613 @@ -// import 'dart:io'; -// -// import 'package:flutter_test/flutter_test.dart'; -// import 'package:mockito/annotations.dart'; -// import 'package:mockito/mockito.dart'; -// import 'package:stackwallet/electrumx_rpc/electrumx_client.dart'; -// import 'package:stackwallet/electrumx_rpc/rpc.dart'; -// import 'package:stackwallet/services/event_bus/events/global/tor_connection_status_changed_event.dart'; -// import 'package:stackwallet/services/tor_service.dart'; -// import 'package:stackwallet/utilities/prefs.dart'; -// -// import 'electrumx_test.mocks.dart'; -// import 'sample_data/get_anonymity_set_sample_data.dart'; -// import 'sample_data/get_used_serials_sample_data.dart'; -// import 'sample_data/transaction_data_samples.dart'; -// -// @GenerateMocks([JsonRPC, Prefs, TorService]) -// void main() { -// group("factory constructors and getters", () { -// test("electrumxnode .from factory", () { -// final nodeA = ElectrumXNode( -// address: "some address", -// port: 1, -// name: "some name", -// id: "some ID", -// useSSL: true, -// ); -// -// final nodeB = ElectrumXNode.from(nodeA); -// -// expect(nodeB.toString(), nodeA.toString()); -// expect(nodeA == nodeB, false); -// }); -// -// test("electrumx .from factory", () { -// final node = ElectrumXNode( -// address: "some address", -// port: 1, -// name: "some name", -// id: "some ID", -// useSSL: true, -// ); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// -// final client = ElectrumXClient.from( -// node: node, -// failovers: [], -// prefs: mockPrefs, -// torService: torService, -// ); -// -// expect(client.useSSL, node.useSSL); -// expect(client.host, node.address); -// expect(client.port, node.port); -// expect(client.rpcClient, null); -// -// verifyNoMoreInteractions(mockPrefs); -// }); -// }); -// -// test("Server error", () { -// final mockClient = MockJsonRPC(); -// const command = "blockchain.transaction.get"; -// const jsonArgs = '["",true]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenAnswer( -// (_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "error": { -// "code": 1, -// "message": "None should be a transaction hash", -// }, -// "id": "some requestId", -// }), -// ); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// failovers: [], -// prefs: mockPrefs, -// torService: torService, -// ); -// -// expect(() => client.getTransaction(requestID: "some requestId", txHash: ''), -// throwsA(isA())); -// -// verify(mockPrefs.wifiOnly).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// -// group("getBlockHeadTip", () { -// test("getBlockHeadTip success", () async { -// final mockClient = MockJsonRPC(); -// const command = "blockchain.headers.subscribe"; -// const jsonArgs = '[]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenAnswer( -// (_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "result": {"height": 520481, "hex": "some block hex string"}, -// "id": "some requestId" -// }), -// ); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// final result = -// await (client.getBlockHeadTip(requestID: "some requestId")); -// -// expect(result["height"], 520481); -// -// verify(mockPrefs.wifiOnly).called(1); -// verify(mockPrefs.useTor).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// -// test("getBlockHeadTip throws/fails", () { -// final mockClient = MockJsonRPC(); -// const command = "blockchain.headers.subscribe"; -// const jsonArgs = '[]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenThrow(Exception()); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// expect(() => client.getBlockHeadTip(requestID: "some requestId"), -// throwsA(isA())); -// -// verify(mockPrefs.wifiOnly).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// }); -// -// group("ping", () { -// test("ping success", () async { -// final mockClient = MockJsonRPC(); -// const command = "server.ping"; -// const jsonArgs = '[]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 2), -// ), -// ).thenAnswer( -// (_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "result": null, -// "id": "some requestId", -// }), -// ); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// final result = await client.ping(requestID: "some requestId"); -// -// expect(result, true); -// -// verify(mockPrefs.wifiOnly).called(1); -// verify(mockPrefs.useTor).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// -// test("ping throws/fails", () { -// final mockClient = MockJsonRPC(); -// const command = "server.ping"; -// const jsonArgs = '[]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 2), -// ), -// ).thenThrow(Exception()); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// expect(() => client.ping(requestID: "some requestId"), -// throwsA(isA())); -// -// verify(mockPrefs.wifiOnly).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// }); -// -// group("getServerFeatures", () { -// test("getServerFeatures success", () async { -// final mockClient = MockJsonRPC(); -// const command = "server.features"; -// const jsonArgs = '[]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenAnswer( -// (_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "result": { -// "genesis_hash": -// "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943", -// "hosts": { -// "0.0.0.0": {"tcp_port": 51001, "ssl_port": 51002} -// }, -// "protocol_max": "1.0", -// "protocol_min": "1.0", -// "pruning": null, -// "server_version": "ElectrumX 1.0.17", -// "hash_function": "sha256" -// }, -// "id": "some requestId" -// }), -// ); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// final result = -// await client.getServerFeatures(requestID: "some requestId"); -// -// expect(result, { -// "genesis_hash": -// "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943", -// "hosts": { -// "0.0.0.0": {"tcp_port": 51001, "ssl_port": 51002} -// }, -// "protocol_max": "1.0", -// "protocol_min": "1.0", -// "pruning": null, -// "server_version": "ElectrumX 1.0.17", -// "hash_function": "sha256", -// }); -// -// verify(mockPrefs.wifiOnly).called(1); -// verify(mockPrefs.useTor).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// -// test("getServerFeatures throws/fails", () { -// final mockClient = MockJsonRPC(); -// const command = "server.features"; -// const jsonArgs = '[]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenThrow(Exception()); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// expect(() => client.getServerFeatures(requestID: "some requestId"), -// throwsA(isA())); -// -// verify(mockPrefs.wifiOnly).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// }); -// -// group("broadcastTransaction", () { -// test("broadcastTransaction success", () async { -// final mockClient = MockJsonRPC(); -// const command = "blockchain.transaction.broadcast"; -// const jsonArgs = '["some raw transaction string"]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenAnswer( -// (_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "result": "the txid of the rawtx", -// "id": "some requestId" -// }), -// ); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// final result = await client.broadcastTransaction( -// rawTx: "some raw transaction string", requestID: "some requestId"); -// -// expect(result, "the txid of the rawtx"); -// -// verify(mockPrefs.wifiOnly).called(1); -// verify(mockPrefs.useTor).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// -// test("broadcastTransaction throws/fails", () { -// final mockClient = MockJsonRPC(); -// const command = "blockchain.transaction.broadcast"; -// const jsonArgs = '["some raw transaction string"]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenThrow(Exception()); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// expect( -// () => client.broadcastTransaction( -// rawTx: "some raw transaction string", -// requestID: "some requestId"), -// throwsA(isA())); -// -// verify(mockPrefs.wifiOnly).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// }); -// -// group("getBalance", () { -// test("getBalance success", () async { -// final mockClient = MockJsonRPC(); -// const command = "blockchain.scripthash.get_balance"; -// const jsonArgs = '["dummy hash"]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenAnswer( -// (_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "result": { -// "confirmed": 103873966, -// "unconfirmed": 23684400, -// }, -// "id": "some requestId" -// }), -// ); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// final result = await client.getBalance( -// scripthash: "dummy hash", requestID: "some requestId"); -// -// expect(result, {"confirmed": 103873966, "unconfirmed": 23684400}); -// -// verify(mockPrefs.wifiOnly).called(1); -// verify(mockPrefs.useTor).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// -// test("getBalance throws/fails", () { -// final mockClient = MockJsonRPC(); -// const command = "blockchain.scripthash.get_balance"; -// const jsonArgs = '["dummy hash"]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenThrow(Exception()); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// expect( -// () => client.getBalance( -// scripthash: "dummy hash", requestID: "some requestId"), -// throwsA(isA())); -// -// verify(mockPrefs.wifiOnly).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// }); -// -// group("getHistory", () { -// test("getHistory success", () async { -// final mockClient = MockJsonRPC(); -// const command = "blockchain.scripthash.get_history"; -// const jsonArgs = '["dummy hash"]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(minutes: 5), -// ), -// ).thenAnswer( -// (_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "result": [ -// { -// "height": 200004, -// "tx_hash": -// "acc3758bd2a26f869fcc67d48ff30b96464d476bca82c1cd6656e7d506816412" -// }, -// { -// "height": 215008, -// "tx_hash": -// "f3e1bf48975b8d6060a9de8884296abb80be618dc00ae3cb2f6cee3085e09403" -// } -// ], -// "id": "some requestId" -// }), -// ); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// final result = await client.getHistory( -// scripthash: "dummy hash", requestID: "some requestId"); -// -// expect(result, [ -// { -// "height": 200004, -// "tx_hash": -// "acc3758bd2a26f869fcc67d48ff30b96464d476bca82c1cd6656e7d506816412" -// }, -// { -// "height": 215008, -// "tx_hash": -// "f3e1bf48975b8d6060a9de8884296abb80be618dc00ae3cb2f6cee3085e09403" -// } -// ]); -// -// verify(mockPrefs.wifiOnly).called(1); -// verify(mockPrefs.useTor).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// -// test("getHistory throws/fails", () { -// final mockClient = MockJsonRPC(); -// const command = "blockchain.scripthash.get_history"; -// const jsonArgs = '["dummy hash"]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(minutes: 5), -// ), -// ).thenThrow(Exception()); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// expect( -// () => client.getHistory( -// scripthash: "dummy hash", requestID: "some requestId"), -// throwsA(isA())); -// -// verify(mockPrefs.wifiOnly).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// }); -// -// group("getUTXOs", () { -// test("getUTXOs success", () async { -// final mockClient = MockJsonRPC(); -// const command = "blockchain.scripthash.listunspent"; -// const jsonArgs = '["dummy hash"]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenAnswer( -// (_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "result": [ -// { -// "tx_pos": 0, -// "value": 45318048, -// "tx_hash": -// "9f2c45a12db0144909b5db269415f7319179105982ac70ed80d76ea79d923ebf", -// "height": 437146 -// }, -// { -// "tx_pos": 0, -// "value": 919195, -// "tx_hash": -// "3d2290c93436a3e964cfc2f0950174d8847b1fbe3946432c4784e168da0f019f", -// "height": 441696 -// } -// ], -// "id": "some requestId" -// }), -// ); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// final result = await client.getUTXOs( -// scripthash: "dummy hash", requestID: "some requestId"); -// -// expect(result, [ -// { -// "tx_pos": 0, -// "value": 45318048, -// "tx_hash": -// "9f2c45a12db0144909b5db269415f7319179105982ac70ed80d76ea79d923ebf", -// "height": 437146 -// }, -// { -// "tx_pos": 0, -// "value": 919195, -// "tx_hash": -// "3d2290c93436a3e964cfc2f0950174d8847b1fbe3946432c4784e168da0f019f", -// "height": 441696 -// } -// ]); -// -// verify(mockPrefs.wifiOnly).called(1); -// verify(mockPrefs.useTor).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// -// test("getUTXOs throws/fails", () { -// final mockClient = MockJsonRPC(); -// const command = "blockchain.scripthash.listunspent"; -// const jsonArgs = '["dummy hash"]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenThrow(Exception()); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// expect( -// () => client.getUTXOs( -// scripthash: "dummy hash", requestID: "some requestId"), -// throwsA(isA())); -// -// verify(mockPrefs.wifiOnly).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// }); -// -// group("getTransaction", () { -// test("getTransaction success", () async { -// final mockClient = MockJsonRPC(); -// const command = "blockchain.transaction.get"; -// const jsonArgs = '["${SampleGetTransactionData.txHash0}",true]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenAnswer( -// (_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "result": SampleGetTransactionData.txData0, -// "id": "some requestId" -// }), -// ); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// final result = await client.getTransaction( -// txHash: SampleGetTransactionData.txHash0, -// verbose: true, -// requestID: "some requestId"); -// -// expect(result, SampleGetTransactionData.txData0); -// -// verify(mockPrefs.wifiOnly).called(1); -// verify(mockPrefs.useTor).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// -// test("getTransaction throws/fails", () { -// final mockClient = MockJsonRPC(); -// const command = "blockchain.transaction.get"; -// const jsonArgs = '["${SampleGetTransactionData.txHash0}",true]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenThrow(Exception()); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// expect( -// () => client.getTransaction( -// txHash: SampleGetTransactionData.txHash0, -// requestID: "some requestId"), -// throwsA(isA())); -// -// verify(mockPrefs.wifiOnly).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// }); -// -// group("getAnonymitySet", () { -// test("getAnonymitySet success", () async { -// final mockClient = MockJsonRPC(); -// const command = "lelantus.getanonymityset"; -// const jsonArgs = '["1",""]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenAnswer( -// (_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "result": GetAnonymitySetSampleData.data, -// "id": "some requestId" -// }), -// ); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// final result = await client.getLelantusAnonymitySet( -// groupId: "1", blockhash: "", requestID: "some requestId"); -// -// expect(result, GetAnonymitySetSampleData.data); -// -// verify(mockPrefs.wifiOnly).called(1); -// verify(mockPrefs.useTor).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// -// test("getAnonymitySet throws/fails", () { -// final mockClient = MockJsonRPC(); -// const command = "lelantus.getanonymityset"; -// const jsonArgs = '["1",""]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenThrow(Exception()); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// expect( -// () => client.getLelantusAnonymitySet( -// groupId: "1", requestID: "some requestId"), -// throwsA(isA())); -// -// verify(mockPrefs.wifiOnly).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// }); -// -// group("getMintData", () { -// test("getMintData success", () async { -// final mockClient = MockJsonRPC(); -// const command = "lelantus.getmintmetadata"; -// const jsonArgs = '["some mints"]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenAnswer( -// (_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "result": "mint meta data", -// "id": "some requestId" -// }), -// ); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// final result = await client.getLelantusMintData( -// mints: "some mints", requestID: "some requestId"); -// -// expect(result, "mint meta data"); -// -// verify(mockPrefs.wifiOnly).called(1); -// verify(mockPrefs.useTor).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// -// test("getMintData throws/fails", () { -// final mockClient = MockJsonRPC(); -// const command = "lelantus.getmintmetadata"; -// const jsonArgs = '["some mints"]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenThrow(Exception()); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// expect( -// () => client.getLelantusMintData( -// mints: "some mints", requestID: "some requestId"), -// throwsA(isA())); -// -// verify(mockPrefs.wifiOnly).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// }); -// -// group("getUsedCoinSerials", () { -// test("getUsedCoinSerials success", () async { -// final mockClient = MockJsonRPC(); -// const command = "lelantus.getusedcoinserials"; -// const jsonArgs = '["0"]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(minutes: 2), -// ), -// ).thenAnswer( -// (_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "result": GetUsedSerialsSampleData.serials, -// "id": "some requestId" -// }), -// ); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// final result = await client.getLelantusUsedCoinSerials( -// requestID: "some requestId", startNumber: 0); -// -// expect(result, GetUsedSerialsSampleData.serials); -// -// verify(mockPrefs.wifiOnly).called(3); -// verify(mockPrefs.useTor).called(3); -// verifyNoMoreInteractions(mockPrefs); -// }); -// -// test("getUsedCoinSerials throws/fails", () { -// final mockClient = MockJsonRPC(); -// const command = "lelantus.getusedcoinserials"; -// const jsonArgs = '["0"]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(minutes: 2), -// ), -// ).thenThrow(Exception()); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// expect( -// () => client.getLelantusUsedCoinSerials( -// requestID: "some requestId", startNumber: 0), -// throwsA(isA())); -// -// verify(mockPrefs.wifiOnly).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// }); -// -// group("getLatestCoinId", () { -// test("getLatestCoinId success", () async { -// final mockClient = MockJsonRPC(); -// const command = "lelantus.getlatestcoinid"; -// const jsonArgs = '[]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenAnswer( -// (_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "result": 1, -// "id": "some requestId", -// }), -// ); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// final result = -// await client.getLelantusLatestCoinId(requestID: "some requestId"); -// -// expect(result, 1); -// -// verify(mockPrefs.wifiOnly).called(1); -// verify(mockPrefs.useTor).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// -// test("getLatestCoinId throws/fails", () { -// final mockClient = MockJsonRPC(); -// const command = "lelantus.getlatestcoinid"; -// const jsonArgs = '[]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenThrow(Exception()); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// expect( -// () => client.getLelantusLatestCoinId( -// requestID: "some requestId", -// ), -// throwsA(isA())); -// -// verify(mockPrefs.wifiOnly).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// }); -// -// group("getCoinsForRecovery", () { -// test("getCoinsForRecovery success", () async { -// final mockClient = MockJsonRPC(); -// const command = "lelantus.getanonymityset"; -// const jsonArgs = '["1",""]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenAnswer( -// (_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "result": GetAnonymitySetSampleData.data, -// "id": "some requestId" -// }), -// ); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// final result = await client.getLelantusAnonymitySet( -// groupId: "1", blockhash: "", requestID: "some requestId"); -// -// expect(result, GetAnonymitySetSampleData.data); -// -// verify(mockPrefs.wifiOnly).called(1); -// verify(mockPrefs.useTor).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// -// test("getAnonymitySet throws/fails", () { -// final mockClient = MockJsonRPC(); -// const command = "lelantus.getanonymityset"; -// const jsonArgs = '["1",""]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenThrow(Exception()); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// expect( -// () => client.getLelantusAnonymitySet( -// groupId: "1", -// requestID: "some requestId", -// ), -// throwsA(isA())); -// -// verify(mockPrefs.wifiOnly).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// }); -// -// group("getMintData", () { -// test("getMintData success", () async { -// final mockClient = MockJsonRPC(); -// const command = "lelantus.getmintmetadata"; -// const jsonArgs = '["some mints"]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenAnswer( -// (_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "result": "mint meta data", -// "id": "some requestId" -// }), -// ); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// final result = await client.getLelantusMintData( -// mints: "some mints", requestID: "some requestId"); -// -// expect(result, "mint meta data"); -// -// verify(mockPrefs.wifiOnly).called(1); -// verify(mockPrefs.useTor).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// -// test("getMintData throws/fails", () { -// final mockClient = MockJsonRPC(); -// const command = "lelantus.getmintmetadata"; -// const jsonArgs = '["some mints"]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenThrow(Exception()); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// expect( -// () => client.getLelantusMintData( -// mints: "some mints", -// requestID: "some requestId", -// ), -// throwsA(isA())); -// -// verify(mockPrefs.wifiOnly).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// }); -// -// group("getUsedCoinSerials", () { -// test("getUsedCoinSerials success", () async { -// final mockClient = MockJsonRPC(); -// const command = "lelantus.getusedcoinserials"; -// const jsonArgs = '["0"]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(minutes: 2), -// ), -// ).thenAnswer( -// (_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "result": GetUsedSerialsSampleData.serials, -// "id": "some requestId" -// }), -// ); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// final result = await client.getLelantusUsedCoinSerials( -// requestID: "some requestId", startNumber: 0); -// -// expect(result, GetUsedSerialsSampleData.serials); -// -// verify(mockPrefs.wifiOnly).called(3); -// verify(mockPrefs.useTor).called(3); -// verifyNoMoreInteractions(mockPrefs); -// }); -// -// test("getUsedCoinSerials throws/fails", () { -// final mockClient = MockJsonRPC(); -// const command = "lelantus.getusedcoinserials"; -// const jsonArgs = '["0"]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(minutes: 2), -// ), -// ).thenThrow(Exception()); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// expect( -// () => client.getLelantusUsedCoinSerials( -// requestID: "some requestId", startNumber: 0), -// throwsA(isA())); -// -// verify(mockPrefs.wifiOnly).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// }); -// -// group("getLatestCoinId", () { -// test("getLatestCoinId success", () async { -// final mockClient = MockJsonRPC(); -// const command = "lelantus.getlatestcoinid"; -// const jsonArgs = '[]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenAnswer( -// (_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "result": 1, -// "id": "some requestId", -// }), -// ); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// final result = -// await client.getLelantusLatestCoinId(requestID: "some requestId"); -// -// expect(result, 1); -// -// verify(mockPrefs.wifiOnly).called(1); -// verify(mockPrefs.useTor).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// -// test("getLatestCoinId throws/fails", () { -// final mockClient = MockJsonRPC(); -// const command = "lelantus.getlatestcoinid"; -// const jsonArgs = '[]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenThrow(Exception()); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// expect(() => client.getLelantusLatestCoinId(requestID: "some requestId"), -// throwsA(isA())); -// -// verify(mockPrefs.wifiOnly).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// }); -// -// group("getFeeRate", () { -// test("getFeeRate success", () async { -// final mockClient = MockJsonRPC(); -// const command = "blockchain.getfeerate"; -// const jsonArgs = '[]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenAnswer( -// (_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "result": { -// "rate": 1000, -// }, -// "id": "some requestId" -// }), -// ); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// final result = await client.getFeeRate(requestID: "some requestId"); -// -// expect(result, {"rate": 1000}); -// -// verify(mockPrefs.wifiOnly).called(1); -// verify(mockPrefs.useTor).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// -// test("getFeeRate throws/fails", () { -// final mockClient = MockJsonRPC(); -// const command = "blockchain.getfeerate"; -// const jsonArgs = '[]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenThrow(Exception()); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: torService, -// failovers: []); -// -// expect(() => client.getFeeRate(requestID: "some requestId"), -// throwsA(isA())); -// -// verify(mockPrefs.wifiOnly).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// }); -// -// test("rpcClient is null throws with bad server info", () { -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((realInvocation) => false); -// final torService = MockTorService(); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final client = ElectrumXClient( -// client: null, -// port: -10, -// host: "_ :sa %", -// useSSL: false, -// prefs: mockPrefs, -// torService: torService, -// failovers: [], -// ); -// -// expect(() => client.getFeeRate(), throwsA(isA())); -// -// verify(mockPrefs.wifiOnly).called(1); -// verifyNoMoreInteractions(mockPrefs); -// }); -// -// group("Tor tests", () { -// // useTor is false, so no TorService calls should be made. -// test("Tor not in use", () async { -// final mockClient = MockJsonRPC(); -// const command = "blockchain.transaction.get"; -// const jsonArgs = '["${SampleGetTransactionData.txHash0}",true]'; -// when(mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId","method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// )).thenAnswer((_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "result": SampleGetTransactionData.txData0, -// "id": "some requestId", -// })); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((_) => false); -// when(mockPrefs.torKillSwitch) -// .thenAnswer((_) => false); // Or true, shouldn't matter. -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final mockTorService = MockTorService(); -// when(mockTorService.status) -// .thenAnswer((_) => TorConnectionStatus.disconnected); -// -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// failovers: [], -// prefs: mockPrefs, -// torService: mockTorService, -// ); -// -// final result = await client.getTransaction( -// txHash: SampleGetTransactionData.txHash0, -// verbose: true, -// requestID: "some requestId"); -// -// expect(result, SampleGetTransactionData.txData0); -// -// verify(mockPrefs.wifiOnly).called(1); -// verify(mockPrefs.useTor).called(1); -// verifyNever(mockPrefs.torKillSwitch); -// verifyNoMoreInteractions(mockPrefs); -// verifyNever(mockTorService.status); -// verifyNoMoreInteractions(mockTorService); -// }); -// -// // useTor is true, but TorService is not enabled and the killswitch is off, so a clearnet call should be made. -// test("Tor in use but Tor unavailable and killswitch off", () async { -// final mockClient = MockJsonRPC(); -// const command = "blockchain.transaction.get"; -// const jsonArgs = '["${SampleGetTransactionData.txHash0}",true]'; -// when(mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId","method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// )).thenAnswer((_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "result": SampleGetTransactionData.txData0, -// "id": "some requestId", -// })); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((_) => true); -// when(mockPrefs.torKillSwitch).thenAnswer((_) => false); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// -// final mockTorService = MockTorService(); -// when(mockTorService.status) -// .thenAnswer((_) => TorConnectionStatus.disconnected); -// when(mockTorService.getProxyInfo()).thenAnswer((_) => ( -// host: InternetAddress('1.2.3.4'), -// port: -1 -// )); // Port is set to -1 until Tor is enabled. -// -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: mockTorService, -// failovers: []); -// -// final result = await client.getTransaction( -// txHash: SampleGetTransactionData.txHash0, -// verbose: true, -// requestID: "some requestId"); -// -// expect(result, SampleGetTransactionData.txData0); -// -// verify(mockPrefs.wifiOnly).called(1); -// verify(mockPrefs.useTor).called(1); -// verify(mockPrefs.torKillSwitch).called(1); -// verifyNoMoreInteractions(mockPrefs); -// verify(mockTorService.status).called(1); -// verifyNever(mockTorService.getProxyInfo()); -// verifyNoMoreInteractions(mockTorService); -// }); -// -// // useTor is true and TorService is enabled, so a TorService call should be made. -// test("Tor in use and available", () async { -// final mockClient = MockJsonRPC(); -// const command = "blockchain.transaction.get"; -// const jsonArgs = '["${SampleGetTransactionData.txHash0}",true]'; -// when(mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId","method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// )).thenAnswer((_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "result": SampleGetTransactionData.txData0, -// "id": "some requestId", -// })); -// when(mockClient.proxyInfo) -// .thenAnswer((_) => (host: InternetAddress('1.2.3.4'), port: 42)); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((_) => true); -// when(mockPrefs.torKillSwitch).thenAnswer((_) => false); // Or true. -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// -// final mockTorService = MockTorService(); -// when(mockTorService.status) -// .thenAnswer((_) => TorConnectionStatus.connected); -// when(mockTorService.getProxyInfo()) -// .thenAnswer((_) => (host: InternetAddress('1.2.3.4'), port: 42)); -// -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// prefs: mockPrefs, -// torService: mockTorService, -// failovers: []); -// -// final result = await client.getTransaction( -// txHash: SampleGetTransactionData.txHash0, -// verbose: true, -// requestID: "some requestId"); -// -// expect(result, SampleGetTransactionData.txData0); -// -// verify(mockClient.proxyInfo).called(1); -// verify(mockPrefs.wifiOnly).called(1); -// verify(mockPrefs.useTor).called(1); -// verifyNever(mockPrefs.torKillSwitch); -// verifyNoMoreInteractions(mockPrefs); -// verify(mockTorService.status).called(1); -// verify(mockTorService.getProxyInfo()).called(1); -// verifyNoMoreInteractions(mockTorService); -// }); -// -// // useTor is true, but TorService is not enabled and the killswitch is on, so no TorService calls should be made. -// test("killswitch enabled", () async { -// final mockClient = MockJsonRPC(); -// const command = "blockchain.transaction.get"; -// const jsonArgs = '["",true]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenAnswer( -// (_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "error": { -// "code": 1, -// "message": "None should be a transaction hash", -// }, -// "id": "some requestId", -// }), -// ); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((_) => true); -// when(mockPrefs.torKillSwitch).thenAnswer((_) => true); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final mockTorService = MockTorService(); -// when(mockTorService.status) -// .thenAnswer((_) => TorConnectionStatus.disconnected); -// -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// failovers: [], -// prefs: mockPrefs, -// torService: mockTorService, -// ); -// -// try { -// var result = await client.getTransaction( -// requestID: "some requestId", txHash: ''); -// } catch (e) { -// expect(e, isA()); -// expect( -// e.toString(), -// equals( -// "Exception: Tor preference and killswitch set but Tor is not enabled, not connecting to ElectrumX")); -// } -// -// verify(mockPrefs.wifiOnly).called(1); -// verify(mockPrefs.useTor).called(1); -// verify(mockPrefs.torKillSwitch).called(1); -// verifyNoMoreInteractions(mockPrefs); -// verify(mockTorService.status).called(1); -// verifyNoMoreInteractions(mockTorService); -// }); -// -// // useTor is true but Tor is not enabled, but because the killswitch is off, a clearnet call should be made. -// test("killswitch disabled", () async { -// final mockClient = MockJsonRPC(); -// const command = "blockchain.transaction.get"; -// const jsonArgs = '["${SampleGetTransactionData.txHash0}",true]'; -// when( -// mockClient.request( -// '{"jsonrpc": "2.0", "id": "some requestId",' -// '"method": "$command","params": $jsonArgs}', -// const Duration(seconds: 60), -// ), -// ).thenAnswer( -// (_) async => JsonRPCResponse(data: { -// "jsonrpc": "2.0", -// "result": SampleGetTransactionData.txData0, -// "id": "some requestId" -// }), -// ); -// -// final mockPrefs = MockPrefs(); -// when(mockPrefs.useTor).thenAnswer((_) => true); -// when(mockPrefs.torKillSwitch).thenAnswer((_) => false); -// when(mockPrefs.wifiOnly).thenAnswer((_) => false); -// final mockTorService = MockTorService(); -// when(mockTorService.status) -// .thenAnswer((_) => TorConnectionStatus.disconnected); -// -// final client = ElectrumXClient( -// host: "some server", -// port: 0, -// useSSL: true, -// client: mockClient, -// failovers: [], -// prefs: mockPrefs, -// torService: mockTorService, -// ); -// -// final result = await client.getTransaction( -// txHash: SampleGetTransactionData.txHash0, -// verbose: true, -// requestID: "some requestId"); -// -// expect(result, SampleGetTransactionData.txData0); -// -// verify(mockPrefs.wifiOnly).called(1); -// verify(mockPrefs.useTor).called(1); -// verify(mockPrefs.torKillSwitch).called(1); -// verifyNoMoreInteractions(mockPrefs); -// verify(mockTorService.status).called(1); -// verifyNoMoreInteractions(mockTorService); -// }); -// }); -// } +import 'dart:io'; + +import 'package:decimal/decimal.dart'; +import 'package:event_bus/event_bus.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:logger/logger.dart' show Level; +import 'package:mockito/mockito.dart'; +import 'package:stackwallet/electrumx_rpc/electrumx_client.dart'; +import 'package:stackwallet/services/event_bus/events/global/tor_connection_status_changed_event.dart'; +import 'package:stackwallet/services/tor_service.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stackwallet/utilities/tor_plain_net_option_enum.dart'; +import 'package:stackwallet/wallets/crypto_currency/coins/bitcoin.dart'; +import 'package:stackwallet/wallets/crypto_currency/coins/firo.dart'; +import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart'; + +import 'sample_data/get_anonymity_set_sample_data.dart'; +import 'sample_data/get_used_serials_sample_data.dart'; +import 'sample_data/gethistory_samples.dart'; +import 'sample_data/transaction_data_samples.dart'; +import 'utilities/mock_electrum_server.dart'; + +class MockPrefs extends Mock implements Prefs { + @override + bool get wifiOnly => + super.noSuchMethod( + Invocation.getter(#wifiOnly), + returnValue: false, + returnValueForMissingStub: false, + ) + as bool; + + @override + bool get useTor => + super.noSuchMethod( + Invocation.getter(#useTor), + returnValue: false, + returnValueForMissingStub: false, + ) + as bool; + + @override + bool get torKillSwitch => + super.noSuchMethod( + Invocation.getter(#torKillSwitch), + returnValue: false, + returnValueForMissingStub: false, + ) + as bool; +} + +class FakeTorService implements TorService { + FakeTorService({ + this.currentStatus = TorConnectionStatus.disconnected, + ({InternetAddress host, int port})? proxyInfo, + }) : _proxyInfo = + proxyInfo ?? (host: InternetAddress.loopbackIPv4, port: 9050); + + TorConnectionStatus currentStatus; + ({InternetAddress host, int port}) _proxyInfo; + int statusReads = 0; + int proxyInfoReads = 0; + + @override + TorConnectionStatus get status { + statusReads++; + return currentStatus; + } + + void setProxyInfo(({InternetAddress host, int port}) proxyInfo) { + _proxyInfo = proxyInfo; + } + + @override + ({InternetAddress host, int port}) getProxyInfo() { + proxyInfoReads++; + return _proxyInfo; + } + + @override + Future disable() async {} + + @override + void init({required String torDataDirPath}) {} + + @override + Future start() async {} +} + +void main() { + late Directory logDir; + late MockPrefs prefs; + late FakeTorService torService; + late EventBus eventBus; + final servers = []; + + setUpAll(() async { + logDir = await Directory.systemTemp.createTemp('electrumx_test_logs'); + await Logging.instance.initialize(logDir.path, level: Level.off); + }); + + Bitcoin bitcoin() => Bitcoin(CryptoCurrencyNetwork.main); + Firo firo() => Firo(CryptoCurrencyNetwork.main); + + MockElectrumServer registerServer({ + Map handlers = const {}, + }) { + final server = MockElectrumServer(handlers: handlers); + servers.add(server); + return server; + } + + ManagedElectrumXClient buildClient({ + required MockElectrumServer clearServer, + MockElectrumServer? torServer, + required CryptoCurrency coin, + TorPlainNetworkOption netType = TorPlainNetworkOption.both, + }) { + return ManagedElectrumXClient( + host: 'mock.stackwallet.dev', + port: 50002, + useSSL: true, + prefs: prefs, + torService: torService, + failovers: [], + cryptoCurrency: coin, + netType: netType, + clearServer: clearServer, + torServer: torServer, + globalEventBusForTesting: eventBus, + ); + } + + Matcher throwsCurrentCastError() => throwsA( + isA().having( + (error) => error.toString(), + 'message', + contains('is not a subtype'), + ), + ); + + setUp(() { + prefs = MockPrefs(); + torService = FakeTorService(); + eventBus = EventBus(); + servers.clear(); + + when(prefs.wifiOnly).thenReturn(false); + when(prefs.useTor).thenReturn(false); + when(prefs.torKillSwitch).thenReturn(false); + }); + + tearDown(() async { + await tearDownManagedElectrum(servers: servers); + }); + + group('factory constructors and getters', () { + test('electrumxnode .from factory copies current fields', () { + final nodeA = ElectrumXNode( + address: 'some address', + port: 50002, + name: 'some name', + id: 'some ID', + useSSL: true, + torEnabled: true, + clearnetEnabled: false, + ); + + final nodeB = ElectrumXNode.from(nodeA); + + expect(nodeB.toString(), nodeA.toString()); + expect(nodeA == nodeB, false); + expect(nodeB.torEnabled, isTrue); + expect(nodeB.clearnetEnabled, isFalse); + }); + + test('electrumx .from factory uses current constructor inputs', () { + final node = ElectrumXNode( + address: 'some address', + port: 60001, + name: 'some name', + id: 'some ID', + useSSL: false, + torEnabled: false, + clearnetEnabled: true, + ); + + final client = ElectrumXClient.from( + node: node, + failovers: [], + prefs: prefs, + torService: torService, + globalEventBusForTesting: eventBus, + cryptoCurrency: bitcoin(), + ); + + expect(client.useSSL, isFalse); + expect(client.host, node.address); + expect(client.port, node.port); + expect(client.netType, TorPlainNetworkOption.clear); + expect(client.getElectrumAdapter(), isNull); + verifyNever(prefs.useTor); + expect(torService.statusReads, 0); + }); + }); + + group('generic request wrappers', () { + test('ping success uses the live adapter client', () async { + final server = registerServer(handlers: {'server.ping': (_) => null}); + final client = buildClient(clearServer: server, coin: bitcoin()); + + final result = await client.ping(requestID: 'ping-1'); + + expect(result, isTrue); + expect(server.requestCount('blockchain.headers.subscribe'), 1); + expect(server.requestCount('server.ping'), 1); + }); + + test('server.features success returns a parsed map', () async { + final expected = { + 'genesis_hash': 'genesis', + 'hosts': { + '0.0.0.0': {'tcp_port': 51001, 'ssl_port': 51002}, + }, + 'protocol_max': '1.4', + 'protocol_min': '1.0', + 'server_version': 'ElectrumX 1.0.17', + 'hash_function': 'sha256', + }; + final server = registerServer( + handlers: {'server.features': (_) => expected}, + ); + final client = buildClient(clearServer: server, coin: bitcoin()); + + final result = await client.getServerFeatures(requestID: 'features-1'); + + expect(result, expected); + expect(server.requestCount('server.features'), 1); + }); + + test('getTransaction supports verbose and raw responses', () async { + final server = registerServer( + handlers: { + 'blockchain.transaction.get': (params) { + if (params.last == false) { + return 'raw-transaction-hex'; + } + return SampleGetTransactionData.txData0; + }, + }, + ); + final client = buildClient(clearServer: server, coin: firo()); + + final verbose = await client.getTransaction( + txHash: SampleGetTransactionData.txHash0, + verbose: true, + requestID: 'tx-verbose', + ); + final raw = await client.getTransaction( + txHash: SampleGetTransactionData.txHash0, + verbose: false, + requestID: 'tx-raw', + ); + + expect(verbose, SampleGetTransactionData.txData0); + expect(raw, {'rawtx': 'raw-transaction-hex'}); + }); + + test('request surfaces server errors for malformed inputs', () async { + final server = registerServer( + handlers: { + 'blockchain.transaction.get': (_) => { + 'error': { + 'code': 1, + 'message': 'None should be a transaction hash', + }, + }, + }, + ); + final client = buildClient(clearServer: server, coin: bitcoin()); + + await expectLater( + () => client.request( + command: 'blockchain.transaction.get', + args: const ['', true], + requestID: 'bad-tx', + ), + throwsA(isA()), + ); + }); + + test('getHistory uses the current list payload', () async { + final server = registerServer( + handlers: { + 'blockchain.scripthash.get_history': (_) => + SampleGetHistoryData.data1, + }, + ); + final client = buildClient(clearServer: server, coin: firo()); + + final history = await client.getHistory( + scripthash: SampleGetHistoryData.scripthash1, + requestID: 'history-1', + ); + + expect(history, SampleGetHistoryData.data1); + expect(server.requestCount('blockchain.scripthash.get_history'), 1); + }); + + test('getHistory throws after retrying malformed payloads', () async { + final server = registerServer( + handlers: { + 'blockchain.scripthash.get_history': (_) => {'unexpected': true}, + }, + ); + final client = buildClient(clearServer: server, coin: firo()); + + await expectLater( + () => client.getHistory( + scripthash: SampleGetHistoryData.scripthash1, + requestID: 'history-bad', + ), + throwsCurrentCastError(), + ); + expect(server.requestCount('blockchain.scripthash.get_history'), 3); + }); + + test('fee wrappers use the current adapter command names', () async { + final server = registerServer( + handlers: { + 'blockchain.getfeerate': (_) => {'rate': 1000}, + 'blockchain.estimatefee': (params) { + expect(params, [5]); + return '0.00001000'; + }, + 'blockchain.relayfee': (_) => '0.00002000', + }, + ); + final client = buildClient(clearServer: server, coin: firo()); + + final feeRate = await client.getFeeRate(requestID: 'fee-rate'); + final estimate = await client.estimateFee( + requestID: 'estimate-1', + blocks: 5, + ); + final relay = await client.relayFee(requestID: 'relay-1'); + + expect(feeRate, {'rate': 1000}); + expect(estimate, Decimal.parse('0.00001000')); + expect(relay, Decimal.parse('0.00002000')); + expect(server.requestCount('blockchain.getfeerate'), 1); + expect(server.requestCount('blockchain.estimatefee'), 1); + expect(server.requestCount('blockchain.relayfee'), 1); + }); + + test('bad server exceptions bubble from current public wrappers', () async { + final server = registerServer( + handlers: { + 'server.features': (_) => throw Exception('mock bad server'), + }, + ); + final client = buildClient(clearServer: server, coin: bitcoin()); + + await expectLater( + () => client.getServerFeatures(requestID: 'features-bad'), + throwsA( + isA().having( + (error) => error.toString(), + 'message', + contains('mock bad server'), + ), + ), + ); + }); + }); + + group('Firo-specific wrappers', () { + test( + 'Lelantus wrappers use the current payloads and request shapes', + () async { + const requestedMints = ['mint-a', 'mint-b']; + final mintMetadata = { + 'mint-a': {'groupId': 1, 'height': 455866}, + 'mint-b': {'groupId': 2, 'height': 455876}, + }; + final server = registerServer( + handlers: { + 'lelantus.getanonymityset': (params) { + expect(params, ['1', '']); + return GetAnonymitySetSampleData.data; + }, + 'lelantus.getmintmetadata': (params) { + expect(params, [requestedMints]); + return mintMetadata; + }, + 'lelantus.getusedcoinserials': (params) { + expect(params, ['0']); + return GetUsedSerialsSampleData.serials; + }, + 'lelantus.getlatestcoinid': (_) => 42, + }, + ); + final client = buildClient(clearServer: server, coin: firo()); + + final anonymitySet = await client.getLelantusAnonymitySet( + groupId: '1', + blockhash: '', + requestID: 'set-1', + ); + final mintData = await client.getLelantusMintData( + mints: requestedMints, + requestID: 'mint-1', + ); + final serials = await client.getLelantusUsedCoinSerials( + requestID: 'serials-1', + startNumber: 0, + ); + final latest = await client.getLelantusLatestCoinId(requestID: 'id-1'); + + expect(anonymitySet, GetAnonymitySetSampleData.data); + expect(mintData, mintMetadata); + expect(serials, GetUsedSerialsSampleData.serials); + expect(latest, 42); + expect(server.requestCount('lelantus.getanonymityset'), 1); + expect(server.requestCount('lelantus.getmintmetadata'), 1); + expect(server.requestCount('lelantus.getusedcoinserials'), 3); + expect(server.requestCount('lelantus.getlatestcoinid'), 1); + }, + ); + + test('Lelantus wrappers surface current failure modes', () async { + final server = registerServer( + handlers: { + 'lelantus.getmintmetadata': (_) => + throw Exception('mint metadata unavailable'), + 'lelantus.getusedcoinserials': (_) => ['not-a-map'], + 'lelantus.getlatestcoinid': (_) => 'forty-two', + }, + ); + final client = buildClient(clearServer: server, coin: firo()); + + await expectLater( + () => client.getLelantusMintData( + mints: const ['mint-a'], + requestID: 'mint-bad', + ), + throwsA( + isA().having( + (error) => error.toString(), + 'message', + contains('mint metadata unavailable'), + ), + ), + ); + await expectLater( + () => client.getLelantusUsedCoinSerials( + requestID: 'serials-bad', + startNumber: 0, + ), + throwsCurrentCastError(), + ); + await expectLater( + () => client.getLelantusLatestCoinId(requestID: 'id-bad'), + throwsCurrentCastError(), + ); + expect(server.requestCount('lelantus.getusedcoinserials'), 1); + }); + }); + + group('Tor tests', () { + test('Tor not in use', () async { + when(prefs.useTor).thenReturn(false); + when(prefs.torKillSwitch).thenReturn(false); + + final clearServer = registerServer( + handlers: { + 'blockchain.transaction.get': (_) => SampleGetTransactionData.txData0, + }, + ); + final torServer = registerServer( + handlers: { + 'blockchain.transaction.get': (_) => {'unexpected': true}, + }, + ); + + final client = buildClient( + clearServer: clearServer, + torServer: torServer, + coin: firo(), + ); + + final result = await client.getTransaction( + txHash: SampleGetTransactionData.txHash0, + verbose: true, + requestID: 'tor-off', + ); + + expect(result, SampleGetTransactionData.txData0); + expect(clearServer.requestCount('blockchain.transaction.get'), 1); + expect(torServer.requestCount('blockchain.transaction.get'), 0); + verify(prefs.useTor).called(greaterThanOrEqualTo(1)); + expect(torService.statusReads, 0); + expect(torService.proxyInfoReads, 0); + }); + + test( + 'Tor in use but unavailable and killswitch off uses clearnet', + () async { + when(prefs.useTor).thenReturn(true); + when(prefs.torKillSwitch).thenReturn(false); + torService.currentStatus = TorConnectionStatus.disconnected; + + final clearServer = registerServer( + handlers: { + 'blockchain.transaction.get': (_) => + SampleGetTransactionData.txData0, + }, + ); + final torServer = registerServer( + handlers: { + 'blockchain.transaction.get': (_) => {'unexpected': true}, + }, + ); + + final client = buildClient( + clearServer: clearServer, + torServer: torServer, + coin: firo(), + ); + + final result = await client.getTransaction( + txHash: SampleGetTransactionData.txHash0, + verbose: true, + requestID: 'tor-fallback', + ); + + expect(result, SampleGetTransactionData.txData0); + expect(clearServer.requestCount('blockchain.transaction.get'), 1); + expect(torServer.requestCount('blockchain.transaction.get'), 0); + expect(torService.statusReads, greaterThanOrEqualTo(1)); + expect(torService.proxyInfoReads, 0); + }, + ); + + test('Tor in use and available uses the tor-backed adapter', () async { + when(prefs.useTor).thenReturn(true); + torService.currentStatus = TorConnectionStatus.connected; + torService.setProxyInfo((host: InternetAddress.loopbackIPv4, port: 9050)); + + final clearServer = registerServer( + handlers: { + 'blockchain.transaction.get': (_) => {'unexpected': true}, + }, + ); + final torServer = registerServer( + handlers: { + 'blockchain.transaction.get': (_) => SampleGetTransactionData.txData0, + }, + ); + + final client = buildClient( + clearServer: clearServer, + torServer: torServer, + coin: firo(), + ); + + final result = await client.getTransaction( + txHash: SampleGetTransactionData.txHash0, + verbose: true, + requestID: 'tor-on', + ); + + expect(result, SampleGetTransactionData.txData0); + expect(clearServer.requestCount('blockchain.transaction.get'), 0); + expect(torServer.requestCount('blockchain.transaction.get'), 1); + expect(torService.statusReads, greaterThanOrEqualTo(1)); + expect(torService.proxyInfoReads, greaterThanOrEqualTo(1)); + }); + + test('killswitch enabled throws before any adapter request', () async { + when(prefs.useTor).thenReturn(true); + when(prefs.torKillSwitch).thenReturn(true); + torService.currentStatus = TorConnectionStatus.disconnected; + + final clearServer = registerServer( + handlers: { + 'blockchain.transaction.get': (_) => SampleGetTransactionData.txData0, + }, + ); + + final client = buildClient(clearServer: clearServer, coin: firo()); + + await expectLater( + () => client.getTransaction( + txHash: SampleGetTransactionData.txHash0, + verbose: true, + requestID: 'tor-killswitch', + ), + throwsA( + isA().having( + (error) => error.toString(), + 'message', + contains( + 'Tor preference and killswitch set but Tor is not enabled', + ), + ), + ), + ); + expect(clearServer.requestCount('blockchain.transaction.get'), 0); + }); + }); +} diff --git a/test/hive/hive_ce_test_utils.dart b/test/hive/hive_ce_test_utils.dart new file mode 100644 index 0000000000..3ac973cca8 --- /dev/null +++ b/test/hive/hive_ce_test_utils.dart @@ -0,0 +1,82 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:hive_ce/hive.dart'; +import 'package:stackwallet/db/hive/db.dart'; + +const _helperPath = 'test/hive/hive_ce_test_utils.dart'; +const _defaultHiveCeTestTimeout = Duration(seconds: 15); + +Directory? _testHiveDirectory; + +Future setUpHiveCeTest({ + Duration timeout = _defaultHiveCeTestTimeout, +}) async { + if (_testHiveDirectory != null) { + throw StateError( + '$_helperPath [init]: previous Hive CE temp directory ' + '"${_testHiveDirectory!.path}" was not cleaned up before reinitialization.', + ); + } + + try { + await (() async { + final tempDirectory = await Directory.systemTemp.createTemp( + 'stack_wallet_hive_ce_test_', + ); + Hive.init(tempDirectory.path); + DB.instance.hive.init(tempDirectory.path); + _testHiveDirectory = tempDirectory; + })().timeout( + timeout, + onTimeout: () => throw TimeoutException( + '$_helperPath [init]: timed out after ${timeout.inSeconds}s.', + ), + ); + } catch (error, stackTrace) { + final tempDirectory = _testHiveDirectory; + _testHiveDirectory = null; + + if (tempDirectory != null && await tempDirectory.exists()) { + await tempDirectory.delete(recursive: true); + } + + Error.throwWithStackTrace( + StateError('$_helperPath [init]: $error'), + stackTrace, + ); + } +} + +Future tearDownHiveCeTest({ + Duration timeout = _defaultHiveCeTestTimeout, +}) async { + final tempDirectory = _testHiveDirectory; + if (tempDirectory == null) { + throw StateError( + '$_helperPath [cleanup]: called before setUpHiveCeTest().', + ); + } + + _testHiveDirectory = null; + + try { + await (() async { + await DB.instance.hive.close(); + await Hive.close(); + if (await tempDirectory.exists()) { + await tempDirectory.delete(recursive: true); + } + })().timeout( + timeout, + onTimeout: () => throw TimeoutException( + '$_helperPath [cleanup]: timed out after ${timeout.inSeconds}s.', + ), + ); + } catch (error, stackTrace) { + Error.throwWithStackTrace( + StateError('$_helperPath [cleanup]: $error'), + stackTrace, + ); + } +} diff --git a/test/pages/send_view/send_view_test.mocks.dart b/test/pages/send_view/send_view_test.mocks.dart index 36200d3895..531bddef0c 100644 --- a/test/pages/send_view/send_view_test.mocks.dart +++ b/test/pages/send_view/send_view_test.mocks.dart @@ -4,23 +4,24 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i10; -import 'dart:typed_data' as _i19; -import 'dart:ui' as _i14; +import 'dart:typed_data' as _i20; +import 'dart:ui' as _i15; -import 'package:logger/logger.dart' as _i22; +import 'package:logger/logger.dart' as _i23; import 'package:mockito/mockito.dart' as _i1; -import 'package:mockito/src/dummies.dart' as _i16; +import 'package:mockito/src/dummies.dart' as _i17; import 'package:stackwallet/db/isar/main_db.dart' as _i3; -import 'package:stackwallet/models/isar/stack_theme.dart' as _i18; +import 'package:stackwallet/models/epicbox_server_model.dart' as _i14; +import 'package:stackwallet/models/isar/stack_theme.dart' as _i19; import 'package:stackwallet/models/node_model.dart' as _i13; import 'package:stackwallet/networking/http.dart' as _i7; -import 'package:stackwallet/services/locale_service.dart' as _i15; +import 'package:stackwallet/services/locale_service.dart' as _i16; import 'package:stackwallet/services/node_service.dart' as _i2; import 'package:stackwallet/services/wallets.dart' as _i9; -import 'package:stackwallet/themes/theme_service.dart' as _i17; -import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i23; -import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i21; -import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i20; +import 'package:stackwallet/themes/theme_service.dart' as _i18; +import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i24; +import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i22; +import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i21; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' as _i6; import 'package:stackwallet/utilities/prefs.dart' as _i12; @@ -320,6 +321,64 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { ) as _i10.Future); + @override + _i10.Future updateDefaultEpicBoxes() => + (super.noSuchMethod( + Invocation.method(#updateDefaultEpicBoxes, []), + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) + as _i10.Future); + + @override + _i10.Future setPrimaryEpicBox({ + required _i14.EpicBoxServerModel? epicBox, + bool? shouldNotifyListeners = false, + }) => + (super.noSuchMethod( + Invocation.method(#setPrimaryEpicBox, [], { + #epicBox: epicBox, + #shouldNotifyListeners: shouldNotifyListeners, + }), + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) + as _i10.Future); + + @override + List<_i14.EpicBoxServerModel> getEpicBoxes() => + (super.noSuchMethod( + Invocation.method(#getEpicBoxes, []), + returnValue: <_i14.EpicBoxServerModel>[], + ) + as List<_i14.EpicBoxServerModel>); + + @override + _i14.EpicBoxServerModel? getEpicBoxById({required String? id}) => + (super.noSuchMethod(Invocation.method(#getEpicBoxById, [], {#id: id})) + as _i14.EpicBoxServerModel?); + + @override + _i10.Future addEpicBox( + _i14.EpicBoxServerModel? epicBox, + bool? shouldNotifyListeners, + ) => + (super.noSuchMethod( + Invocation.method(#addEpicBox, [epicBox, shouldNotifyListeners]), + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) + as _i10.Future); + + @override + _i10.Future deleteEpicBox(String? id, bool? shouldNotifyListeners) => + (super.noSuchMethod( + Invocation.method(#deleteEpicBox, [id, shouldNotifyListeners]), + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) + as _i10.Future); + @override _i10.Future updateCommunityNodes() => (super.noSuchMethod( @@ -330,13 +389,13 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { as _i10.Future); @override - void addListener(_i14.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i15.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#addListener, [listener]), returnValueForMissingStub: null, ); @override - void removeListener(_i14.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i15.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#removeListener, [listener]), returnValueForMissingStub: null, ); @@ -357,7 +416,7 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { /// A class which mocks [LocaleService]. /// /// See the documentation for Mockito's code generation for more information. -class MockLocaleService extends _i1.Mock implements _i15.LocaleService { +class MockLocaleService extends _i1.Mock implements _i16.LocaleService { MockLocaleService() { _i1.throwOnMissingStub(this); } @@ -366,7 +425,7 @@ class MockLocaleService extends _i1.Mock implements _i15.LocaleService { String get locale => (super.noSuchMethod( Invocation.getter(#locale), - returnValue: _i16.dummyValue( + returnValue: _i17.dummyValue( this, Invocation.getter(#locale), ), @@ -388,13 +447,13 @@ class MockLocaleService extends _i1.Mock implements _i15.LocaleService { as _i10.Future); @override - void addListener(_i14.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i15.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#addListener, [listener]), returnValueForMissingStub: null, ); @override - void removeListener(_i14.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i15.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#removeListener, [listener]), returnValueForMissingStub: null, ); @@ -415,7 +474,7 @@ class MockLocaleService extends _i1.Mock implements _i15.LocaleService { /// A class which mocks [ThemeService]. /// /// See the documentation for Mockito's code generation for more information. -class MockThemeService extends _i1.Mock implements _i17.ThemeService { +class MockThemeService extends _i1.Mock implements _i18.ThemeService { MockThemeService() { _i1.throwOnMissingStub(this); } @@ -437,12 +496,12 @@ class MockThemeService extends _i1.Mock implements _i17.ThemeService { as _i7.HTTP); @override - List<_i18.StackTheme> get installedThemes => + List<_i19.StackTheme> get installedThemes => (super.noSuchMethod( Invocation.getter(#installedThemes), - returnValue: <_i18.StackTheme>[], + returnValue: <_i19.StackTheme>[], ) - as List<_i18.StackTheme>); + as List<_i19.StackTheme>); @override set client(_i7.HTTP? value) => super.noSuchMethod( @@ -457,7 +516,7 @@ class MockThemeService extends _i1.Mock implements _i17.ThemeService { ); @override - _i10.Future install({required _i19.Uint8List? themeArchiveData}) => + _i10.Future install({required _i20.Uint8List? themeArchiveData}) => (super.noSuchMethod( Invocation.method(#install, [], { #themeArchiveData: themeArchiveData, @@ -494,29 +553,29 @@ class MockThemeService extends _i1.Mock implements _i17.ThemeService { as _i10.Future); @override - _i10.Future> fetchThemes() => + _i10.Future> fetchThemes() => (super.noSuchMethod( Invocation.method(#fetchThemes, []), - returnValue: _i10.Future>.value( - <_i17.StackThemeMetaData>[], + returnValue: _i10.Future>.value( + <_i18.StackThemeMetaData>[], ), ) - as _i10.Future>); + as _i10.Future>); @override - _i10.Future<_i19.Uint8List> fetchTheme({ - required _i17.StackThemeMetaData? themeMetaData, + _i10.Future<_i20.Uint8List> fetchTheme({ + required _i18.StackThemeMetaData? themeMetaData, }) => (super.noSuchMethod( Invocation.method(#fetchTheme, [], {#themeMetaData: themeMetaData}), - returnValue: _i10.Future<_i19.Uint8List>.value(_i19.Uint8List(0)), + returnValue: _i10.Future<_i20.Uint8List>.value(_i20.Uint8List(0)), ) - as _i10.Future<_i19.Uint8List>); + as _i10.Future<_i20.Uint8List>); @override - _i18.StackTheme? getTheme({required String? themeId}) => + _i19.StackTheme? getTheme({required String? themeId}) => (super.noSuchMethod(Invocation.method(#getTheme, [], {#themeId: themeId})) - as _i18.StackTheme?); + as _i19.StackTheme?); } /// A class which mocks [Prefs]. @@ -562,12 +621,12 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { as List); @override - _i20.SyncingType get syncType => + _i21.SyncingType get syncType => (super.noSuchMethod( Invocation.getter(#syncType), - returnValue: _i20.SyncingType.currentWalletOnly, + returnValue: _i21.SyncingType.currentWalletOnly, ) - as _i20.SyncingType); + as _i21.SyncingType); @override bool get wifiOnly => @@ -586,7 +645,7 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { String get language => (super.noSuchMethod( Invocation.getter(#language), - returnValue: _i16.dummyValue( + returnValue: _i17.dummyValue( this, Invocation.getter(#language), ), @@ -597,7 +656,7 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { String get currency => (super.noSuchMethod( Invocation.getter(#currency), - returnValue: _i16.dummyValue( + returnValue: _i17.dummyValue( this, Invocation.getter(#currency), ), @@ -659,12 +718,12 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { as bool); @override - _i21.BackupFrequencyType get backupFrequencyType => + _i22.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod( Invocation.getter(#backupFrequencyType), - returnValue: _i21.BackupFrequencyType.everyTenMinutes, + returnValue: _i22.BackupFrequencyType.everyTenMinutes, ) - as _i21.BackupFrequencyType); + as _i22.BackupFrequencyType); @override bool get hideBlockExplorerWarning => @@ -707,7 +766,7 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { String get themeId => (super.noSuchMethod( Invocation.getter(#themeId), - returnValue: _i16.dummyValue( + returnValue: _i17.dummyValue( this, Invocation.getter(#themeId), ), @@ -718,7 +777,7 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { String get systemBrightnessLightThemeId => (super.noSuchMethod( Invocation.getter(#systemBrightnessLightThemeId), - returnValue: _i16.dummyValue( + returnValue: _i17.dummyValue( this, Invocation.getter(#systemBrightnessLightThemeId), ), @@ -729,7 +788,7 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { String get systemBrightnessDarkThemeId => (super.noSuchMethod( Invocation.getter(#systemBrightnessDarkThemeId), - returnValue: _i16.dummyValue( + returnValue: _i17.dummyValue( this, Invocation.getter(#systemBrightnessDarkThemeId), ), @@ -763,12 +822,12 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { as bool); @override - _i22.Level get logLevel => + _i23.Level get logLevel => (super.noSuchMethod( Invocation.getter(#logLevel), - returnValue: _i22.Level.all, + returnValue: _i23.Level.all, ) - as _i22.Level); + as _i23.Level); @override ({bool enabled, int minutes}) get autoLockInfo => @@ -811,7 +870,7 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { ); @override - set syncType(_i20.SyncingType? syncType) => super.noSuchMethod( + set syncType(_i21.SyncingType? syncType) => super.noSuchMethod( Invocation.setter(#syncType, syncType), returnValueForMissingStub: null, ); @@ -901,7 +960,7 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { ); @override - set backupFrequencyType(_i21.BackupFrequencyType? backupFrequencyType) => + set backupFrequencyType(_i22.BackupFrequencyType? backupFrequencyType) => super.noSuchMethod( Invocation.setter(#backupFrequencyType, backupFrequencyType), returnValueForMissingStub: null, @@ -1008,7 +1067,7 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { ); @override - set logLevel(_i22.Level? logLevel) => super.noSuchMethod( + set logLevel(_i23.Level? logLevel) => super.noSuchMethod( Invocation.setter(#logLevel, logLevel), returnValueForMissingStub: null, ); @@ -1082,17 +1141,17 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { as _i10.Future); @override - _i23.AmountUnit amountUnit(_i4.CryptoCurrency? coin) => + _i24.AmountUnit amountUnit(_i4.CryptoCurrency? coin) => (super.noSuchMethod( Invocation.method(#amountUnit, [coin]), - returnValue: _i23.AmountUnit.normal, + returnValue: _i24.AmountUnit.normal, ) - as _i23.AmountUnit); + as _i24.AmountUnit); @override void updateAmountUnit({ required _i4.CryptoCurrency? coin, - required _i23.AmountUnit? amountUnit, + required _i24.AmountUnit? amountUnit, }) => super.noSuchMethod( Invocation.method(#updateAmountUnit, [], { #coin: coin, @@ -1142,13 +1201,13 @@ class MockPrefs extends _i1.Mock implements _i12.Prefs { ); @override - void addListener(_i14.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i15.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#addListener, [listener]), returnValueForMissingStub: null, ); @override - void removeListener(_i14.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i15.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#removeListener, [listener]), returnValueForMissingStub: null, ); diff --git a/test/price_test.dart b/test/price_test.dart index e7a8b401a4..468295b79b 100644 --- a/test/price_test.dart +++ b/test/price_test.dart @@ -4,22 +4,21 @@ import 'dart:convert'; import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; -import 'package:hive_ce/hive.dart'; -import 'package:hive_test/hive_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/networking/http.dart'; import 'package:stackwallet/services/price.dart'; +import 'hive/hive_ce_test_utils.dart'; import 'price_test.mocks.dart'; @GenerateMocks([HTTP]) void main() { setUp(() async { - await setUpTestHive(); - await Hive.openBox(DB.boxNamePriceCache); - await Hive.openBox(DB.boxNamePrefs); + await setUpHiveCeTest(); + await DB.instance.hive.openBox(DB.boxNamePriceCache); + await DB.instance.hive.openBox(DB.boxNamePrefs); await DB.instance.put( boxName: DB.boxNamePrefs, key: "externalCalls", @@ -27,19 +26,71 @@ void main() { ); }); + void expectFetchedPriceSnapshot(String prices) { + expect( + prices, + contains("Instance of 'Bitcoin': (change24h: 0.0, value: 1)"), + ); + expect( + prices, + contains( + "Instance of 'Monero': (change24h: -0.77656, value: 0.00717236)", + ), + ); + expect( + prices, + contains( + "Instance of 'Dogecoin': (change24h: -2.68533, value: 0.00000315)", + ), + ); + expect( + prices, + contains( + "Instance of 'Epiccash': (change24h: 7.27524, value: 0.00002803)", + ), + ); + expect( + prices, + contains("Instance of 'Firo': (change24h: -0.89304, value: 0.0001096)"), + ); + expect( + prices, + contains("Instance of 'Xelis': (change24h: 5.67, value: 0.00001234)"), + ); + expect( + prices, + contains("Instance of 'Cardano': (change24h: 0.0, value: 0)"), + ); + expect( + prices, + contains("Instance of 'Fact0rn': (change24h: 0.0, value: 0)"), + ); + expect( + prices, + contains("Instance of 'Peercoin': (change24h: 0.0, value: 0)"), + ); + expect( + prices, + contains("Instance of 'Salvium': (change24h: 0.0, value: 0)"), + ); + expect( + prices, + contains("Instance of 'Solana': (change24h: 0.0, value: 0)"), + ); + expect(prices, isNot('{}')); + } + + void expectEmptyPriceSnapshot(String prices) { + expect(prices, '{}'); + } + test("getPricesAnd24hChange fetch", () async { final client = MockHTTP(); when( client.get( proxyInfo: null, - url: Uri.parse( - "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids" - "=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,bitcoin-cash" - ",namecoin,wownero,ethereum,particl,nano,banano,stellar,tezos,xelis" - "&order=market_cap_desc&per_page=50" - "&page=1&sparkline=false", - ), + url: anyNamed('url'), headers: {'Content-Type': 'application/json'}, ), ).thenAnswer( @@ -115,44 +166,11 @@ void main() { final price = await priceAPI.getPricesAnd24hChange(baseCurrency: "btc"); - expect( - price.toString(), - '{' - 'Coin.bitcoin: [1, 0.0], ' - 'Coin.monero: [0.00717236, -0.77656], ' - 'Coin.banano: [0, 0.0], ' - 'Coin.bitcoincash: [0, 0.0], ' - 'Coin.dogecoin: [0.00000315, -2.68533], ' - 'Coin.eCash: [0, 0.0], ' - 'Coin.epicCash: [0.00002803, 7.27524], ' - 'Coin.ethereum: [0, 0.0], ' - 'Coin.firo: [0.0001096, -0.89304], ' - 'Coin.litecoin: [0, 0.0], ' - 'Coin.namecoin: [0, 0.0], ' - 'Coin.nano: [0, 0.0], ' - 'Coin.particl: [0, 0.0], ' - 'Coin.stellar: [0, 0.0], ' - 'Coin.tezos: [0, 0.0], ' - 'Coin.wownero: [0, 0.0], ' - 'Coin.bitcoinTestNet: [0, 0.0], ' - 'Coin.bitcoincashTestnet: [0, 0.0], ' - 'Coin.dogecoinTestNet: [0, 0.0], ' - 'Coin.firoTestNet: [0, 0.0], ' - 'Coin.litecoinTestNet: [0, 0.0], ' - 'Coin.stellarTestnet: [0, 0.0], ' - 'Coin.xelis: [0.00001234, 5.67]' - '}', - ); + expectFetchedPriceSnapshot(price.toString()); verify( client.get( proxyInfo: null, - url: Uri.parse( - "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc" - "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," - "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" - ",tezos,xelis" - "&order=market_cap_desc&per_page=50&page=1&sparkline=false", - ), + url: anyNamed('url'), headers: {'Content-Type': 'application/json'}, ), ).called(1); @@ -166,13 +184,7 @@ void main() { when( client.get( proxyInfo: null, - url: Uri.parse( - "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&" - "ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," - "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" - ",tezos,xelis" - "&order=market_cap_desc&per_page=50&page=1&sparkline=false", - ), + url: anyNamed('url'), headers: {'Content-Type': 'application/json'}, ), ).thenAnswer( @@ -254,43 +266,13 @@ void main() { baseCurrency: "btc", ); - expect( - cachedPrice.toString(), - '{' - 'Coin.bitcoin: [1, 0.0], ' - 'Coin.monero: [0.00717236, -0.77656], ' - 'Coin.banano: [0, 0.0], Coin.bitcoincash: [0, 0.0], ' - 'Coin.dogecoin: [0.00000315, -2.68533], ' - 'Coin.eCash: [0, 0.0], ' - 'Coin.epicCash: [0.00002803, 7.27524], Coin.ethereum: [0, 0.0], ' - 'Coin.firo: [0.0001096, -0.89304], ' - 'Coin.litecoin: [0, 0.0], ' - 'Coin.namecoin: [0, 0.0], ' - 'Coin.nano: [0, 0.0], ' - 'Coin.particl: [0, 0.0], ' - 'Coin.stellar: [0, 0.0], ' - 'Coin.tezos: [0, 0.0], ' - 'Coin.wownero: [0, 0.0], ' - 'Coin.bitcoinTestNet: [0, 0.0], ' - 'Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], ' - 'Coin.firoTestNet: [0, 0.0], ' - 'Coin.litecoinTestNet: [0, 0.0], ' - 'Coin.stellarTestnet: [0, 0.0], ' - 'Coin.xelis: [0.00001234, 5.67]' - '}', - ); + expectFetchedPriceSnapshot(cachedPrice.toString()); // verify only called once during filling of cache verify( client.get( proxyInfo: null, - url: Uri.parse( - "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids" - "=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," - "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" - ",tezos,xelis" - "&order=market_cap_desc&per_page=50&page=1&sparkline=false", - ), + url: anyNamed('url'), headers: {'Content-Type': 'application/json'}, ), ).called(1); @@ -304,13 +286,7 @@ void main() { when( client.get( proxyInfo: null, - url: Uri.parse( - "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc" - "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," - "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" - ",tezos,xelis" - "&order=market_cap_desc&per_page=50&page=1&sparkline=false", - ), + url: anyNamed('url'), headers: {'Content-Type': 'application/json'}, ), ).thenAnswer( @@ -386,33 +362,7 @@ void main() { final price = await priceAPI.getPricesAnd24hChange(baseCurrency: "btc"); - expect( - price.toString(), - '{' - 'Coin.bitcoin: [0, 0.0], Coin.monero: [0, 0.0], ' - 'Coin.banano: [0, 0.0], ' - 'Coin.bitcoincash: [0, 0.0], ' - 'Coin.dogecoin: [0, 0.0], ' - 'Coin.eCash: [0, 0.0], ' - 'Coin.epicCash: [0, 0.0], ' - 'Coin.ethereum: [0, 0.0], ' - 'Coin.firo: [0, 0.0], ' - 'Coin.litecoin: [0, 0.0], ' - 'Coin.namecoin: [0, 0.0], ' - 'Coin.nano: [0, 0.0], ' - 'Coin.particl: [0, 0.0], ' - 'Coin.stellar: [0, 0.0], ' - 'Coin.tezos: [0, 0.0], ' - 'Coin.wownero: [0, 0.0], ' - 'Coin.bitcoinTestNet: [0, 0.0], ' - 'Coin.bitcoincashTestnet: [0, 0.0], ' - 'Coin.dogecoinTestNet: [0, 0.0], ' - 'Coin.firoTestNet: [0, 0.0], ' - 'Coin.litecoinTestNet: [0, 0.0], ' - 'Coin.stellarTestnet: [0, 0.0], ' - 'Coin.xelis: [0, 0.0]' - '}', - ); + expectEmptyPriceSnapshot(price.toString()); }); test("no internet available", () async { @@ -421,13 +371,7 @@ void main() { when( client.get( proxyInfo: null, - url: Uri.parse( - "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc" - "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," - "bitcoin-cash,namecoin,wownero,ethereum,particl,nano,banano,stellar" - ",tezos,xelis" - "&order=market_cap_desc&per_page=50&page=1&sparkline=false", - ), + url: anyNamed('url'), headers: {'Content-Type': 'application/json'}, ), ).thenThrow( @@ -441,37 +385,10 @@ void main() { final price = await priceAPI.getPricesAnd24hChange(baseCurrency: "btc"); - expect( - price.toString(), - '{' - 'Coin.bitcoin: [0, 0.0], ' - 'Coin.monero: [0, 0.0], ' - 'Coin.banano: [0, 0.0], ' - 'Coin.bitcoincash: [0, 0.0], ' - 'Coin.dogecoin: [0, 0.0], ' - 'Coin.eCash: [0, 0.0], ' - 'Coin.epicCash: [0, 0.0], ' - 'Coin.ethereum: [0, 0.0], ' - 'Coin.firo: [0, 0.0], ' - 'Coin.litecoin: [0, 0.0], ' - 'Coin.namecoin: [0, 0.0], ' - 'Coin.nano: [0, 0.0], ' - 'Coin.particl: [0, 0.0], ' - 'Coin.stellar: [0, 0.0], ' - 'Coin.tezos: [0, 0.0], ' - 'Coin.wownero: [0, 0.0], ' - 'Coin.bitcoinTestNet: [0, 0.0], ' - 'Coin.bitcoincashTestnet: [0, 0.0], ' - 'Coin.dogecoinTestNet: [0, 0.0], ' - 'Coin.firoTestNet: [0, 0.0], ' - 'Coin.litecoinTestNet: [0, 0.0], ' - 'Coin.stellarTestnet: [0, 0.0], ' - 'Coin.xelis: [0, 0.0]' - '}', - ); + expectEmptyPriceSnapshot(price.toString()); }); tearDown(() async { - await tearDownTestHive(); + await tearDownHiveCeTest(); }); } diff --git a/test/price_test.mocks.dart b/test/price_test.mocks.dart index 36619fe9c1..a7a491f5b7 100644 --- a/test/price_test.mocks.dart +++ b/test/price_test.mocks.dart @@ -43,12 +43,14 @@ class MockHTTP extends _i1.Mock implements _i2.HTTP { required Uri? url, Map? headers, required ({_i4.InternetAddress host, int port})? proxyInfo, + Duration? connectionTimeout, }) => (super.noSuchMethod( Invocation.method(#get, [], { #url: url, #headers: headers, #proxyInfo: proxyInfo, + #connectionTimeout: connectionTimeout, }), returnValue: _i3.Future<_i2.Response>.value( _FakeResponse_0( @@ -57,6 +59,7 @@ class MockHTTP extends _i1.Mock implements _i2.HTTP { #url: url, #headers: headers, #proxyInfo: proxyInfo, + #connectionTimeout: connectionTimeout, }), ), ), @@ -93,4 +96,57 @@ class MockHTTP extends _i1.Mock implements _i2.HTTP { ), ) as _i3.Future<_i2.Response>); + + @override + _i3.Future<_i2.Response> patch({ + required Uri? url, + Map? headers, + Object? body, + required ({_i4.InternetAddress host, int port})? proxyInfo, + }) => + (super.noSuchMethod( + Invocation.method(#patch, [], { + #url: url, + #headers: headers, + #body: body, + #proxyInfo: proxyInfo, + }), + returnValue: _i3.Future<_i2.Response>.value( + _FakeResponse_0( + this, + Invocation.method(#patch, [], { + #url: url, + #headers: headers, + #body: body, + #proxyInfo: proxyInfo, + }), + ), + ), + ) + as _i3.Future<_i2.Response>); + + @override + _i3.Future<_i2.Response> delete({ + required Uri? url, + Map? headers, + required ({_i4.InternetAddress host, int port})? proxyInfo, + }) => + (super.noSuchMethod( + Invocation.method(#delete, [], { + #url: url, + #headers: headers, + #proxyInfo: proxyInfo, + }), + returnValue: _i3.Future<_i2.Response>.value( + _FakeResponse_0( + this, + Invocation.method(#delete, [], { + #url: url, + #headers: headers, + #proxyInfo: proxyInfo, + }), + ), + ), + ) + as _i3.Future<_i2.Response>); } diff --git a/test/screen_tests/lockscreen_view_screen_test.dart b/test/screen_tests/lockscreen_view_screen_test.dart index 8019ad22da..c513f715c1 100644 --- a/test/screen_tests/lockscreen_view_screen_test.dart +++ b/test/screen_tests/lockscreen_view_screen_test.dart @@ -1,312 +1,227 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/annotations.dart'; -// import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart'; - -import 'package:stackwallet/services/node_service.dart'; -import 'package:stackwallet/services/wallets_service.dart'; - -@GenerateMocks( - [], - customMocks: [ - MockSpec(), - MockSpec(), - ], -) +import 'package:mockito/mockito.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart'; +import 'package:stackwallet/providers/global/duress_provider.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_service.dart'; +import 'package:stackwallet/utilities/biometrics.dart'; +import 'package:stackwallet/widgets/custom_pin_put/pin_keyboard.dart'; + +import '../sample_data/theme_json.dart'; +import '../widget_tests/custom_loading_overlay_test.mocks.dart'; +import '../widget_tests/node_options_sheet_test.mocks.dart'; +import '../widget_tests/support/platform_test_overrides.dart'; + +class SpyBiometrics extends Biometrics { + SpyBiometrics({this.result = false}); + + final bool result; + int calls = 0; + + @override + Future authenticate({ + required String cancelButtonText, + required String localizedReason, + required String title, + }) async { + calls += 1; + return result; + } +} + void main() { - testWidgets("LockscreenView builds correctly", (tester) async { - // final navigator = mockingjay.MockNavigator(); - // final walletsService = MockWalletsService(); - // final nodeService = MockNodeService(); - // final wallet = MockManager(); - // final secureStore = FakeSecureStorage(); - // - // secureStore.write(key: "walletID", value: "1234"); - // - // when(walletsService.getWalletId("My Firo Wallet")) - // .thenAnswer((_) async => "walletID"); - // - // await tester.pumpWidget( - // MaterialApp( - // home: mockingjay.MockNavigatorProvider( - // navigator: navigator, - // child: MultiProvider( - // providers: [ - // ChangeNotifierProvider( - // create: (_) => walletsService, - // ), - // ChangeNotifierProvider( - // create: (_) => nodeService, - // ), - // ChangeNotifierProvider( - // create: (_) => manager, - // ), - // ], - // child: LockscreenView( - // routeOnSuccess: "/mainview", - // secureStore: secureStore, - // ), - // ), - // ), - // ), - // ); - // - // await tester.pumpAndSettle(); - // - // expect(find.byType(AppBarIconButton), findsOneWidget); - // expect(find.byType(SvgPicture), findsOneWidget); - // - // expect(find.text("My Firo Wallet"), findsOneWidget); - // expect(find.text("Enter PIN"), findsOneWidget); - // - // expect(find.byType(CustomPinPut), findsOneWidget); - }); + ThemeData buildTheme() { + return ThemeData( + extensions: [ + StackColors.fromStackColorTheme( + StackTheme.fromJson(json: lightThemeJsonMap), + ), + ], + ); + } - testWidgets("enter valid pin", (tester) async { - // final navigator = mockingjay.MockNavigator(); - // final walletsService = MockWalletsService(); - // final nodeService = MockNodeService(); - // final wallet = MockManager(); - // final secureStore = FakeSecureStorage(); - // - // secureStore.write(key: "walletID_pin", value: "1234"); - // - // when(walletsService.getWalletId("My Firo Wallet")) - // .thenAnswer((_) async => "walletID"); - // - // mockingjay - // .when(() => navigator.pushReplacementNamed("/mainview")) - // .thenAnswer((_) async => {}); - // - // await tester.pumpWidget( - // MaterialApp( - // home: mockingjay.MockNavigatorProvider( - // navigator: navigator, - // child: MultiProvider( - // providers: [ - // ChangeNotifierProvider( - // create: (_) => walletsService, - // ), - // ChangeNotifierProvider( - // create: (_) => nodeService, - // ), - // ChangeNotifierProvider( - // create: (_) => manager, - // ), - // ], - // child: LockscreenView( - // routeOnSuccess: "/mainview", - // secureStore: secureStore, - // ), - // ), - // ), - // ), - // ); - // - // await tester.pumpAndSettle(); - // - // await tester.tap(find.byWidgetPredicate( - // (widget) => widget is NumberKey && widget.number == "1")); - // await tester.pump(const Duration(milliseconds: 200)); - // await tester.tap(find.byWidgetPredicate( - // (widget) => widget is NumberKey && widget.number == "2")); - // await tester.pump(const Duration(milliseconds: 200)); - // await tester.tap(find.byWidgetPredicate( - // (widget) => widget is NumberKey && widget.number == "3")); - // await tester.pump(const Duration(milliseconds: 200)); - // await tester.tap(find.byWidgetPredicate( - // (widget) => widget is NumberKey && widget.number == "4")); - // await tester.pump(const Duration(milliseconds: 500)); - // - // expect(find.text("PIN code correct. Unlocking wallet..."), findsOneWidget); - // - // await tester.pump(const Duration(seconds: 2)); - // - // mockingjay - // .verify(() => navigator.pushReplacementNamed("/mainview")) - // .called(1); - }); + void stubPrefs(MockPrefs prefs) { + when(prefs.isInitialized).thenReturn(true); + when(prefs.randomizePIN).thenReturn(false); + when(prefs.autoPin).thenReturn(false); + when(prefs.useBiometrics).thenReturn(false); + when(prefs.biometricsDuress).thenReturn(false); + when(prefs.lastUnlocked).thenReturn(0); + } + + Future pumpLockscreenView( + WidgetTester tester, { + required MockPrefs prefs, + required SpyBiometrics biometrics, + required List overrides, + required bool isDuress, + VoidCallback? onSuccess, + }) async { + final mockThemeService = MockThemeService(); + final theme = StackTheme.fromJson(json: lightThemeJsonMap); + + when(mockThemeService.getTheme(themeId: 'light')).thenReturn(theme); + + final container = ProviderContainer( + overrides: [ + pThemeService.overrideWithValue(mockThemeService), + prefsChangeNotifierProvider.overrideWithValue(prefs), + ...overrides, + ], + ); + + addTearDown(container.dispose); + container.read(pDuress.notifier).state = isDuress; + + await tester.pumpWidget( + UncontrolledProviderScope( + container: container, + child: MaterialApp( + theme: buildTheme(), + routes: { + '/unlocked': (_) => const Scaffold(body: Text('unlocked route')), + }, + home: LockscreenView( + routeOnSuccess: '/unlocked', + biometricsAuthenticationTitle: 'Unlock wallet', + biometricsLocalizedReason: 'Unlock Stack Wallet', + biometricsCancelButtonString: 'Cancel', + biometrics: biometrics, + onSuccess: onSuccess, + ), + ), + ), + ); + + await tester.pumpAndSettle(); + return container; + } + + Future tapDigit(WidgetTester tester, String digit) async { + await tester.tap( + find.byWidgetPredicate( + (widget) => widget is NumberKey && widget.number == digit, + ), + ); + await tester.pump(const Duration(milliseconds: 250)); + } + + Future enterAndSubmitPin(WidgetTester tester, String pin) async { + for (final digit in pin.split('')) { + await tapDigit(tester, digit); + } + + await tester.tap(find.byType(SubmitKey)); + await tester.pump(); + } - testWidgets("wallet initialization fails", (tester) async { - // final navigator = mockingjay.MockNavigator(); - // final walletsService = MockWalletsService(); - // final nodeService = MockNodeService(); - // final wallet = MockManager(); - // final secureStore = FakeSecureStorage(); - // - // secureStore.write(key: "walletID_pin", value: "1234"); - // - // when(walletsService.getWalletId("My Firo Wallet")) - // .thenAnswer((_) async => "walletID"); - // - // mockingjay - // .when(() => navigator.pushReplacementNamed("/mainview")) - // .thenAnswer((_) async => {}); - // - // await tester.pumpWidget( - // MaterialApp( - // home: mockingjay.MockNavigatorProvider( - // navigator: navigator, - // child: MultiProvider( - // providers: [ - // ChangeNotifierProvider( - // create: (_) => walletsService, - // ), - // ChangeNotifierProvider( - // create: (_) => nodeService, - // ), - // ChangeNotifierProvider( - // create: (_) => manager, - // ), - // ], - // child: LockscreenView( - // routeOnSuccess: "/mainview", - // secureStore: secureStore, - // ), - // ), - // ), - // ), - // ); - // - // await tester.pumpAndSettle(); - // - // await tester.tap(find.byWidgetPredicate( - // (widget) => widget is NumberKey && widget.number == "1")); - // await tester.pump(const Duration(milliseconds: 200)); - // await tester.tap(find.byWidgetPredicate( - // (widget) => widget is NumberKey && widget.number == "2")); - // await tester.pump(const Duration(milliseconds: 200)); - // await tester.tap(find.byWidgetPredicate( - // (widget) => widget is NumberKey && widget.number == "3")); - // await tester.pump(const Duration(milliseconds: 200)); - // await tester.tap(find.byWidgetPredicate( - // (widget) => widget is NumberKey && widget.number == "4")); - // await tester.pump(const Duration(milliseconds: 500)); - // - // expect(find.text("PIN code correct. Unlocking wallet..."), findsOneWidget); - // - // await tester.pump(const Duration(seconds: 2)); - // - // expect( - // find.text( - // "Failed to connect to network. Check your internet connection and make sure the Electrum X node you are connected to is not having any issues."), - // findsOneWidget); - // - // await tester.tap(find.byKey(Key("campfireAlertOKButtonKey"))); - // await tester.pump(const Duration(seconds: 2)); - // await tester.pump(const Duration(seconds: 2)); - // - // expect( - // find.text( - // "Failed to connect to network. Check your internet connection and make sure the Electrum X node you are connected to is not having any issues."), - // findsNothing); - // - // mockingjay - // .verify(() => navigator.pushReplacementNamed("/mainview")) - // .called(1); + testWidgets('valid standard PIN unlocks through fake storage seam', ( + tester, + ) async { + final prefs = MockPrefs(); + final biometrics = SpyBiometrics(); + final platformOverrides = await createPlatformTestOverrides( + secureStorageEntries: {kPinKey: '1234', kDuressPinKey: '9876'}, + ); + var onSuccessCalls = 0; + + stubPrefs(prefs); + + await pumpLockscreenView( + tester, + prefs: prefs, + biometrics: biometrics, + overrides: platformOverrides.overrides, + isDuress: false, + onSuccess: () => onSuccessCalls += 1, + ); + + expect(find.text('Enter PIN'), findsOneWidget); + expect( + platformOverrides.secureStorage.writtenKeys, + containsAll([kPinKey, kDuressPinKey]), + ); + + await enterAndSubmitPin(tester, '1234'); + await tester.pumpAndSettle(const Duration(milliseconds: 200)); + + expect(platformOverrides.secureStorage.readKeys, [kPinKey]); + expect(platformOverrides.secureStorage.reads, 1); + expect(onSuccessCalls, 1); + expect(biometrics.calls, 0); + expect(find.text('unlocked route'), findsOneWidget); + + verify(prefs.lastUnlocked = any).called(1); }); - testWidgets("enter invalid pin", (tester) async { - // final navigator = mockingjay.MockNavigator(); - // final walletsService = MockWalletsService(); - // final nodeService = MockNodeService(); - // final wallet = MockManager(); - // final secureStore = FakeSecureStorage(); - // - // secureStore.write(key: "walletID_pin", value: "1234"); - // - // when(walletsService.getWalletId("My Firo Wallet")) - // .thenAnswer((_) async => "walletID"); - // - // mockingjay - // .when(() => navigator.pushReplacementNamed("/mainview")) - // .thenAnswer((_) async => {}); - - // await tester.pumpWidget( - // MaterialApp( - // home: mockingjay.MockNavigatorProvider( - // navigator: navigator, - // child: MultiProvider( - // providers: [ - // ChangeNotifierProvider( - // create: (_) => walletsService, - // ), - // ChangeNotifierProvider( - // create: (_) => nodeService, - // ), - // ChangeNotifierProvider( - // create: (_) => manager, - // ), - // ], - // child: LockscreenView( - // routeOnSuccess: "/mainview", - // secureStore: secureStore, - // ), - // ), - // ), - // ), - // ); - - // await tester.pumpAndSettle(); - // - // await tester.tap(find.byWidgetPredicate( - // (widget) => widget is NumberKey && widget.number == "1")); - // await tester.pump(const Duration(milliseconds: 200)); - // await tester.tap(find.byWidgetPredicate( - // (widget) => widget is NumberKey && widget.number == "1")); - // await tester.pump(const Duration(milliseconds: 200)); - // await tester.tap(find.byWidgetPredicate( - // (widget) => widget is NumberKey && widget.number == "3")); - // await tester.pump(const Duration(milliseconds: 200)); - // await tester.tap(find.byWidgetPredicate( - // (widget) => widget is NumberKey && widget.number == "4")); - // await tester.pump(const Duration(milliseconds: 500)); - // - // expect(find.text("Incorrect PIN. Please try again"), findsOneWidget); - // - // await tester.pump(const Duration(seconds: 2)); - // - // mockingjay.verifyNever(() => navigator.pushReplacementNamed("/mainview")); + testWidgets('duress mode unlocks with the duress PIN only', (tester) async { + final prefs = MockPrefs(); + final biometrics = SpyBiometrics(); + final platformOverrides = await createPlatformTestOverrides( + secureStorageEntries: {kPinKey: '1234', kDuressPinKey: '9876'}, + ); + var onSuccessCalls = 0; + + stubPrefs(prefs); + + final container = await pumpLockscreenView( + tester, + prefs: prefs, + biometrics: biometrics, + overrides: platformOverrides.overrides, + isDuress: true, + onSuccess: () => onSuccessCalls += 1, + ); + + await enterAndSubmitPin(tester, '9876'); + await tester.pumpAndSettle(const Duration(milliseconds: 200)); + + expect(platformOverrides.secureStorage.readKeys, [kDuressPinKey]); + expect(platformOverrides.secureStorage.reads, 1); + expect(container.read(pDuress), isTrue); + expect(onSuccessCalls, 1); + expect(biometrics.calls, 0); + expect(find.text('unlocked route'), findsOneWidget); + + verify(prefs.lastUnlocked = any).called(1); }); - testWidgets("tap back", (tester) async { - // final navigator = mockingjay.MockNavigator(); - // final walletsService = MockWalletsService(); - // final nodeService = MockNodeService(); - // final wallet = MockManager(); - // final secureStore = FakeSecureStorage(); - // - // mockingjay.when(() => navigator.pop()).thenAnswer((_) async => {}); - - // await tester.pumpWidget( - // MaterialApp( - // home: mockingjay.MockNavigatorProvider( - // navigator: navigator, - // child: MultiProvider( - // providers: [ - // ChangeNotifierProvider( - // create: (_) => walletsService, - // ), - // ChangeNotifierProvider( - // create: (_) => nodeService, - // ), - // ChangeNotifierProvider( - // create: (_) => manager, - // ), - // ], - // child: LockscreenView( - // routeOnSuccess: "/mainview", - // secureStore: secureStore, - // ), - // ), - // ), - // ), - // ); - - // await tester.pumpAndSettle(); - // - // await tester.tap(find.byType(AppBarIconButton)); - // await tester.pumpAndSettle(); - // - // mockingjay.verify(() => navigator.pop()).called(1); + testWidgets('duress mode rejects the standard PIN without unlocking', ( + tester, + ) async { + final prefs = MockPrefs(); + final biometrics = SpyBiometrics(); + final platformOverrides = await createPlatformTestOverrides( + secureStorageEntries: {kPinKey: '1234', kDuressPinKey: '9876'}, + ); + var onSuccessCalls = 0; + + stubPrefs(prefs); + + final container = await pumpLockscreenView( + tester, + prefs: prefs, + biometrics: biometrics, + overrides: platformOverrides.overrides, + isDuress: true, + onSuccess: () => onSuccessCalls += 1, + ); + + await enterAndSubmitPin(tester, '1234'); + await tester.pump(const Duration(milliseconds: 900)); + + expect(platformOverrides.secureStorage.readKeys, [kDuressPinKey]); + expect(platformOverrides.secureStorage.reads, 1); + expect(container.read(pDuress), isTrue); + expect(onSuccessCalls, 0); + expect(biometrics.calls, 0); + expect(find.text('Enter PIN'), findsOneWidget); + expect(find.text('unlocked route'), findsNothing); + + verifyNever(prefs.lastUnlocked = any); }); } diff --git a/test/screen_tests/lockscreen_view_screen_test.mocks.dart b/test/screen_tests/lockscreen_view_screen_test.mocks.dart deleted file mode 100644 index c54cd49630..0000000000 --- a/test/screen_tests/lockscreen_view_screen_test.mocks.dart +++ /dev/null @@ -1,254 +0,0 @@ -// Mocks generated by Mockito 5.4.6 from annotations -// in stackwallet/test/screen_tests/lockscreen_view_screen_test.dart. -// Do not manually edit this file. - -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i4; -import 'dart:ui' as _i5; - -import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/models/node_model.dart' as _i7; -import 'package:stackwallet/services/node_service.dart' as _i6; -import 'package:stackwallet/services/wallets_service.dart' as _i3; -import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' - as _i2; -import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart' - as _i8; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: deprecated_member_use -// ignore_for_file: deprecated_member_use_from_same_package -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: must_be_immutable -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types -// ignore_for_file: subtype_of_sealed_class -// ignore_for_file: invalid_use_of_internal_member - -class _FakeSecureStorageInterface_0 extends _i1.SmartFake - implements _i2.SecureStorageInterface { - _FakeSecureStorageInterface_0(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); -} - -/// A class which mocks [WalletsService]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockWalletsService extends _i1.Mock implements _i3.WalletsService { - MockWalletsService() { - _i1.throwOnMissingStub(this); - } - - @override - _i4.Future> get walletNames => - (super.noSuchMethod( - Invocation.getter(#walletNames), - returnValue: _i4.Future>.value( - {}, - ), - ) - as _i4.Future>); - - @override - bool get hasListeners => - (super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) - as bool); - - @override - void addListener(_i5.VoidCallback? listener) => super.noSuchMethod( - Invocation.method(#addListener, [listener]), - returnValueForMissingStub: null, - ); - - @override - void removeListener(_i5.VoidCallback? listener) => super.noSuchMethod( - Invocation.method(#removeListener, [listener]), - returnValueForMissingStub: null, - ); - - @override - void dispose() => super.noSuchMethod( - Invocation.method(#dispose, []), - returnValueForMissingStub: null, - ); - - @override - void notifyListeners() => super.noSuchMethod( - Invocation.method(#notifyListeners, []), - returnValueForMissingStub: null, - ); -} - -/// A class which mocks [NodeService]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockNodeService extends _i1.Mock implements _i6.NodeService { - MockNodeService() { - _i1.throwOnMissingStub(this); - } - - @override - _i2.SecureStorageInterface get secureStorageInterface => - (super.noSuchMethod( - Invocation.getter(#secureStorageInterface), - returnValue: _FakeSecureStorageInterface_0( - this, - Invocation.getter(#secureStorageInterface), - ), - ) - as _i2.SecureStorageInterface); - - @override - List<_i7.NodeModel> get primaryNodes => - (super.noSuchMethod( - Invocation.getter(#primaryNodes), - returnValue: <_i7.NodeModel>[], - ) - as List<_i7.NodeModel>); - - @override - List<_i7.NodeModel> get nodes => - (super.noSuchMethod( - Invocation.getter(#nodes), - returnValue: <_i7.NodeModel>[], - ) - as List<_i7.NodeModel>); - - @override - bool get hasListeners => - (super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) - as bool); - - @override - _i4.Future updateDefaults() => - (super.noSuchMethod( - Invocation.method(#updateDefaults, []), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) - as _i4.Future); - - @override - _i4.Future setPrimaryNodeFor({ - required _i8.CryptoCurrency? coin, - required _i7.NodeModel? node, - bool? shouldNotifyListeners = false, - }) => - (super.noSuchMethod( - Invocation.method(#setPrimaryNodeFor, [], { - #coin: coin, - #node: node, - #shouldNotifyListeners: shouldNotifyListeners, - }), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) - as _i4.Future); - - @override - _i7.NodeModel? getPrimaryNodeFor({required _i8.CryptoCurrency? currency}) => - (super.noSuchMethod( - Invocation.method(#getPrimaryNodeFor, [], {#currency: currency}), - ) - as _i7.NodeModel?); - - @override - List<_i7.NodeModel> getNodesFor(_i8.CryptoCurrency? coin) => - (super.noSuchMethod( - Invocation.method(#getNodesFor, [coin]), - returnValue: <_i7.NodeModel>[], - ) - as List<_i7.NodeModel>); - - @override - _i7.NodeModel? getNodeById({required String? id}) => - (super.noSuchMethod(Invocation.method(#getNodeById, [], {#id: id})) - as _i7.NodeModel?); - - @override - List<_i7.NodeModel> failoverNodesFor({ - required _i8.CryptoCurrency? currency, - }) => - (super.noSuchMethod( - Invocation.method(#failoverNodesFor, [], {#currency: currency}), - returnValue: <_i7.NodeModel>[], - ) - as List<_i7.NodeModel>); - - @override - _i4.Future save( - _i7.NodeModel? node, - String? password, - bool? shouldNotifyListeners, - ) => - (super.noSuchMethod( - Invocation.method(#save, [node, password, shouldNotifyListeners]), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) - as _i4.Future); - - @override - _i4.Future delete(String? id, bool? shouldNotifyListeners) => - (super.noSuchMethod( - Invocation.method(#delete, [id, shouldNotifyListeners]), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) - as _i4.Future); - - @override - _i4.Future setEnabledState( - String? id, - bool? enabled, - bool? shouldNotifyListeners, - ) => - (super.noSuchMethod( - Invocation.method(#setEnabledState, [ - id, - enabled, - shouldNotifyListeners, - ]), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) - as _i4.Future); - - @override - _i4.Future updateCommunityNodes() => - (super.noSuchMethod( - Invocation.method(#updateCommunityNodes, []), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) - as _i4.Future); - - @override - void addListener(_i5.VoidCallback? listener) => super.noSuchMethod( - Invocation.method(#addListener, [listener]), - returnValueForMissingStub: null, - ); - - @override - void removeListener(_i5.VoidCallback? listener) => super.noSuchMethod( - Invocation.method(#removeListener, [listener]), - returnValueForMissingStub: null, - ); - - @override - void dispose() => super.noSuchMethod( - Invocation.method(#dispose, []), - returnValueForMissingStub: null, - ); - - @override - void notifyListeners() => super.noSuchMethod( - Invocation.method(#notifyListeners, []), - returnValueForMissingStub: null, - ); -} diff --git a/test/screen_tests/onboarding/create_pin_view_screen_test.dart b/test/screen_tests/onboarding/create_pin_view_screen_test.dart index fcd0aa2c56..455d347c45 100644 --- a/test/screen_tests/onboarding/create_pin_view_screen_test.dart +++ b/test/screen_tests/onboarding/create_pin_view_screen_test.dart @@ -1,382 +1,207 @@ -// import 'package:flutter/material.dart'; -// import 'package:flutter_test/flutter_test.dart'; -// import 'package:mockingjay/mockingjay.dart' as mockingjay; -import 'package:mockito/annotations.dart'; -// import 'package:mockito/mockito.dart'; -// import 'package:stackwallet/pages/onboarding_view/create_pin_view.dart'; -// import 'package:stackwallet/pages/onboarding_view/helpers/create_wallet_type.dart'; - -import 'package:stackwallet/services/node_service.dart'; -import 'package:stackwallet/services/wallets_service.dart'; -// import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; -// import 'package:stackwallet/utilities/misc_global_constants.dart'; -// import 'package:stackwallet/widgets/custom_buttons/gradient_button.dart'; -// import 'package:stackwallet/widgets/custom_pin_put/custom_pin_put.dart'; -// import 'package:stackwallet/widgets/custom_pin_put/pin_keyboard.dart'; -// import 'package:provider/provider.dart'; -// -// import 'create_pin_view_screen_test.mocks.dart'; - -@GenerateMocks([], customMocks: [ - MockSpec(), - MockSpec(), -]) +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/pages/home_view/home_view.dart'; +import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart'; +import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_service.dart'; +import 'package:stackwallet/utilities/biometrics.dart'; +import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stackwallet/widgets/custom_pin_put/pin_keyboard.dart'; + +import '../../sample_data/theme_json.dart'; +import '../../widget_tests/custom_loading_overlay_test.mocks.dart'; +import '../../widget_tests/node_options_sheet_test.mocks.dart'; +import '../../widget_tests/support/platform_test_overrides.dart'; + +class SpyBiometrics extends Biometrics { + SpyBiometrics({this.result = false}); + + final bool result; + int calls = 0; + + @override + Future authenticate({ + required String cancelButtonText, + required String localizedReason, + required String title, + }) async { + calls += 1; + return result; + } +} + void main() { -// testWidgets("CreatePinView builds correctly", (tester) async { -// await tester.pumpWidget( -// MaterialApp( -// home: CreatePinView( -// type: CreateWalletType.NEW, -// walletName: "My Firo Wallet", -// useTestNet: false, -// ), -// ), -// ); -// -// expect(find.byKey(Key("onboardingAppBarBackButton")), findsOneWidget); -// expect(find.byKey(Key("onboardingAppBarBackButtonChevronSvg")), -// findsOneWidget); -// -// final imageFinder = find.byType(Image); -// expect(imageFinder, findsOneWidget); -// -// final imageSource = -// ((imageFinder.evaluate().single.widget as Image).image as AssetImage) -// .assetName; -// expect(imageSource, "assets/images/logo.png"); -// -// expect(find.text("Create a PIN"), findsOneWidget); -// -// expect(find.byType(CustomPinPut), findsOneWidget); -// }); -// -// testWidgets("back button test", (tester) async { -// final navigator = mockingjay.MockNavigator(); -// -// mockingjay.when(() => navigator.pop()).thenAnswer((_) async => {}); -// -// await tester.pumpWidget( -// MaterialApp( -// home: mockingjay.MockNavigatorProvider( -// navigator: navigator, -// child: CreatePinView( -// type: CreateWalletType.NEW, -// walletName: "My Firo Wallet", -// useTestNet: false, -// ), -// ), -// ), -// ); -// -// await tester.tap(find.byKey(Key("onboardingAppBarBackButton"))); -// await tester.pumpAndSettle(); -// -// mockingjay.verify(() => navigator.pop()).called(1); -// }); -// -// testWidgets("Entering unmatched PINs", (tester) async { -// await tester.pumpWidget( -// MaterialApp( -// home: CreatePinView( -// type: CreateWalletType.NEW, -// walletName: "My Firo Wallet", -// useTestNet: false, -// ), -// ), -// ); -// -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "1")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "2")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "3")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester -// .tap(find.byWidgetPredicate((widget) => widget is BackspaceKey)); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "6")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "4")); -// await tester.pumpAndSettle(Duration(seconds: 1)); -// -// expect(find.text("Confirm PIN"), findsOneWidget); -// -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "7")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "9")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "8")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "5")); -// await tester.pumpAndSettle(Duration(seconds: 2)); -// -// expect(find.text("Create a PIN"), findsOneWidget); -// }); -// -// testWidgets("Entering matched PINs on a new wallet", (tester) async { -// final navigator = mockingjay.MockNavigator(); -// final walletsService = MockWalletsService(); -// final wallet = MockManager(); -// final nodeService = MockNodeService(); -// -// final store = FakeSecureStorage(); -// -// mockingjay -// .when(() => navigator.push(mockingjay.any())) -// .thenAnswer((_) async => {}); -// -// when(walletsService.addNewWalletName("My Firo Wallet", "main")) -// .thenAnswer((_) async => true); -// when(walletsService.getWalletId("My Firo Wallet")) -// .thenAnswer((_) async => "walletID"); -// -// when(nodeService.reInit()).thenAnswer((_) => {}); -// when( -// nodeService.createNode( -// name: CampfireConstants.defaultNodeName, -// ipAddress: CampfireConstants.defaultIpAddress, -// port: CampfireConstants.defaultPort.toString(), -// useSSL: CampfireConstants.defaultUseSSL, -// ), -// ).thenAnswer((_) async => true); -// -// when(manager.initializeWallet()).thenAnswer((_) async => true); -// -// await tester.pumpWidget( -// MaterialApp( -// home: mockingjay.MockNavigatorProvider( -// navigator: navigator, -// child: MultiProvider( -// // home: MultiProvider( -// providers: [ -// ChangeNotifierProvider( -// create: (_) => walletsService, -// ), -// ChangeNotifierProvider( -// create: (_) => manager, -// ), -// ChangeNotifierProvider( -// create: (_) => nodeService, -// ), -// ], -// child: CreatePinView( -// type: CreateWalletType.NEW, -// walletName: "My Firo Wallet", -// useTestNet: false, -// secureStore: store, -// ), -// ), -// ), -// ), -// ); -// -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "1")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "2")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "3")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "4")); -// await tester.pumpAndSettle(Duration(seconds: 1)); -// -// expect(find.text("Confirm PIN"), findsOneWidget); -// -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "1")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "2")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "3")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "4")); -// await tester.pump(Duration(seconds: 2)); -// -// expect(find.byType(CircularProgressIndicator), findsOneWidget); -// -// await tester.pump(Duration(seconds: 20)); -// -// mockingjay.verify(() => navigator.push(mockingjay.any())).called(1); -// }); -// -// testWidgets("Wallet init fails on entering matched PINs", (tester) async { -// final navigator = mockingjay.MockNavigator(); -// final walletsService = MockWalletsService(); -// final wallet = MockManager(); -// final nodeService = MockNodeService(); -// -// final store = FakeSecureStorage(); -// -// mockingjay.when(() => navigator.pop()).thenAnswer((_) async => {}); -// -// when(walletsService.addNewWalletName("My Firo Wallet", "main")) -// .thenAnswer((_) async => true); -// when(walletsService.getWalletId("My Firo Wallet")) -// .thenAnswer((_) async => "walletID"); -// -// when(nodeService.reInit()).thenAnswer((_) => {}); -// when( -// nodeService.createNode( -// name: CampfireConstants.defaultNodeNameTestNet, -// ipAddress: CampfireConstants.defaultIpAddressTestNet, -// port: CampfireConstants.defaultPortTestNet.toString(), -// useSSL: CampfireConstants.defaultUseSSLTestNet, -// ), -// ).thenAnswer((_) async => true); -// -// when(manager.initializeWallet()).thenAnswer((_) async => false); -// when(manager.exitCurrentWallet()).thenAnswer((_) async => {}); -// when(walletsService.deleteWallet("My Firo Wallet")) -// .thenAnswer((_) async => 0); -// -// await tester.pumpWidget( -// MaterialApp( -// home: mockingjay.MockNavigatorProvider( -// navigator: navigator, -// child: MultiProvider( -// // home: MultiProvider( -// providers: [ -// ChangeNotifierProvider( -// create: (_) => walletsService, -// ), -// ChangeNotifierProvider( -// create: (_) => manager, -// ), -// ChangeNotifierProvider( -// create: (_) => nodeService, -// ), -// ], -// child: CreatePinView( -// type: CreateWalletType.NEW, -// walletName: "My Firo Wallet", -// useTestNet: true, -// secureStore: store, -// ), -// ), -// ), -// ), -// ); -// -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "1")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "2")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "3")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "4")); -// await tester.pumpAndSettle(Duration(seconds: 1)); -// -// expect(find.text("Confirm PIN"), findsOneWidget); -// -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "1")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "2")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "3")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "4")); -// await tester.pump(Duration(seconds: 2)); -// -// expect( -// find.text( -// "Failed to connect to network. Check your internet connection."), -// findsOneWidget); -// expect(find.text("OK"), findsOneWidget); -// -// await tester.tap(find.byType(GradientButton)); -// await tester.pump(Duration(seconds: 1)); -// -// mockingjay.verify(() => navigator.pop()).called(4); -// }); -// -// testWidgets("Entering matched PINs on a restore", (tester) async { -// final navigator = mockingjay.MockNavigator(); -// final walletsService = MockWalletsService(); -// -// final store = FakeSecureStorage(); -// -// mockingjay -// .when(() => navigator.push(mockingjay.any())) -// .thenAnswer((_) async => {}); -// -// when(walletsService.addNewWalletName("My Firo Wallet", "main")) -// .thenAnswer((_) async => true); -// when(walletsService.getWalletId("My Firo Wallet")) -// .thenAnswer((_) async => "walletID"); -// -// await tester.pumpWidget( -// MaterialApp( -// home: mockingjay.MockNavigatorProvider( -// navigator: navigator, -// child: MultiProvider( -// // home: MultiProvider( -// providers: [ -// ChangeNotifierProvider( -// create: (_) => walletsService, -// ), -// ], -// child: CreatePinView( -// type: CreateWalletType.RESTORE, -// walletName: "My Firo Wallet", -// useTestNet: false, -// secureStore: store, -// ), -// ), -// ), -// ), -// ); -// -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "1")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "2")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "3")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "4")); -// await tester.pumpAndSettle(Duration(seconds: 1)); -// -// expect(find.text("Confirm PIN"), findsOneWidget); -// -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "1")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "2")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "3")); -// await tester.pumpAndSettle(Duration(milliseconds: 100)); -// await tester.tap(find.byWidgetPredicate( -// (widget) => widget is NumberKey && widget.number == "4")); -// await tester.pumpAndSettle(Duration(seconds: 6)); -// -// mockingjay.verify(() => navigator.push(mockingjay.any())).called(1); -// }); + ThemeData buildTheme() { + return ThemeData( + extensions: [ + StackColors.fromStackColorTheme( + StackTheme.fromJson(json: lightThemeJsonMap), + ), + ], + ); + } + + void stubPrefs(MockPrefs prefs) { + when(prefs.randomizePIN).thenReturn(false); + when(prefs.hasPin).thenReturn(false); + } + + Future pumpCreatePinView( + WidgetTester tester, { + required MockPrefs prefs, + required SpyBiometrics biometrics, + required List overrides, + }) async { + final mockThemeService = MockThemeService(); + final theme = StackTheme.fromJson(json: lightThemeJsonMap); + + when(mockThemeService.getTheme(themeId: 'light')).thenReturn(theme); + + await tester.pumpWidget( + ProviderScope( + overrides: [ + pThemeService.overrideWithValue(mockThemeService), + prefsChangeNotifierProvider.overrideWithValue(prefs), + ...overrides, + ], + child: MaterialApp( + theme: buildTheme(), + routes: { + HomeView.routeName: (_) => const Scaffold(body: Text('home route')), + }, + home: CreatePinView(biometrics: biometrics), + ), + ), + ); + + await tester.pumpAndSettle(); + } + + Future tapDigit(WidgetTester tester, String digit) async { + await tester.tap( + find.byWidgetPredicate( + (widget) => widget is NumberKey && widget.number == digit, + ), + ); + await tester.pump(const Duration(milliseconds: 250)); + } + + Future enterPin(WidgetTester tester, String pin) async { + for (final digit in pin.split('')) { + await tapDigit(tester, digit); + } + } + + Future submitCurrentPin(WidgetTester tester) async { + await tester.tap(find.byType(SubmitKey)); + await tester.pump(); + } + + testWidgets('matching PIN persists through fake secure storage seam', ( + tester, + ) async { + final prefs = MockPrefs(); + final biometrics = SpyBiometrics(); + final platformOverrides = await createPlatformTestOverrides(); + + stubPrefs(prefs); + + await pumpCreatePinView( + tester, + prefs: prefs, + biometrics: biometrics, + overrides: platformOverrides.overrides, + ); + + expect(find.text('Create a PIN'), findsOneWidget); + + await enterPin(tester, '1234'); + await submitCurrentPin(tester); + await tester.pumpAndSettle(); + + expect(find.text('Confirm PIN'), findsOneWidget); + + await enterPin(tester, '1234'); + await submitCurrentPin(tester); + await tester.pumpAndSettle(const Duration(milliseconds: 300)); + + expect(await platformOverrides.secureStorage.read(key: kPinKey), '1234'); + expect(platformOverrides.secureStorage.writes, 1); + expect(biometrics.calls, 0); + + verify(prefs.useBiometrics = false).called(1); + verify(prefs.hasPin = true).called(1); + expect(find.text('home route'), findsOneWidget); + }); + + testWidgets('short PIN submission is blocked before confirmation page', ( + tester, + ) async { + final prefs = MockPrefs(); + final biometrics = SpyBiometrics(); + final platformOverrides = await createPlatformTestOverrides(); + + stubPrefs(prefs); + + await pumpCreatePinView( + tester, + prefs: prefs, + biometrics: biometrics, + overrides: platformOverrides.overrides, + ); + + await enterPin(tester, '123'); + await submitCurrentPin(tester); + await tester.pumpAndSettle(); + + expect(find.text('Create a PIN'), findsOneWidget); + expect(find.text('Confirm PIN'), findsNothing); + expect(await platformOverrides.secureStorage.read(key: kPinKey), isNull); + expect(platformOverrides.secureStorage.writes, 0); + expect(biometrics.calls, 0); + + verifyNever(prefs.useBiometrics = false); + verifyNever(prefs.hasPin = true); + }); + + testWidgets('mismatched confirmation resets flow without storage writes', ( + tester, + ) async { + final prefs = MockPrefs(); + final biometrics = SpyBiometrics(); + final platformOverrides = await createPlatformTestOverrides(); + + stubPrefs(prefs); + + await pumpCreatePinView( + tester, + prefs: prefs, + biometrics: biometrics, + overrides: platformOverrides.overrides, + ); + + await enterPin(tester, '1234'); + await submitCurrentPin(tester); + await tester.pumpAndSettle(); + + expect(find.text('Confirm PIN'), findsOneWidget); + + await enterPin(tester, '9876'); + await submitCurrentPin(tester); + await tester.pumpAndSettle(); + + expect(find.text('Create a PIN'), findsOneWidget); + expect(find.text('Confirm PIN'), findsNothing); + expect(await platformOverrides.secureStorage.read(key: kPinKey), isNull); + expect(platformOverrides.secureStorage.writes, 0); + expect(biometrics.calls, 0); + + verifyNever(prefs.useBiometrics = false); + verifyNever(prefs.hasPin = true); + }); } diff --git a/test/screen_tests/onboarding/create_pin_view_screen_test.mocks.dart b/test/screen_tests/onboarding/create_pin_view_screen_test.mocks.dart deleted file mode 100644 index de530dad1f..0000000000 --- a/test/screen_tests/onboarding/create_pin_view_screen_test.mocks.dart +++ /dev/null @@ -1,254 +0,0 @@ -// Mocks generated by Mockito 5.4.6 from annotations -// in stackwallet/test/screen_tests/onboarding/create_pin_view_screen_test.dart. -// Do not manually edit this file. - -// ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i4; -import 'dart:ui' as _i5; - -import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/models/node_model.dart' as _i7; -import 'package:stackwallet/services/node_service.dart' as _i6; -import 'package:stackwallet/services/wallets_service.dart' as _i3; -import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' - as _i2; -import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart' - as _i8; - -// ignore_for_file: type=lint -// ignore_for_file: avoid_redundant_argument_values -// ignore_for_file: avoid_setters_without_getters -// ignore_for_file: comment_references -// ignore_for_file: deprecated_member_use -// ignore_for_file: deprecated_member_use_from_same_package -// ignore_for_file: implementation_imports -// ignore_for_file: invalid_use_of_visible_for_testing_member -// ignore_for_file: must_be_immutable -// ignore_for_file: prefer_const_constructors -// ignore_for_file: unnecessary_parenthesis -// ignore_for_file: camel_case_types -// ignore_for_file: subtype_of_sealed_class -// ignore_for_file: invalid_use_of_internal_member - -class _FakeSecureStorageInterface_0 extends _i1.SmartFake - implements _i2.SecureStorageInterface { - _FakeSecureStorageInterface_0(Object parent, Invocation parentInvocation) - : super(parent, parentInvocation); -} - -/// A class which mocks [WalletsService]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockWalletsService extends _i1.Mock implements _i3.WalletsService { - MockWalletsService() { - _i1.throwOnMissingStub(this); - } - - @override - _i4.Future> get walletNames => - (super.noSuchMethod( - Invocation.getter(#walletNames), - returnValue: _i4.Future>.value( - {}, - ), - ) - as _i4.Future>); - - @override - bool get hasListeners => - (super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) - as bool); - - @override - void addListener(_i5.VoidCallback? listener) => super.noSuchMethod( - Invocation.method(#addListener, [listener]), - returnValueForMissingStub: null, - ); - - @override - void removeListener(_i5.VoidCallback? listener) => super.noSuchMethod( - Invocation.method(#removeListener, [listener]), - returnValueForMissingStub: null, - ); - - @override - void dispose() => super.noSuchMethod( - Invocation.method(#dispose, []), - returnValueForMissingStub: null, - ); - - @override - void notifyListeners() => super.noSuchMethod( - Invocation.method(#notifyListeners, []), - returnValueForMissingStub: null, - ); -} - -/// A class which mocks [NodeService]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockNodeService extends _i1.Mock implements _i6.NodeService { - MockNodeService() { - _i1.throwOnMissingStub(this); - } - - @override - _i2.SecureStorageInterface get secureStorageInterface => - (super.noSuchMethod( - Invocation.getter(#secureStorageInterface), - returnValue: _FakeSecureStorageInterface_0( - this, - Invocation.getter(#secureStorageInterface), - ), - ) - as _i2.SecureStorageInterface); - - @override - List<_i7.NodeModel> get primaryNodes => - (super.noSuchMethod( - Invocation.getter(#primaryNodes), - returnValue: <_i7.NodeModel>[], - ) - as List<_i7.NodeModel>); - - @override - List<_i7.NodeModel> get nodes => - (super.noSuchMethod( - Invocation.getter(#nodes), - returnValue: <_i7.NodeModel>[], - ) - as List<_i7.NodeModel>); - - @override - bool get hasListeners => - (super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) - as bool); - - @override - _i4.Future updateDefaults() => - (super.noSuchMethod( - Invocation.method(#updateDefaults, []), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) - as _i4.Future); - - @override - _i4.Future setPrimaryNodeFor({ - required _i8.CryptoCurrency? coin, - required _i7.NodeModel? node, - bool? shouldNotifyListeners = false, - }) => - (super.noSuchMethod( - Invocation.method(#setPrimaryNodeFor, [], { - #coin: coin, - #node: node, - #shouldNotifyListeners: shouldNotifyListeners, - }), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) - as _i4.Future); - - @override - _i7.NodeModel? getPrimaryNodeFor({required _i8.CryptoCurrency? currency}) => - (super.noSuchMethod( - Invocation.method(#getPrimaryNodeFor, [], {#currency: currency}), - ) - as _i7.NodeModel?); - - @override - List<_i7.NodeModel> getNodesFor(_i8.CryptoCurrency? coin) => - (super.noSuchMethod( - Invocation.method(#getNodesFor, [coin]), - returnValue: <_i7.NodeModel>[], - ) - as List<_i7.NodeModel>); - - @override - _i7.NodeModel? getNodeById({required String? id}) => - (super.noSuchMethod(Invocation.method(#getNodeById, [], {#id: id})) - as _i7.NodeModel?); - - @override - List<_i7.NodeModel> failoverNodesFor({ - required _i8.CryptoCurrency? currency, - }) => - (super.noSuchMethod( - Invocation.method(#failoverNodesFor, [], {#currency: currency}), - returnValue: <_i7.NodeModel>[], - ) - as List<_i7.NodeModel>); - - @override - _i4.Future save( - _i7.NodeModel? node, - String? password, - bool? shouldNotifyListeners, - ) => - (super.noSuchMethod( - Invocation.method(#save, [node, password, shouldNotifyListeners]), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) - as _i4.Future); - - @override - _i4.Future delete(String? id, bool? shouldNotifyListeners) => - (super.noSuchMethod( - Invocation.method(#delete, [id, shouldNotifyListeners]), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) - as _i4.Future); - - @override - _i4.Future setEnabledState( - String? id, - bool? enabled, - bool? shouldNotifyListeners, - ) => - (super.noSuchMethod( - Invocation.method(#setEnabledState, [ - id, - enabled, - shouldNotifyListeners, - ]), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) - as _i4.Future); - - @override - _i4.Future updateCommunityNodes() => - (super.noSuchMethod( - Invocation.method(#updateCommunityNodes, []), - returnValue: _i4.Future.value(), - returnValueForMissingStub: _i4.Future.value(), - ) - as _i4.Future); - - @override - void addListener(_i5.VoidCallback? listener) => super.noSuchMethod( - Invocation.method(#addListener, [listener]), - returnValueForMissingStub: null, - ); - - @override - void removeListener(_i5.VoidCallback? listener) => super.noSuchMethod( - Invocation.method(#removeListener, [listener]), - returnValueForMissingStub: null, - ); - - @override - void dispose() => super.noSuchMethod( - Invocation.method(#dispose, []), - returnValueForMissingStub: null, - ); - - @override - void notifyListeners() => super.noSuchMethod( - Invocation.method(#notifyListeners, []), - returnValueForMissingStub: null, - ); -} diff --git a/test/screen_tests/onboarding/restore_wallet_view_screen_test.mocks.dart b/test/screen_tests/onboarding/restore_wallet_view_screen_test.mocks.dart index 1b9698d161..4f133dc983 100644 --- a/test/screen_tests/onboarding/restore_wallet_view_screen_test.mocks.dart +++ b/test/screen_tests/onboarding/restore_wallet_view_screen_test.mocks.dart @@ -8,6 +8,7 @@ import 'dart:ui' as _i7; import 'package:flutter/material.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/epicbox_server_model.dart' as _i11; import 'package:stackwallet/models/node_model.dart' as _i9; import 'package:stackwallet/services/node_service.dart' as _i8; import 'package:stackwallet/services/wallets_service.dart' as _i6; @@ -249,6 +250,64 @@ class MockNodeService extends _i1.Mock implements _i8.NodeService { ) as _i4.Future); + @override + _i4.Future updateDefaultEpicBoxes() => + (super.noSuchMethod( + Invocation.method(#updateDefaultEpicBoxes, []), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future setPrimaryEpicBox({ + required _i11.EpicBoxServerModel? epicBox, + bool? shouldNotifyListeners = false, + }) => + (super.noSuchMethod( + Invocation.method(#setPrimaryEpicBox, [], { + #epicBox: epicBox, + #shouldNotifyListeners: shouldNotifyListeners, + }), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + List<_i11.EpicBoxServerModel> getEpicBoxes() => + (super.noSuchMethod( + Invocation.method(#getEpicBoxes, []), + returnValue: <_i11.EpicBoxServerModel>[], + ) + as List<_i11.EpicBoxServerModel>); + + @override + _i11.EpicBoxServerModel? getEpicBoxById({required String? id}) => + (super.noSuchMethod(Invocation.method(#getEpicBoxById, [], {#id: id})) + as _i11.EpicBoxServerModel?); + + @override + _i4.Future addEpicBox( + _i11.EpicBoxServerModel? epicBox, + bool? shouldNotifyListeners, + ) => + (super.noSuchMethod( + Invocation.method(#addEpicBox, [epicBox, shouldNotifyListeners]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + + @override + _i4.Future deleteEpicBox(String? id, bool? shouldNotifyListeners) => + (super.noSuchMethod( + Invocation.method(#deleteEpicBox, [id, shouldNotifyListeners]), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) + as _i4.Future); + @override _i4.Future updateCommunityNodes() => (super.noSuchMethod( diff --git a/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view_screen_test.mocks.dart index 7448120ce1..2e86d9e57e 100644 --- a/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view_screen_test.mocks.dart @@ -4,9 +4,10 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i5; -import 'dart:ui' as _i7; +import 'dart:ui' as _i8; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/epicbox_server_model.dart' as _i7; import 'package:stackwallet/models/node_model.dart' as _i4; import 'package:stackwallet/services/node_service.dart' as _i3; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' @@ -170,6 +171,64 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { ) as _i5.Future); + @override + _i5.Future updateDefaultEpicBoxes() => + (super.noSuchMethod( + Invocation.method(#updateDefaultEpicBoxes, []), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setPrimaryEpicBox({ + required _i7.EpicBoxServerModel? epicBox, + bool? shouldNotifyListeners = false, + }) => + (super.noSuchMethod( + Invocation.method(#setPrimaryEpicBox, [], { + #epicBox: epicBox, + #shouldNotifyListeners: shouldNotifyListeners, + }), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + List<_i7.EpicBoxServerModel> getEpicBoxes() => + (super.noSuchMethod( + Invocation.method(#getEpicBoxes, []), + returnValue: <_i7.EpicBoxServerModel>[], + ) + as List<_i7.EpicBoxServerModel>); + + @override + _i7.EpicBoxServerModel? getEpicBoxById({required String? id}) => + (super.noSuchMethod(Invocation.method(#getEpicBoxById, [], {#id: id})) + as _i7.EpicBoxServerModel?); + + @override + _i5.Future addEpicBox( + _i7.EpicBoxServerModel? epicBox, + bool? shouldNotifyListeners, + ) => + (super.noSuchMethod( + Invocation.method(#addEpicBox, [epicBox, shouldNotifyListeners]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future deleteEpicBox(String? id, bool? shouldNotifyListeners) => + (super.noSuchMethod( + Invocation.method(#deleteEpicBox, [id, shouldNotifyListeners]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + @override _i5.Future updateCommunityNodes() => (super.noSuchMethod( @@ -180,13 +239,13 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { as _i5.Future); @override - void addListener(_i7.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#addListener, [listener]), returnValueForMissingStub: null, ); @override - void removeListener(_i7.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#removeListener, [listener]), returnValueForMissingStub: null, ); diff --git a/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/node_details_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/node_details_view_screen_test.mocks.dart index da7af34723..eeb6f02923 100644 --- a/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/node_details_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/node_details_view_screen_test.mocks.dart @@ -4,9 +4,10 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i5; -import 'dart:ui' as _i7; +import 'dart:ui' as _i8; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/epicbox_server_model.dart' as _i7; import 'package:stackwallet/models/node_model.dart' as _i4; import 'package:stackwallet/services/node_service.dart' as _i3; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' @@ -170,6 +171,64 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { ) as _i5.Future); + @override + _i5.Future updateDefaultEpicBoxes() => + (super.noSuchMethod( + Invocation.method(#updateDefaultEpicBoxes, []), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setPrimaryEpicBox({ + required _i7.EpicBoxServerModel? epicBox, + bool? shouldNotifyListeners = false, + }) => + (super.noSuchMethod( + Invocation.method(#setPrimaryEpicBox, [], { + #epicBox: epicBox, + #shouldNotifyListeners: shouldNotifyListeners, + }), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + List<_i7.EpicBoxServerModel> getEpicBoxes() => + (super.noSuchMethod( + Invocation.method(#getEpicBoxes, []), + returnValue: <_i7.EpicBoxServerModel>[], + ) + as List<_i7.EpicBoxServerModel>); + + @override + _i7.EpicBoxServerModel? getEpicBoxById({required String? id}) => + (super.noSuchMethod(Invocation.method(#getEpicBoxById, [], {#id: id})) + as _i7.EpicBoxServerModel?); + + @override + _i5.Future addEpicBox( + _i7.EpicBoxServerModel? epicBox, + bool? shouldNotifyListeners, + ) => + (super.noSuchMethod( + Invocation.method(#addEpicBox, [epicBox, shouldNotifyListeners]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future deleteEpicBox(String? id, bool? shouldNotifyListeners) => + (super.noSuchMethod( + Invocation.method(#deleteEpicBox, [id, shouldNotifyListeners]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + @override _i5.Future updateCommunityNodes() => (super.noSuchMethod( @@ -180,13 +239,13 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { as _i5.Future); @override - void addListener(_i7.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#addListener, [listener]), returnValueForMissingStub: null, ); @override - void removeListener(_i7.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#removeListener, [listener]), returnValueForMissingStub: null, ); diff --git a/test/screen_tests/settings_view/settings_subviews/network_settings_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/network_settings_view_screen_test.mocks.dart index 241eb0d584..0d75015217 100644 --- a/test/screen_tests/settings_view/settings_subviews/network_settings_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/network_settings_view_screen_test.mocks.dart @@ -4,9 +4,10 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i5; -import 'dart:ui' as _i7; +import 'dart:ui' as _i8; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/epicbox_server_model.dart' as _i7; import 'package:stackwallet/models/node_model.dart' as _i4; import 'package:stackwallet/services/node_service.dart' as _i3; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' @@ -170,6 +171,64 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { ) as _i5.Future); + @override + _i5.Future updateDefaultEpicBoxes() => + (super.noSuchMethod( + Invocation.method(#updateDefaultEpicBoxes, []), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setPrimaryEpicBox({ + required _i7.EpicBoxServerModel? epicBox, + bool? shouldNotifyListeners = false, + }) => + (super.noSuchMethod( + Invocation.method(#setPrimaryEpicBox, [], { + #epicBox: epicBox, + #shouldNotifyListeners: shouldNotifyListeners, + }), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + List<_i7.EpicBoxServerModel> getEpicBoxes() => + (super.noSuchMethod( + Invocation.method(#getEpicBoxes, []), + returnValue: <_i7.EpicBoxServerModel>[], + ) + as List<_i7.EpicBoxServerModel>); + + @override + _i7.EpicBoxServerModel? getEpicBoxById({required String? id}) => + (super.noSuchMethod(Invocation.method(#getEpicBoxById, [], {#id: id})) + as _i7.EpicBoxServerModel?); + + @override + _i5.Future addEpicBox( + _i7.EpicBoxServerModel? epicBox, + bool? shouldNotifyListeners, + ) => + (super.noSuchMethod( + Invocation.method(#addEpicBox, [epicBox, shouldNotifyListeners]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future deleteEpicBox(String? id, bool? shouldNotifyListeners) => + (super.noSuchMethod( + Invocation.method(#deleteEpicBox, [id, shouldNotifyListeners]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + @override _i5.Future updateCommunityNodes() => (super.noSuchMethod( @@ -180,13 +239,13 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { as _i5.Future); @override - void addListener(_i7.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#addListener, [listener]), returnValueForMissingStub: null, ); @override - void removeListener(_i7.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#removeListener, [listener]), returnValueForMissingStub: null, ); diff --git a/test/screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart index 6105ab2b81..453540f038 100644 --- a/test/screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart @@ -100,6 +100,22 @@ class MockCachedElectrumXClient extends _i1.Mock ) as _i5.Future>); + @override + _i5.Future>> getBatchTransactions({ + required List? txHashes, + required _i6.CryptoCurrency? cryptoCurrency, + }) => + (super.noSuchMethod( + Invocation.method(#getBatchTransactions, [], { + #txHashes: txHashes, + #cryptoCurrency: cryptoCurrency, + }), + returnValue: _i5.Future>>.value( + >[], + ), + ) + as _i5.Future>>); + @override _i5.Future clearSharedTransactionCache({ required _i6.CryptoCurrency? cryptoCurrency, diff --git a/test/services/change_now/change_now_sample_data.dart b/test/services/change_now/change_now_sample_data.dart index 4e63dbf884..b396edbf9d 100644 --- a/test/services/change_now/change_now_sample_data.dart +++ b/test/services/change_now/change_now_sample_data.dart @@ -7,7 +7,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "eth", @@ -17,7 +17,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ethbsc", @@ -27,7 +27,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdt", @@ -37,7 +37,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdterc20", @@ -48,7 +48,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": true, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdttrc20", @@ -59,7 +59,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdtbsc", @@ -69,7 +69,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdc", @@ -79,7 +79,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": true, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdcmatic", @@ -90,7 +90,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bnbmainnet", @@ -101,7 +101,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bnbbsc", @@ -111,7 +111,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "busd", @@ -121,7 +121,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "busdbsc", @@ -131,7 +131,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xrp", @@ -141,7 +141,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xrpbsc", @@ -151,7 +151,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ada", @@ -161,7 +161,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "adabsc", @@ -171,7 +171,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sol", @@ -181,7 +181,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "doge", @@ -191,7 +191,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dot", @@ -201,7 +201,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dotbsc", @@ -211,7 +211,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dai", @@ -221,7 +221,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "matic", @@ -231,7 +231,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "maticmainnet", @@ -242,7 +242,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "shib", @@ -252,7 +252,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "shibbsc", @@ -262,7 +262,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "trx", @@ -272,7 +272,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avax", @@ -282,7 +282,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avaxc", @@ -292,7 +292,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "wbtc", @@ -302,7 +302,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "leo", @@ -312,7 +312,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "uni", @@ -322,7 +322,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "etc", @@ -332,7 +332,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ltc", @@ -342,7 +342,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ltcbsc", @@ -352,7 +352,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ftt", @@ -362,7 +362,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "atom", @@ -372,7 +372,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "link", @@ -382,7 +382,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cro", @@ -392,7 +392,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "near", @@ -402,7 +402,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xmr", @@ -412,7 +412,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xlm", @@ -422,7 +422,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bch", @@ -432,7 +432,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "algo", @@ -442,7 +442,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "flow", @@ -452,7 +452,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "vet", @@ -462,7 +462,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "icp", @@ -472,7 +472,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fil", @@ -482,7 +482,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ape", @@ -492,7 +492,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "eos", @@ -502,7 +502,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mana", @@ -512,7 +512,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sand", @@ -522,7 +522,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hbar", @@ -532,7 +532,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xtz", @@ -542,7 +542,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xtzbsc", @@ -552,7 +552,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "chz", @@ -562,7 +562,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "qnt", @@ -572,7 +572,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "egld", @@ -582,7 +582,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "aave", @@ -592,7 +592,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "theta", @@ -602,7 +602,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "axs", @@ -612,7 +612,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tusd", @@ -622,7 +622,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bsv", @@ -632,7 +632,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "okb", @@ -642,7 +642,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "galabsc", @@ -652,7 +652,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zec", @@ -662,7 +662,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdp", @@ -672,7 +672,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bttbsc", @@ -682,7 +682,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "iota", @@ -692,7 +692,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mkr", @@ -702,7 +702,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hnt", @@ -712,7 +712,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ht", @@ -722,7 +722,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "snx", @@ -732,7 +732,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "grt", @@ -742,7 +742,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ftm", @@ -752,7 +752,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ftmmainnet", @@ -762,7 +762,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "klay", @@ -772,7 +772,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "neo", @@ -782,7 +782,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rune", @@ -792,7 +792,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "paxg", @@ -802,7 +802,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ldo", @@ -812,7 +812,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cake", @@ -822,7 +822,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "crv", @@ -832,7 +832,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nexo", @@ -842,7 +842,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bat", @@ -852,7 +852,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dash", @@ -862,7 +862,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "waves", @@ -872,7 +872,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zil", @@ -882,7 +882,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lrc", @@ -892,7 +892,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "enj", @@ -902,7 +902,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ksm", @@ -912,7 +912,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dcr", @@ -922,7 +922,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "btg", @@ -932,7 +932,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gmt", @@ -942,7 +942,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "twt", @@ -952,7 +952,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gno", @@ -962,7 +962,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xem", @@ -972,7 +972,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "1inch", @@ -982,7 +982,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "1inchbsc", @@ -992,7 +992,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "celo", @@ -1002,7 +1002,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hot", @@ -1012,7 +1012,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ust", @@ -1023,7 +1023,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "galaerc20", @@ -1034,7 +1034,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ankr", @@ -1044,7 +1044,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "comp", @@ -1054,7 +1054,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gt", @@ -1064,7 +1064,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "cvx", @@ -1074,7 +1074,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "qtum", @@ -1084,7 +1084,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "yfi", @@ -1094,7 +1094,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xdc", @@ -1104,7 +1104,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "kda", @@ -1114,7 +1114,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "iotx", @@ -1124,7 +1124,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cel", @@ -1134,7 +1134,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gusd", @@ -1144,7 +1144,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tfuel", @@ -1154,7 +1154,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rvn", @@ -1164,7 +1164,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "flux", @@ -1174,7 +1174,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bal", @@ -1184,7 +1184,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "amp", @@ -1194,7 +1194,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "op", @@ -1204,7 +1204,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "omg", @@ -1214,7 +1214,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "one", @@ -1224,7 +1224,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zrx", @@ -1234,7 +1234,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rsr", @@ -1244,7 +1244,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "jst", @@ -1254,7 +1254,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "icx", @@ -1264,7 +1264,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xym", @@ -1274,7 +1274,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "iost", @@ -1284,7 +1284,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ens", @@ -1294,7 +1294,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lpt", @@ -1304,7 +1304,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "glm", @@ -1314,7 +1314,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "audio", @@ -1324,7 +1324,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "storj", @@ -1334,7 +1334,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ont", @@ -1344,7 +1344,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ontbsc", @@ -1354,7 +1354,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "waxp", @@ -1364,7 +1364,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "srm", @@ -1374,7 +1374,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sc", @@ -1384,7 +1384,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "imx", @@ -1394,7 +1394,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zen", @@ -1404,7 +1404,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "uma", @@ -1414,7 +1414,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "scrt", @@ -1424,7 +1424,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mxc", @@ -1434,7 +1434,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "btrst", @@ -1444,7 +1444,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "skl", @@ -1454,7 +1454,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "poly", @@ -1464,7 +1464,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "slp", @@ -1474,7 +1474,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "woobsc", @@ -1484,7 +1484,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "woo", @@ -1494,7 +1494,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "chsb", @@ -1504,7 +1504,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cspr", @@ -1514,7 +1514,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "dgb", @@ -1524,7 +1524,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "eur", @@ -1534,7 +1534,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "elon", @@ -1544,7 +1544,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dao", @@ -1554,7 +1554,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pla", @@ -1564,7 +1564,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cvc", @@ -1574,7 +1574,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ceek", @@ -1584,7 +1584,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "spell", @@ -1594,7 +1594,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rndr", @@ -1604,7 +1604,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sushi", @@ -1614,7 +1614,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "btcst", @@ -1624,7 +1624,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lsk", @@ -1634,7 +1634,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "eps", @@ -1644,7 +1644,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pundix", @@ -1654,7 +1654,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "celr", @@ -1664,7 +1664,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ren", @@ -1674,7 +1674,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xyo", @@ -1684,7 +1684,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nano", @@ -1694,7 +1694,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "win", @@ -1704,7 +1704,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ong", @@ -1714,7 +1714,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "uos", @@ -1724,7 +1724,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "people", @@ -1734,7 +1734,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cfx", @@ -1744,7 +1744,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "req", @@ -1754,7 +1754,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tribe", @@ -1764,7 +1764,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "dydx", @@ -1774,7 +1774,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ardr", @@ -1784,7 +1784,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rly", @@ -1794,7 +1794,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "coti", @@ -1804,7 +1804,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mx", @@ -1814,7 +1814,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "rlc", @@ -1824,7 +1824,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "powr", @@ -1834,7 +1834,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nmr", @@ -1844,7 +1844,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "snt", @@ -1854,7 +1854,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ocean", @@ -1864,7 +1864,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "chr", @@ -1874,7 +1874,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "api3", @@ -1884,7 +1884,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dent", @@ -1894,7 +1894,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bnt", @@ -1904,7 +1904,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fxs", @@ -1914,7 +1914,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hex", @@ -1924,7 +1924,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "steth", @@ -1934,7 +1934,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "btcb", @@ -1944,7 +1944,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "frax", @@ -1954,7 +1954,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "lunc", @@ -1964,7 +1964,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dfi", @@ -1974,7 +1974,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bnx", @@ -1984,7 +1984,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rpl", @@ -1994,7 +1994,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "luna", @@ -2004,7 +2004,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "husd", @@ -2014,7 +2014,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "babydoge", @@ -2024,7 +2024,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "metis", @@ -2034,7 +2034,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "raca", @@ -2044,7 +2044,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "prom", @@ -2054,7 +2054,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "sys", @@ -2064,7 +2064,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gal", @@ -2074,7 +2074,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bico", @@ -2084,7 +2084,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "steem", @@ -2094,7 +2094,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "c98", @@ -2104,7 +2104,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "susd", @@ -2114,7 +2114,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ctsi", @@ -2124,7 +2124,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hxro", @@ -2134,7 +2134,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rep", @@ -2144,7 +2144,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fun", @@ -2154,7 +2154,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pyr", @@ -2164,7 +2164,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "strax", @@ -2174,7 +2174,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bsw", @@ -2184,7 +2184,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lyxe", @@ -2194,7 +2194,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mtl", @@ -2204,7 +2204,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "stmx", @@ -2214,7 +2214,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "stpt", @@ -2224,7 +2224,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "elf", @@ -2234,7 +2234,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ufo", @@ -2244,7 +2244,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "oxt", @@ -2254,7 +2254,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ach", @@ -2264,7 +2264,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ogn", @@ -2274,7 +2274,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sfund", @@ -2284,7 +2284,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tlm", @@ -2294,7 +2294,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "loom", @@ -2304,7 +2304,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ant", @@ -2314,7 +2314,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "alice", @@ -2324,7 +2324,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fet", @@ -2334,7 +2334,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ygg", @@ -2344,7 +2344,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ark", @@ -2354,7 +2354,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "utk", @@ -2364,7 +2364,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "super", @@ -2374,7 +2374,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dusk", @@ -2384,7 +2384,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ilv", @@ -2394,7 +2394,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mbox", @@ -2404,7 +2404,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sun", @@ -2414,7 +2414,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "vra", @@ -2424,7 +2424,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "aergo", @@ -2434,7 +2434,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bake", @@ -2444,7 +2444,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xvg", @@ -2454,7 +2454,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dpi", @@ -2464,7 +2464,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pols", @@ -2474,7 +2474,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mln", @@ -2484,7 +2484,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xcad", @@ -2494,7 +2494,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "divi", @@ -2504,7 +2504,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "divierc20", @@ -2515,7 +2515,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "sfp", @@ -2525,7 +2525,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tomo", @@ -2535,7 +2535,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "arpa", @@ -2545,7 +2545,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "band", @@ -2555,7 +2555,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bandmainnet", @@ -2566,7 +2566,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sps", @@ -2576,7 +2576,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ava", @@ -2586,7 +2586,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avaerc20", @@ -2596,7 +2596,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avabsc", @@ -2606,7 +2606,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "jasmy", @@ -2616,7 +2616,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cult", @@ -2626,7 +2626,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "starl", @@ -2636,7 +2636,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "aioz", @@ -2646,7 +2646,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "alpaca", @@ -2656,7 +2656,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "blz", @@ -2666,7 +2666,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "alcx", @@ -2676,7 +2676,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "kmd", @@ -2686,7 +2686,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "yfii", @@ -2696,7 +2696,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "unfi", @@ -2706,7 +2706,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "bel", @@ -2716,7 +2716,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mc", @@ -2726,7 +2726,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dia", @@ -2736,7 +2736,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tko", @@ -2746,7 +2746,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bcd", @@ -2756,7 +2756,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "anc", @@ -2766,7 +2766,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "farm", @@ -2776,7 +2776,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bifi", @@ -2786,7 +2786,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "ata", @@ -2796,7 +2796,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fio", @@ -2806,7 +2806,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ubt", @@ -2816,7 +2816,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pit", @@ -2826,7 +2826,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "dnt", @@ -2836,7 +2836,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "burger", @@ -2846,7 +2846,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "om", @@ -2856,7 +2856,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "grs", @@ -2866,7 +2866,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gas", @@ -2876,7 +2876,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hoge", @@ -2886,7 +2886,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "fox", @@ -2896,7 +2896,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "firo", @@ -2906,7 +2906,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "aion", @@ -2916,7 +2916,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "adx", @@ -2926,7 +2926,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nwc", @@ -2937,7 +2937,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "solve", @@ -2947,7 +2947,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cudos", @@ -2957,7 +2957,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rook", @@ -2967,7 +2967,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "klv", @@ -2977,7 +2977,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "front", @@ -2987,7 +2987,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "wtc", @@ -2997,7 +2997,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "beam", @@ -3007,7 +3007,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gto", @@ -3017,7 +3017,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "akro", @@ -3027,7 +3027,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mdt", @@ -3037,7 +3037,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hez", @@ -3047,7 +3047,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pnk", @@ -3057,7 +3057,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ast", @@ -3067,7 +3067,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "snm", @@ -3077,7 +3077,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "qsp", @@ -3087,7 +3087,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "xdb", @@ -3097,7 +3097,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pivx", @@ -3107,7 +3107,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mir", @@ -3117,7 +3117,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "perl", @@ -3127,7 +3127,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "go", @@ -3137,7 +3137,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "urus", @@ -3147,7 +3147,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cell", @@ -3157,7 +3157,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "arv", @@ -3167,7 +3167,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "caps", @@ -3177,7 +3177,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "wabi", @@ -3187,7 +3187,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "swftc", @@ -3197,7 +3197,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "shr", @@ -3207,7 +3207,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "san", @@ -3217,7 +3217,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dobo", @@ -3227,7 +3227,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "hc", @@ -3237,7 +3237,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "fuse", @@ -3247,7 +3247,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dogedash", @@ -3257,7 +3257,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "poolz", @@ -3267,7 +3267,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "vib", @@ -3277,7 +3277,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "now", @@ -3287,7 +3287,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "muse", @@ -3297,7 +3297,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mint", @@ -3307,7 +3307,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xor", @@ -3317,7 +3317,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mtv", @@ -3327,7 +3327,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "spi", @@ -3337,7 +3337,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "belt", @@ -3347,7 +3347,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ppt", @@ -3357,7 +3357,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "awc", @@ -3367,7 +3367,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "defit", @@ -3377,7 +3377,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "srk", @@ -3387,7 +3387,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "swrv", @@ -3397,7 +3397,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "pay", @@ -3407,7 +3407,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "lgcy", @@ -3417,7 +3417,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nftb", @@ -3427,7 +3427,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "open", @@ -3437,7 +3437,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "hotcross", @@ -3447,7 +3447,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bin", @@ -3457,7 +3457,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rcn", @@ -3467,7 +3467,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "srn", @@ -3477,7 +3477,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "tking", @@ -3487,7 +3487,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mph", @@ -3497,7 +3497,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mda", @@ -3507,7 +3507,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "skill", @@ -3517,7 +3517,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xio", @@ -3527,7 +3527,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zoon", @@ -3537,7 +3537,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "naft", @@ -3547,7 +3547,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lxt", @@ -3557,7 +3557,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "rainbow", @@ -3567,7 +3567,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "marsh", @@ -3577,7 +3577,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "spo", @@ -3587,7 +3587,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "brd", @@ -3597,7 +3597,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "eved", @@ -3607,7 +3607,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lead", @@ -3617,7 +3617,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cns", @@ -3627,7 +3627,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sfuel", @@ -3637,7 +3637,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bunny", @@ -3647,7 +3647,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "leash", @@ -3657,7 +3657,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "flokibsc", @@ -3667,7 +3667,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "floki", @@ -3677,7 +3677,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "volt", @@ -3687,7 +3687,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "brise", @@ -3697,7 +3697,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "kishu", @@ -3707,7 +3707,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "shinja", @@ -3717,7 +3717,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ntvrk", @@ -3727,7 +3727,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "akita", @@ -3737,7 +3737,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zinu", @@ -3747,7 +3747,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gafa", @@ -3757,7 +3757,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rbif", @@ -3767,7 +3767,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "trvl", @@ -3777,7 +3777,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "kibabsc", @@ -3787,7 +3787,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "kiba", @@ -3797,7 +3797,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "guard", @@ -3807,7 +3807,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "feg", @@ -3817,7 +3817,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "fegbsc", @@ -3827,7 +3827,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "blocks", @@ -3837,7 +3837,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "copi", @@ -3847,7 +3847,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dogecoin", @@ -3857,7 +3857,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "klee", @@ -3867,7 +3867,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "lblock", @@ -3877,7 +3877,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gspi", @@ -3887,7 +3887,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "asia", @@ -3897,7 +3897,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "wise", @@ -3907,7 +3907,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gmr", @@ -3917,7 +3917,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "knc", @@ -3927,7 +3927,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fjb", @@ -3937,7 +3937,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tenfi", @@ -3947,7 +3947,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "btfa", @@ -3957,7 +3957,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "aquagoat", @@ -3967,7 +3967,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "titano", @@ -3977,7 +3977,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "sanshu", @@ -3987,7 +3987,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "avn", @@ -3997,7 +3997,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "geth", @@ -4007,7 +4007,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tenshi", @@ -4017,7 +4017,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "poodl", @@ -4027,7 +4027,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pika", @@ -4037,7 +4037,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "defc", @@ -4047,7 +4047,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "keanu", @@ -4057,7 +4057,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "rxcg", @@ -4067,7 +4067,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "dgmoon", @@ -4077,7 +4077,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "koromaru", @@ -4087,7 +4087,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "nsh", @@ -4097,7 +4097,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "fluf", @@ -4107,7 +4107,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lof", @@ -4117,7 +4117,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hmc", @@ -4127,7 +4127,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "nyxt", @@ -4137,7 +4137,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usd", @@ -4147,7 +4147,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "gbp", @@ -4157,7 +4157,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "cad", @@ -4167,7 +4167,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "jpy", @@ -4177,7 +4177,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "rub", @@ -4187,7 +4187,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "aud", @@ -4197,7 +4197,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "chf", @@ -4207,7 +4207,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "czk", @@ -4217,7 +4217,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "dkk", @@ -4227,7 +4227,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "nok", @@ -4237,7 +4237,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "nzd", @@ -4247,7 +4247,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "pln", @@ -4257,7 +4257,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "sek", @@ -4267,7 +4267,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "try", @@ -4277,7 +4277,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "zar", @@ -4287,7 +4287,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "huf", @@ -4297,7 +4297,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "ils", @@ -4307,7 +4307,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "brl", @@ -4317,7 +4317,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "fetbsc", @@ -4327,7 +4327,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mononoke", @@ -4338,7 +4338,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "daibsc", @@ -4348,7 +4348,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "miota", @@ -4358,7 +4358,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "luffy", @@ -4368,7 +4368,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "vgx", @@ -4378,7 +4378,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdtsol", @@ -4389,7 +4389,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nearbsc", @@ -4399,7 +4399,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "iotxbsc", @@ -4409,7 +4409,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "metiserc20", @@ -4419,7 +4419,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nowbep2", @@ -4429,7 +4429,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "saitamav2", @@ -4439,7 +4439,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "vlxbsc", @@ -4449,7 +4449,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "dfibsc", @@ -4459,7 +4459,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "usdcsol", @@ -4469,7 +4469,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "clear", @@ -4479,7 +4479,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdcbsc", @@ -4489,7 +4489,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bttcbsc", @@ -4499,7 +4499,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "maticbsc", @@ -4509,7 +4509,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avaxbsc", @@ -4519,7 +4519,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ppm", @@ -4529,7 +4529,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bttc", @@ -4539,7 +4539,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "trxbsc", @@ -4549,7 +4549,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "etcbsc", @@ -4559,7 +4559,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "atombsc", @@ -4569,7 +4569,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bchbsc", @@ -4579,7 +4579,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "vetbsc", @@ -4589,7 +4589,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "filbsc", @@ -4599,7 +4599,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "egldbsc", @@ -4609,7 +4609,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "axsbsc", @@ -4619,7 +4619,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tusdbsc", @@ -4629,7 +4629,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "eosbsc", @@ -4639,7 +4639,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mkrbsc", @@ -4649,7 +4649,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdpbsc", @@ -4659,7 +4659,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "daimatic", @@ -4669,7 +4669,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "zecbsc", @@ -4679,7 +4679,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ftmbsc", @@ -4689,7 +4689,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "manabsc", @@ -4699,7 +4699,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "batbsc", @@ -4709,7 +4709,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sxpmainnet", @@ -4720,7 +4720,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "zilbsc", @@ -4730,7 +4730,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "compbsc", @@ -4740,7 +4740,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "snxbsc", @@ -4750,7 +4750,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "solbsc", @@ -4760,7 +4760,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ceekerc20", @@ -4771,7 +4771,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "yfibsc", @@ -4781,7 +4781,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "kncbsc", @@ -4791,7 +4791,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "chrbsc", @@ -4801,7 +4801,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sushibsc", @@ -4811,7 +4811,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdtmatic", @@ -4821,7 +4821,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "ankrbsc", @@ -4831,7 +4831,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "celrbsc", @@ -4841,7 +4841,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sandmatic", @@ -4852,7 +4852,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "busdbnb", @@ -4862,7 +4862,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "xcnbsc", @@ -4872,7 +4872,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "plamatic", @@ -4882,7 +4882,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fluxerc20", @@ -4892,7 +4892,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "c98erc20", @@ -4902,7 +4902,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "krw", @@ -4912,7 +4912,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "world", @@ -4922,7 +4922,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "all", @@ -4932,7 +4932,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "amd", @@ -4942,7 +4942,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "ang", @@ -4952,7 +4952,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "bam", @@ -4962,7 +4962,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "bbd", @@ -4972,7 +4972,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "bdt", @@ -4982,7 +4982,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "bmd", @@ -4992,7 +4992,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "bnd", @@ -5002,7 +5002,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "bob", @@ -5012,7 +5012,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "bwp", @@ -5022,7 +5022,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "byn", @@ -5032,7 +5032,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "cny", @@ -5042,7 +5042,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "djf", @@ -5052,7 +5052,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "egp", @@ -5062,7 +5062,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "ghs", @@ -5072,7 +5072,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "gtq", @@ -5082,7 +5082,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "hnl", @@ -5092,7 +5092,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "hrk", @@ -5102,7 +5102,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "isk", @@ -5112,7 +5112,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "jmd", @@ -5122,7 +5122,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "kes", @@ -5132,7 +5132,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "kgs", @@ -5142,7 +5142,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "khr", @@ -5152,7 +5152,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "kyd", @@ -5162,7 +5162,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "lbp", @@ -5172,7 +5172,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "lkr", @@ -5182,7 +5182,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "mkd", @@ -5192,7 +5192,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "mnt", @@ -5202,7 +5202,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "mop", @@ -5212,7 +5212,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "mur", @@ -5222,7 +5222,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "mzn", @@ -5232,7 +5232,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "pab", @@ -5242,7 +5242,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "pgk", @@ -5252,7 +5252,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "pkr", @@ -5262,7 +5262,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "pyg", @@ -5272,7 +5272,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "rsd", @@ -5282,7 +5282,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "sos", @@ -5292,7 +5292,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "thb", @@ -5302,7 +5302,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "ttd", @@ -5312,7 +5312,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "tzs", @@ -5322,7 +5322,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "ugx", @@ -5332,7 +5332,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "xaf", @@ -5342,7 +5342,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "xof", @@ -5352,7 +5352,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "zmw", @@ -5362,7 +5362,7 @@ const List> availableCurrenciesJSON = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "momento", @@ -5372,7 +5372,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fire", @@ -5382,7 +5382,7 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ghc", @@ -5392,8 +5392,8 @@ const List> availableCurrenciesJSON = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true - } + "supportsFixedRate": true, + }, ]; const List> availableCurrenciesJSONActive = [ @@ -5405,7 +5405,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "eth", @@ -5415,7 +5415,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ethbsc", @@ -5425,7 +5425,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdt", @@ -5435,7 +5435,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdterc20", @@ -5446,7 +5446,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": true, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdttrc20", @@ -5457,7 +5457,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdtbsc", @@ -5467,7 +5467,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdc", @@ -5477,7 +5477,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": true, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdcmatic", @@ -5488,7 +5488,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bnbmainnet", @@ -5499,7 +5499,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bnbbsc", @@ -5509,7 +5509,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "busd", @@ -5519,7 +5519,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "busdbsc", @@ -5529,7 +5529,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xrp", @@ -5539,7 +5539,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xrpbsc", @@ -5549,7 +5549,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ada", @@ -5559,7 +5559,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "adabsc", @@ -5569,7 +5569,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sol", @@ -5579,7 +5579,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "doge", @@ -5589,7 +5589,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dot", @@ -5599,7 +5599,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dotbsc", @@ -5609,7 +5609,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dai", @@ -5619,7 +5619,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "matic", @@ -5629,7 +5629,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "maticmainnet", @@ -5640,7 +5640,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "shib", @@ -5650,7 +5650,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "shibbsc", @@ -5660,7 +5660,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "trx", @@ -5670,7 +5670,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avax", @@ -5680,7 +5680,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avaxc", @@ -5690,7 +5690,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "wbtc", @@ -5700,7 +5700,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "leo", @@ -5710,7 +5710,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "uni", @@ -5720,7 +5720,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "etc", @@ -5730,7 +5730,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ltc", @@ -5740,7 +5740,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ltcbsc", @@ -5750,7 +5750,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ftt", @@ -5760,7 +5760,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "link", @@ -5770,7 +5770,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "atom", @@ -5780,7 +5780,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cro", @@ -5790,7 +5790,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "near", @@ -5800,7 +5800,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xmr", @@ -5810,7 +5810,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xlm", @@ -5820,7 +5820,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bch", @@ -5830,7 +5830,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "algo", @@ -5840,7 +5840,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "flow", @@ -5850,7 +5850,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "vet", @@ -5860,7 +5860,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "icp", @@ -5870,7 +5870,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fil", @@ -5880,7 +5880,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ape", @@ -5890,7 +5890,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "eos", @@ -5900,7 +5900,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mana", @@ -5910,7 +5910,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sand", @@ -5920,7 +5920,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hbar", @@ -5930,7 +5930,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xtz", @@ -5940,7 +5940,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xtzbsc", @@ -5950,7 +5950,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "chz", @@ -5960,7 +5960,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "qnt", @@ -5970,7 +5970,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "egld", @@ -5980,7 +5980,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "aave", @@ -5990,7 +5990,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "theta", @@ -6000,7 +6000,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "axs", @@ -6010,7 +6010,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tusd", @@ -6020,7 +6020,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bsv", @@ -6030,7 +6030,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "okb", @@ -6040,7 +6040,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "galabsc", @@ -6050,7 +6050,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zec", @@ -6060,7 +6060,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdp", @@ -6070,7 +6070,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bttbsc", @@ -6080,7 +6080,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "iota", @@ -6090,7 +6090,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mkr", @@ -6100,7 +6100,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hnt", @@ -6110,7 +6110,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ht", @@ -6120,7 +6120,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "snx", @@ -6130,7 +6130,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "grt", @@ -6140,7 +6140,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "klay", @@ -6150,7 +6150,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ftm", @@ -6160,7 +6160,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ftmmainnet", @@ -6170,7 +6170,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "neo", @@ -6180,7 +6180,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rune", @@ -6190,7 +6190,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "paxg", @@ -6200,7 +6200,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ldo", @@ -6210,7 +6210,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cake", @@ -6220,7 +6220,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "crv", @@ -6230,7 +6230,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nexo", @@ -6240,7 +6240,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bat", @@ -6250,7 +6250,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dash", @@ -6260,7 +6260,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "waves", @@ -6270,7 +6270,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zil", @@ -6280,7 +6280,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lrc", @@ -6290,7 +6290,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "enj", @@ -6300,7 +6300,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ksm", @@ -6310,7 +6310,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dcr", @@ -6320,7 +6320,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "btg", @@ -6330,7 +6330,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gmt", @@ -6340,7 +6340,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gno", @@ -6350,7 +6350,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "twt", @@ -6360,7 +6360,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xem", @@ -6370,7 +6370,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "1inch", @@ -6380,7 +6380,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "1inchbsc", @@ -6390,7 +6390,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "celo", @@ -6400,7 +6400,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hot", @@ -6410,7 +6410,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "galaerc20", @@ -6421,7 +6421,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ankr", @@ -6431,7 +6431,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "comp", @@ -6441,7 +6441,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gt", @@ -6451,7 +6451,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "cvx", @@ -6461,7 +6461,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "qtum", @@ -6471,7 +6471,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "yfi", @@ -6481,7 +6481,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xdc", @@ -6491,7 +6491,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "iotx", @@ -6501,7 +6501,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cel", @@ -6511,7 +6511,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gusd", @@ -6521,7 +6521,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tfuel", @@ -6531,7 +6531,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rvn", @@ -6541,7 +6541,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "flux", @@ -6551,7 +6551,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bal", @@ -6561,7 +6561,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "amp", @@ -6571,7 +6571,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "omg", @@ -6581,7 +6581,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zrx", @@ -6591,7 +6591,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rsr", @@ -6601,7 +6601,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "one", @@ -6611,7 +6611,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "jst", @@ -6621,7 +6621,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "icx", @@ -6631,7 +6631,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xym", @@ -6641,7 +6641,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "iost", @@ -6651,7 +6651,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ens", @@ -6661,7 +6661,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lpt", @@ -6671,7 +6671,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "glm", @@ -6681,7 +6681,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "audio", @@ -6691,7 +6691,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "storj", @@ -6701,7 +6701,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ont", @@ -6711,7 +6711,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ontbsc", @@ -6721,7 +6721,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "waxp", @@ -6731,7 +6731,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "srm", @@ -6741,7 +6741,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sc", @@ -6751,7 +6751,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "imx", @@ -6761,7 +6761,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zen", @@ -6771,7 +6771,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "uma", @@ -6781,7 +6781,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "scrt", @@ -6791,7 +6791,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mxc", @@ -6801,7 +6801,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "btrst", @@ -6811,7 +6811,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "skl", @@ -6821,7 +6821,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "poly", @@ -6831,7 +6831,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "slp", @@ -6841,7 +6841,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "woobsc", @@ -6851,7 +6851,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "woo", @@ -6861,7 +6861,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "chsb", @@ -6871,7 +6871,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cspr", @@ -6881,7 +6881,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "dgb", @@ -6891,7 +6891,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "eur", @@ -6901,7 +6901,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "elon", @@ -6911,7 +6911,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dao", @@ -6921,7 +6921,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pla", @@ -6931,7 +6931,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cvc", @@ -6941,7 +6941,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ceek", @@ -6951,7 +6951,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "spell", @@ -6961,7 +6961,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rndr", @@ -6971,7 +6971,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sushi", @@ -6981,7 +6981,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "btcst", @@ -6991,7 +6991,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lsk", @@ -7001,7 +7001,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "eps", @@ -7011,7 +7011,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pundix", @@ -7021,7 +7021,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "celr", @@ -7031,7 +7031,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ren", @@ -7041,7 +7041,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nano", @@ -7051,7 +7051,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xyo", @@ -7061,7 +7061,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "win", @@ -7071,7 +7071,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ong", @@ -7081,7 +7081,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "people", @@ -7091,7 +7091,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "uos", @@ -7101,7 +7101,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cfx", @@ -7111,7 +7111,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "req", @@ -7121,7 +7121,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tribe", @@ -7131,7 +7131,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "dydx", @@ -7141,7 +7141,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ardr", @@ -7151,7 +7151,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rly", @@ -7161,7 +7161,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "coti", @@ -7171,7 +7171,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mx", @@ -7181,7 +7181,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "rlc", @@ -7191,7 +7191,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "powr", @@ -7201,7 +7201,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nmr", @@ -7211,7 +7211,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "snt", @@ -7221,7 +7221,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ocean", @@ -7231,7 +7231,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "chr", @@ -7241,7 +7241,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "api3", @@ -7251,7 +7251,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dent", @@ -7261,7 +7261,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bnt", @@ -7271,7 +7271,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fxs", @@ -7281,7 +7281,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hex", @@ -7291,7 +7291,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "steth", @@ -7301,7 +7301,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "btcb", @@ -7311,7 +7311,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lunc", @@ -7321,7 +7321,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dfi", @@ -7331,7 +7331,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bnx", @@ -7341,7 +7341,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rpl", @@ -7351,7 +7351,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "luna", @@ -7361,7 +7361,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "husd", @@ -7371,7 +7371,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "babydoge", @@ -7381,7 +7381,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "metis", @@ -7391,7 +7391,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "raca", @@ -7401,7 +7401,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "prom", @@ -7411,7 +7411,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "sys", @@ -7421,7 +7421,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gal", @@ -7431,7 +7431,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bico", @@ -7441,7 +7441,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "steem", @@ -7451,7 +7451,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "c98", @@ -7461,7 +7461,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "susd", @@ -7471,7 +7471,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ctsi", @@ -7481,7 +7481,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hxro", @@ -7491,7 +7491,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rep", @@ -7501,7 +7501,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fun", @@ -7511,7 +7511,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pyr", @@ -7521,7 +7521,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "strax", @@ -7531,7 +7531,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bsw", @@ -7541,7 +7541,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lyxe", @@ -7551,7 +7551,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mtl", @@ -7561,7 +7561,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "stmx", @@ -7571,7 +7571,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "stpt", @@ -7581,7 +7581,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "elf", @@ -7591,7 +7591,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "oxt", @@ -7601,7 +7601,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ufo", @@ -7611,7 +7611,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ach", @@ -7621,7 +7621,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ogn", @@ -7631,7 +7631,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sfund", @@ -7641,7 +7641,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tlm", @@ -7651,7 +7651,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "loom", @@ -7661,7 +7661,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ant", @@ -7671,7 +7671,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "alice", @@ -7681,7 +7681,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fet", @@ -7691,7 +7691,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ygg", @@ -7701,7 +7701,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ark", @@ -7711,7 +7711,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "utk", @@ -7721,7 +7721,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "super", @@ -7731,7 +7731,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dusk", @@ -7741,7 +7741,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ilv", @@ -7751,7 +7751,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mbox", @@ -7761,7 +7761,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sun", @@ -7771,7 +7771,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "aergo", @@ -7781,7 +7781,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "vra", @@ -7791,7 +7791,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bake", @@ -7801,7 +7801,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xvg", @@ -7811,7 +7811,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dpi", @@ -7821,7 +7821,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pols", @@ -7831,7 +7831,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mln", @@ -7841,7 +7841,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xcad", @@ -7851,7 +7851,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "divi", @@ -7861,7 +7861,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "divierc20", @@ -7872,7 +7872,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "sfp", @@ -7882,7 +7882,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tomo", @@ -7892,7 +7892,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "arpa", @@ -7902,7 +7902,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "band", @@ -7912,7 +7912,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bandmainnet", @@ -7923,7 +7923,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sps", @@ -7933,7 +7933,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ava", @@ -7943,7 +7943,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avaerc20", @@ -7953,7 +7953,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avabsc", @@ -7963,7 +7963,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "jasmy", @@ -7973,7 +7973,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cult", @@ -7983,7 +7983,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "starl", @@ -7993,7 +7993,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "aioz", @@ -8003,7 +8003,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "alpaca", @@ -8013,7 +8013,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "blz", @@ -8023,7 +8023,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "kmd", @@ -8033,7 +8033,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "alcx", @@ -8043,7 +8043,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "yfii", @@ -8053,7 +8053,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "unfi", @@ -8063,7 +8063,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "bel", @@ -8073,7 +8073,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mc", @@ -8083,7 +8083,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dia", @@ -8093,7 +8093,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tko", @@ -8103,7 +8103,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bcd", @@ -8113,7 +8113,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "farm", @@ -8123,7 +8123,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bifi", @@ -8133,7 +8133,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "ata", @@ -8143,7 +8143,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fio", @@ -8153,7 +8153,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ubt", @@ -8163,7 +8163,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pit", @@ -8173,7 +8173,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "dnt", @@ -8183,7 +8183,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "burger", @@ -8193,7 +8193,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "grs", @@ -8203,7 +8203,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "om", @@ -8213,7 +8213,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gas", @@ -8223,7 +8223,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hoge", @@ -8233,7 +8233,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "fox", @@ -8243,7 +8243,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "firo", @@ -8253,7 +8253,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "aion", @@ -8263,7 +8263,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "adx", @@ -8273,7 +8273,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nwc", @@ -8284,7 +8284,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cudos", @@ -8294,7 +8294,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "solve", @@ -8304,7 +8304,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "klv", @@ -8314,7 +8314,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rook", @@ -8324,7 +8324,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "front", @@ -8334,7 +8334,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "wtc", @@ -8344,7 +8344,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "beam", @@ -8354,7 +8354,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gto", @@ -8364,7 +8364,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "akro", @@ -8374,7 +8374,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mdt", @@ -8384,7 +8384,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hez", @@ -8394,7 +8394,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pnk", @@ -8404,7 +8404,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ast", @@ -8414,7 +8414,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "snm", @@ -8424,7 +8424,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "qsp", @@ -8434,7 +8434,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "pivx", @@ -8444,7 +8444,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xdb", @@ -8454,7 +8454,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mir", @@ -8464,7 +8464,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "perl", @@ -8474,7 +8474,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "go", @@ -8484,7 +8484,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "urus", @@ -8494,7 +8494,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "arv", @@ -8504,7 +8504,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cell", @@ -8514,7 +8514,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "caps", @@ -8524,7 +8524,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "wabi", @@ -8534,7 +8534,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "swftc", @@ -8544,7 +8544,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "shr", @@ -8554,7 +8554,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "san", @@ -8564,7 +8564,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dobo", @@ -8574,7 +8574,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "hc", @@ -8584,7 +8584,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "fuse", @@ -8594,7 +8594,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dogedash", @@ -8604,7 +8604,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "poolz", @@ -8614,7 +8614,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "vib", @@ -8624,7 +8624,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "now", @@ -8634,7 +8634,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "muse", @@ -8644,7 +8644,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mint", @@ -8654,7 +8654,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xor", @@ -8664,7 +8664,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mtv", @@ -8674,7 +8674,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "spi", @@ -8684,7 +8684,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "belt", @@ -8694,7 +8694,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ppt", @@ -8704,7 +8704,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "awc", @@ -8714,7 +8714,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "defit", @@ -8724,7 +8724,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "srk", @@ -8734,7 +8734,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "swrv", @@ -8744,7 +8744,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "pay", @@ -8754,7 +8754,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "lgcy", @@ -8764,7 +8764,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nftb", @@ -8774,7 +8774,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "open", @@ -8784,7 +8784,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "hotcross", @@ -8794,7 +8794,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bin", @@ -8804,7 +8804,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rcn", @@ -8814,7 +8814,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "srn", @@ -8824,7 +8824,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "tking", @@ -8834,7 +8834,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mph", @@ -8844,7 +8844,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mda", @@ -8854,7 +8854,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "skill", @@ -8864,7 +8864,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xio", @@ -8874,7 +8874,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zoon", @@ -8884,7 +8884,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lxt", @@ -8894,7 +8894,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "naft", @@ -8904,7 +8904,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rainbow", @@ -8914,7 +8914,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "marsh", @@ -8924,7 +8924,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "spo", @@ -8934,7 +8934,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "brd", @@ -8944,7 +8944,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "eved", @@ -8954,7 +8954,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lead", @@ -8964,7 +8964,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cns", @@ -8974,7 +8974,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sfuel", @@ -8984,7 +8984,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bunny", @@ -8994,7 +8994,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "leash", @@ -9004,7 +9004,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "flokibsc", @@ -9014,7 +9014,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "floki", @@ -9024,7 +9024,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "volt", @@ -9034,7 +9034,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "brise", @@ -9044,7 +9044,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "kishu", @@ -9054,7 +9054,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "shinja", @@ -9064,7 +9064,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ntvrk", @@ -9074,7 +9074,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "akita", @@ -9084,7 +9084,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zinu", @@ -9094,7 +9094,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gafa", @@ -9104,7 +9104,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rbif", @@ -9114,7 +9114,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "trvl", @@ -9124,7 +9124,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "kibabsc", @@ -9134,7 +9134,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "kiba", @@ -9144,7 +9144,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "guard", @@ -9154,7 +9154,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "feg", @@ -9164,7 +9164,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "fegbsc", @@ -9174,7 +9174,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "blocks", @@ -9184,7 +9184,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "copi", @@ -9194,7 +9194,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dogecoin", @@ -9204,7 +9204,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "klee", @@ -9214,7 +9214,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "lblock", @@ -9224,7 +9224,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gspi", @@ -9234,7 +9234,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "asia", @@ -9244,7 +9244,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "wise", @@ -9254,7 +9254,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gmr", @@ -9264,7 +9264,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "knc", @@ -9274,7 +9274,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fjb", @@ -9284,7 +9284,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tenfi", @@ -9294,7 +9294,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "btfa", @@ -9304,7 +9304,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "aquagoat", @@ -9314,7 +9314,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "titano", @@ -9324,7 +9324,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "sanshu", @@ -9334,7 +9334,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "avn", @@ -9344,7 +9344,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tenshi", @@ -9354,7 +9354,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "poodl", @@ -9364,7 +9364,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pika", @@ -9374,7 +9374,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "geth", @@ -9384,7 +9384,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "defc", @@ -9394,7 +9394,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "keanu", @@ -9404,7 +9404,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "dgmoon", @@ -9414,7 +9414,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "koromaru", @@ -9424,7 +9424,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "nsh", @@ -9434,7 +9434,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "fluf", @@ -9444,7 +9444,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lof", @@ -9454,7 +9454,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hmc", @@ -9464,7 +9464,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "nyxt", @@ -9474,7 +9474,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usd", @@ -9484,7 +9484,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "gbp", @@ -9494,7 +9494,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "cad", @@ -9504,7 +9504,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "jpy", @@ -9514,7 +9514,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "rub", @@ -9524,7 +9524,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "aud", @@ -9534,7 +9534,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "chf", @@ -9544,7 +9544,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "czk", @@ -9554,7 +9554,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "dkk", @@ -9564,7 +9564,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "nok", @@ -9574,7 +9574,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "nzd", @@ -9584,7 +9584,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "pln", @@ -9594,7 +9594,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "sek", @@ -9604,7 +9604,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "try", @@ -9614,7 +9614,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "zar", @@ -9624,7 +9624,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "huf", @@ -9634,7 +9634,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "ils", @@ -9644,7 +9644,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "brl", @@ -9654,7 +9654,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "fetbsc", @@ -9664,7 +9664,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mononoke", @@ -9675,7 +9675,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "daibsc", @@ -9685,7 +9685,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "miota", @@ -9695,7 +9695,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "luffy", @@ -9705,7 +9705,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "vgx", @@ -9715,7 +9715,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdtsol", @@ -9726,7 +9726,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nearbsc", @@ -9736,7 +9736,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "iotxbsc", @@ -9746,7 +9746,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "metiserc20", @@ -9756,7 +9756,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nowbep2", @@ -9766,7 +9766,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "saitamav2", @@ -9776,7 +9776,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "vlxbsc", @@ -9786,7 +9786,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "dfibsc", @@ -9796,7 +9796,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "usdcsol", @@ -9806,7 +9806,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "clear", @@ -9816,7 +9816,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdcbsc", @@ -9826,7 +9826,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bttcbsc", @@ -9836,7 +9836,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "maticbsc", @@ -9846,7 +9846,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avaxbsc", @@ -9856,7 +9856,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ppm", @@ -9866,7 +9866,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bttc", @@ -9876,7 +9876,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "trxbsc", @@ -9886,7 +9886,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "etcbsc", @@ -9896,7 +9896,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "atombsc", @@ -9906,7 +9906,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bchbsc", @@ -9916,7 +9916,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "vetbsc", @@ -9926,7 +9926,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "filbsc", @@ -9936,7 +9936,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "egldbsc", @@ -9946,7 +9946,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "axsbsc", @@ -9956,7 +9956,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tusdbsc", @@ -9966,7 +9966,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "eosbsc", @@ -9976,7 +9976,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mkrbsc", @@ -9986,7 +9986,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdpbsc", @@ -9996,7 +9996,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "daimatic", @@ -10006,7 +10006,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "zecbsc", @@ -10016,7 +10016,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ftmbsc", @@ -10026,7 +10026,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "manabsc", @@ -10036,7 +10036,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "batbsc", @@ -10046,7 +10046,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sxpmainnet", @@ -10057,7 +10057,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "zilbsc", @@ -10067,7 +10067,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "compbsc", @@ -10077,7 +10077,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "snxbsc", @@ -10087,7 +10087,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "solbsc", @@ -10097,7 +10097,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ceekerc20", @@ -10108,7 +10108,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "yfibsc", @@ -10118,7 +10118,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "kncbsc", @@ -10128,7 +10128,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "chrbsc", @@ -10138,7 +10138,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sushibsc", @@ -10148,7 +10148,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdtmatic", @@ -10158,7 +10158,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "ankrbsc", @@ -10168,7 +10168,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "celrbsc", @@ -10178,7 +10178,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sandmatic", @@ -10189,7 +10189,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xcnbsc", @@ -10199,7 +10199,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "plamatic", @@ -10209,7 +10209,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fluxerc20", @@ -10219,7 +10219,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "c98erc20", @@ -10229,7 +10229,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "krw", @@ -10239,7 +10239,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "world", @@ -10249,7 +10249,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "all", @@ -10259,7 +10259,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "amd", @@ -10269,7 +10269,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "ang", @@ -10279,7 +10279,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "bam", @@ -10289,7 +10289,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "bbd", @@ -10299,7 +10299,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "bdt", @@ -10309,7 +10309,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "bmd", @@ -10319,7 +10319,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "bnd", @@ -10329,7 +10329,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "bob", @@ -10339,7 +10339,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "bwp", @@ -10349,7 +10349,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "byn", @@ -10359,7 +10359,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "cny", @@ -10369,7 +10369,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "djf", @@ -10379,7 +10379,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "egp", @@ -10389,7 +10389,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "ghs", @@ -10399,7 +10399,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "gtq", @@ -10409,7 +10409,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "hnl", @@ -10419,7 +10419,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "hrk", @@ -10429,7 +10429,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "isk", @@ -10439,7 +10439,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "jmd", @@ -10449,7 +10449,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "kes", @@ -10459,7 +10459,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "kgs", @@ -10469,7 +10469,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "khr", @@ -10479,7 +10479,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "kyd", @@ -10489,7 +10489,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "lbp", @@ -10499,7 +10499,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "lkr", @@ -10509,7 +10509,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "mkd", @@ -10519,7 +10519,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "mnt", @@ -10529,7 +10529,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "mop", @@ -10539,7 +10539,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "mur", @@ -10549,7 +10549,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "mzn", @@ -10559,7 +10559,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "pab", @@ -10569,7 +10569,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "pgk", @@ -10579,7 +10579,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "pkr", @@ -10589,7 +10589,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "pyg", @@ -10599,7 +10599,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "rsd", @@ -10609,7 +10609,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "sos", @@ -10619,7 +10619,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "thb", @@ -10629,7 +10629,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "ttd", @@ -10639,7 +10639,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "tzs", @@ -10649,7 +10649,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "ugx", @@ -10659,7 +10659,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "xaf", @@ -10669,7 +10669,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "xof", @@ -10679,7 +10679,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "zmw", @@ -10689,7 +10689,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": true, "featured": false, "isStable": false, - "supportsFixedRate": false + "supportsFixedRate": false, }, { "ticker": "momento", @@ -10699,7 +10699,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fire", @@ -10709,7 +10709,7 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ghc", @@ -10719,8 +10719,8 @@ const List> availableCurrenciesJSONActive = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true - } + "supportsFixedRate": true, + }, ]; const List> availableCurrenciesJSONFixedRate = [ @@ -10732,7 +10732,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "eth", @@ -10742,7 +10742,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ethbsc", @@ -10752,7 +10752,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdt", @@ -10762,7 +10762,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdterc20", @@ -10773,7 +10773,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": true, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdttrc20", @@ -10784,7 +10784,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdtbsc", @@ -10794,7 +10794,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdc", @@ -10804,7 +10804,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": true, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdcmatic", @@ -10815,7 +10815,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bnbmainnet", @@ -10826,7 +10826,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bnbbsc", @@ -10836,7 +10836,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "busd", @@ -10846,7 +10846,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "busdbsc", @@ -10856,7 +10856,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xrp", @@ -10866,7 +10866,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xrpbsc", @@ -10876,7 +10876,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ada", @@ -10886,7 +10886,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "adabsc", @@ -10896,7 +10896,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sol", @@ -10906,7 +10906,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "doge", @@ -10916,7 +10916,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dot", @@ -10926,7 +10926,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dotbsc", @@ -10936,7 +10936,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dai", @@ -10946,7 +10946,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "matic", @@ -10956,7 +10956,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "maticmainnet", @@ -10967,7 +10967,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "shib", @@ -10977,7 +10977,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "shibbsc", @@ -10987,7 +10987,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "trx", @@ -10997,7 +10997,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avax", @@ -11007,7 +11007,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avaxc", @@ -11017,7 +11017,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "wbtc", @@ -11027,7 +11027,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "leo", @@ -11037,7 +11037,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "uni", @@ -11047,7 +11047,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "etc", @@ -11057,7 +11057,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ltc", @@ -11067,7 +11067,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ltcbsc", @@ -11077,7 +11077,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ftt", @@ -11087,7 +11087,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "link", @@ -11097,7 +11097,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "atom", @@ -11107,7 +11107,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cro", @@ -11117,7 +11117,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "near", @@ -11127,7 +11127,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xmr", @@ -11137,7 +11137,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xlm", @@ -11147,7 +11147,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bch", @@ -11157,7 +11157,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "algo", @@ -11167,7 +11167,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "flow", @@ -11177,7 +11177,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "vet", @@ -11187,7 +11187,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "icp", @@ -11197,7 +11197,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fil", @@ -11207,7 +11207,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ape", @@ -11217,7 +11217,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "eos", @@ -11227,7 +11227,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mana", @@ -11237,7 +11237,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sand", @@ -11247,7 +11247,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hbar", @@ -11257,7 +11257,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xtz", @@ -11267,7 +11267,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xtzbsc", @@ -11277,7 +11277,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "chz", @@ -11287,7 +11287,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "qnt", @@ -11297,7 +11297,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "egld", @@ -11307,7 +11307,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "aave", @@ -11317,7 +11317,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "theta", @@ -11327,7 +11327,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "axs", @@ -11337,7 +11337,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tusd", @@ -11347,7 +11347,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bsv", @@ -11357,7 +11357,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "okb", @@ -11367,7 +11367,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "galabsc", @@ -11377,7 +11377,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zec", @@ -11387,7 +11387,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdp", @@ -11397,7 +11397,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bttbsc", @@ -11407,7 +11407,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "iota", @@ -11417,7 +11417,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mkr", @@ -11427,7 +11427,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hnt", @@ -11437,7 +11437,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ht", @@ -11447,7 +11447,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "snx", @@ -11457,7 +11457,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "grt", @@ -11467,7 +11467,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "klay", @@ -11477,7 +11477,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ftm", @@ -11487,7 +11487,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ftmmainnet", @@ -11497,7 +11497,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "neo", @@ -11507,7 +11507,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "paxg", @@ -11517,7 +11517,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ldo", @@ -11527,7 +11527,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cake", @@ -11537,7 +11537,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "crv", @@ -11547,7 +11547,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nexo", @@ -11557,7 +11557,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bat", @@ -11567,7 +11567,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dash", @@ -11577,7 +11577,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "waves", @@ -11587,7 +11587,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zil", @@ -11597,7 +11597,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lrc", @@ -11607,7 +11607,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "enj", @@ -11617,7 +11617,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ksm", @@ -11627,7 +11627,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dcr", @@ -11637,7 +11637,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "btg", @@ -11647,7 +11647,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gmt", @@ -11657,7 +11657,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gno", @@ -11667,7 +11667,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "twt", @@ -11677,7 +11677,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xem", @@ -11687,7 +11687,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "1inch", @@ -11697,7 +11697,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "1inchbsc", @@ -11707,7 +11707,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "celo", @@ -11717,7 +11717,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hot", @@ -11727,7 +11727,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "galaerc20", @@ -11738,7 +11738,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ankr", @@ -11748,7 +11748,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "comp", @@ -11758,7 +11758,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cvx", @@ -11768,7 +11768,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "qtum", @@ -11778,7 +11778,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "yfi", @@ -11788,7 +11788,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xdc", @@ -11798,7 +11798,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "iotx", @@ -11808,7 +11808,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cel", @@ -11818,7 +11818,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gusd", @@ -11828,7 +11828,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tfuel", @@ -11838,7 +11838,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rvn", @@ -11848,7 +11848,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "flux", @@ -11858,7 +11858,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bal", @@ -11868,7 +11868,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "amp", @@ -11878,7 +11878,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "omg", @@ -11888,7 +11888,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zrx", @@ -11898,7 +11898,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rsr", @@ -11908,7 +11908,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "one", @@ -11918,7 +11918,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "jst", @@ -11928,7 +11928,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "icx", @@ -11938,7 +11938,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xym", @@ -11948,7 +11948,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "iost", @@ -11958,7 +11958,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ens", @@ -11968,7 +11968,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lpt", @@ -11978,7 +11978,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "glm", @@ -11988,7 +11988,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "audio", @@ -11998,7 +11998,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "storj", @@ -12008,7 +12008,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ont", @@ -12018,7 +12018,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ontbsc", @@ -12028,7 +12028,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "waxp", @@ -12038,7 +12038,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "srm", @@ -12048,7 +12048,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sc", @@ -12058,7 +12058,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "imx", @@ -12068,7 +12068,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zen", @@ -12078,7 +12078,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "uma", @@ -12088,7 +12088,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "scrt", @@ -12098,7 +12098,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "skl", @@ -12108,7 +12108,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "poly", @@ -12118,7 +12118,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "slp", @@ -12128,7 +12128,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "woobsc", @@ -12138,7 +12138,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "woo", @@ -12148,7 +12148,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "chsb", @@ -12158,7 +12158,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dgb", @@ -12168,7 +12168,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "elon", @@ -12178,7 +12178,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dao", @@ -12188,7 +12188,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pla", @@ -12198,7 +12198,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cvc", @@ -12208,7 +12208,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "spell", @@ -12218,7 +12218,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rndr", @@ -12228,7 +12228,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sushi", @@ -12238,7 +12238,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "btcst", @@ -12248,7 +12248,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lsk", @@ -12258,7 +12258,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "eps", @@ -12268,7 +12268,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pundix", @@ -12278,7 +12278,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "celr", @@ -12288,7 +12288,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ren", @@ -12298,7 +12298,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nano", @@ -12308,7 +12308,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xyo", @@ -12318,7 +12318,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "win", @@ -12328,7 +12328,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ong", @@ -12338,7 +12338,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "people", @@ -12348,7 +12348,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "uos", @@ -12358,7 +12358,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cfx", @@ -12368,7 +12368,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "req", @@ -12378,7 +12378,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dydx", @@ -12388,7 +12388,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ardr", @@ -12398,7 +12398,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rly", @@ -12408,7 +12408,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "coti", @@ -12418,7 +12418,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rlc", @@ -12428,7 +12428,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "powr", @@ -12438,7 +12438,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nmr", @@ -12448,7 +12448,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "snt", @@ -12458,7 +12458,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ocean", @@ -12468,7 +12468,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "chr", @@ -12478,7 +12478,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "api3", @@ -12488,7 +12488,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dent", @@ -12498,7 +12498,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bnt", @@ -12508,7 +12508,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fxs", @@ -12518,7 +12518,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hex", @@ -12528,7 +12528,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "steth", @@ -12538,7 +12538,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "btcb", @@ -12548,7 +12548,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lunc", @@ -12558,7 +12558,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dfi", @@ -12568,7 +12568,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bnx", @@ -12578,7 +12578,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rpl", @@ -12588,7 +12588,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "luna", @@ -12598,7 +12598,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "babydoge", @@ -12608,7 +12608,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "raca", @@ -12618,7 +12618,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sys", @@ -12628,7 +12628,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gal", @@ -12638,7 +12638,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bico", @@ -12648,7 +12648,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "steem", @@ -12658,7 +12658,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "c98", @@ -12668,7 +12668,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "susd", @@ -12678,7 +12678,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ctsi", @@ -12688,7 +12688,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hxro", @@ -12698,7 +12698,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rep", @@ -12708,7 +12708,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fun", @@ -12718,7 +12718,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pyr", @@ -12728,7 +12728,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "strax", @@ -12738,7 +12738,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bsw", @@ -12748,7 +12748,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lyxe", @@ -12758,7 +12758,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mtl", @@ -12768,7 +12768,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "stmx", @@ -12778,7 +12778,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "stpt", @@ -12788,7 +12788,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "elf", @@ -12798,7 +12798,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "oxt", @@ -12808,7 +12808,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ufo", @@ -12818,7 +12818,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ach", @@ -12828,7 +12828,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ogn", @@ -12838,7 +12838,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sfund", @@ -12848,7 +12848,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tlm", @@ -12858,7 +12858,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "loom", @@ -12868,7 +12868,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ant", @@ -12878,7 +12878,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "alice", @@ -12888,7 +12888,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fet", @@ -12898,7 +12898,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ygg", @@ -12908,7 +12908,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ark", @@ -12918,7 +12918,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "utk", @@ -12928,7 +12928,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "super", @@ -12938,7 +12938,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dusk", @@ -12948,7 +12948,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ilv", @@ -12958,7 +12958,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mbox", @@ -12968,7 +12968,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sun", @@ -12978,7 +12978,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "aergo", @@ -12988,7 +12988,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "vra", @@ -12998,7 +12998,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bake", @@ -13008,7 +13008,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xvg", @@ -13018,7 +13018,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dpi", @@ -13028,7 +13028,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pols", @@ -13038,7 +13038,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mln", @@ -13048,7 +13048,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xcad", @@ -13058,7 +13058,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "divi", @@ -13068,7 +13068,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sfp", @@ -13078,7 +13078,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tomo", @@ -13088,7 +13088,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "arpa", @@ -13098,7 +13098,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "band", @@ -13108,7 +13108,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bandmainnet", @@ -13119,7 +13119,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sps", @@ -13129,7 +13129,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ava", @@ -13139,7 +13139,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avaerc20", @@ -13149,7 +13149,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avabsc", @@ -13159,7 +13159,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "jasmy", @@ -13169,7 +13169,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cult", @@ -13179,7 +13179,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "starl", @@ -13189,7 +13189,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "alpaca", @@ -13199,7 +13199,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "blz", @@ -13209,7 +13209,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "kmd", @@ -13219,7 +13219,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "alcx", @@ -13229,7 +13229,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "yfii", @@ -13239,7 +13239,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bel", @@ -13249,7 +13249,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mc", @@ -13259,7 +13259,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dia", @@ -13269,7 +13269,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tko", @@ -13279,7 +13279,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bcd", @@ -13289,7 +13289,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "farm", @@ -13299,7 +13299,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ata", @@ -13309,7 +13309,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fio", @@ -13319,7 +13319,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ubt", @@ -13329,7 +13329,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dnt", @@ -13339,7 +13339,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "grs", @@ -13349,7 +13349,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "om", @@ -13359,7 +13359,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gas", @@ -13369,7 +13369,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fox", @@ -13379,7 +13379,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "firo", @@ -13389,7 +13389,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "aion", @@ -13399,7 +13399,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "adx", @@ -13409,7 +13409,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nwc", @@ -13420,7 +13420,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cudos", @@ -13430,7 +13430,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "solve", @@ -13440,7 +13440,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "klv", @@ -13450,7 +13450,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rook", @@ -13460,7 +13460,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "front", @@ -13470,7 +13470,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "wtc", @@ -13480,7 +13480,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "beam", @@ -13490,7 +13490,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gto", @@ -13500,7 +13500,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "akro", @@ -13510,7 +13510,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mdt", @@ -13520,7 +13520,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hez", @@ -13530,7 +13530,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pnk", @@ -13540,7 +13540,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ast", @@ -13550,7 +13550,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "snm", @@ -13560,7 +13560,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pivx", @@ -13570,7 +13570,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xdb", @@ -13580,7 +13580,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mir", @@ -13590,7 +13590,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "perl", @@ -13600,7 +13600,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "go", @@ -13610,7 +13610,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "urus", @@ -13620,7 +13620,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "arv", @@ -13630,7 +13630,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cell", @@ -13640,7 +13640,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "caps", @@ -13650,7 +13650,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "wabi", @@ -13660,7 +13660,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "shr", @@ -13670,7 +13670,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "san", @@ -13680,7 +13680,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fuse", @@ -13690,7 +13690,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "poolz", @@ -13700,7 +13700,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "vib", @@ -13710,7 +13710,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "now", @@ -13720,7 +13720,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "muse", @@ -13730,7 +13730,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mint", @@ -13740,7 +13740,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xor", @@ -13750,7 +13750,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mtv", @@ -13760,7 +13760,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "spi", @@ -13770,7 +13770,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "belt", @@ -13780,7 +13780,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ppt", @@ -13790,7 +13790,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "awc", @@ -13800,7 +13800,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "defit", @@ -13810,7 +13810,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "srk", @@ -13820,7 +13820,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lgcy", @@ -13830,7 +13830,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nftb", @@ -13840,7 +13840,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hotcross", @@ -13850,7 +13850,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bin", @@ -13860,7 +13860,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tking", @@ -13870,7 +13870,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mph", @@ -13880,7 +13880,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "skill", @@ -13890,7 +13890,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xio", @@ -13900,7 +13900,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zoon", @@ -13910,7 +13910,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "naft", @@ -13920,7 +13920,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "marsh", @@ -13930,7 +13930,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "spo", @@ -13940,7 +13940,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "eved", @@ -13950,7 +13950,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lead", @@ -13960,7 +13960,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cns", @@ -13970,7 +13970,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sfuel", @@ -13980,7 +13980,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "leash", @@ -13990,7 +13990,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "flokibsc", @@ -14000,7 +14000,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "floki", @@ -14010,7 +14010,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "volt", @@ -14020,7 +14020,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "brise", @@ -14030,7 +14030,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "kishu", @@ -14040,7 +14040,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "shinja", @@ -14050,7 +14050,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ntvrk", @@ -14060,7 +14060,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "akita", @@ -14070,7 +14070,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zinu", @@ -14080,7 +14080,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gafa", @@ -14090,7 +14090,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "trvl", @@ -14100,7 +14100,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "kibabsc", @@ -14110,7 +14110,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "kiba", @@ -14120,7 +14120,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "guard", @@ -14130,7 +14130,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "blocks", @@ -14140,7 +14140,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "copi", @@ -14150,7 +14150,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dogecoin", @@ -14160,7 +14160,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lblock", @@ -14170,7 +14170,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gspi", @@ -14180,7 +14180,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "asia", @@ -14190,7 +14190,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "wise", @@ -14200,7 +14200,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gmr", @@ -14210,7 +14210,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "knc", @@ -14220,7 +14220,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fjb", @@ -14230,7 +14230,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tenfi", @@ -14240,7 +14240,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "btfa", @@ -14250,7 +14250,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "aquagoat", @@ -14260,7 +14260,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avn", @@ -14270,7 +14270,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tenshi", @@ -14280,7 +14280,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "poodl", @@ -14290,7 +14290,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "geth", @@ -14300,7 +14300,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fluf", @@ -14310,7 +14310,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lof", @@ -14320,7 +14320,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nyxt", @@ -14330,7 +14330,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fetbsc", @@ -14340,7 +14340,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mononoke", @@ -14351,7 +14351,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "luffy", @@ -14361,7 +14361,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "vgx", @@ -14371,7 +14371,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdtsol", @@ -14382,7 +14382,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nearbsc", @@ -14392,7 +14392,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "iotxbsc", @@ -14402,7 +14402,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "metiserc20", @@ -14412,7 +14412,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdcsol", @@ -14422,7 +14422,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "clear", @@ -14432,7 +14432,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdcbsc", @@ -14442,7 +14442,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bttcbsc", @@ -14452,7 +14452,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "maticbsc", @@ -14462,7 +14462,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avaxbsc", @@ -14472,7 +14472,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ppm", @@ -14482,7 +14482,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bttc", @@ -14492,7 +14492,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "trxbsc", @@ -14502,7 +14502,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "etcbsc", @@ -14512,7 +14512,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "atombsc", @@ -14522,7 +14522,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bchbsc", @@ -14532,7 +14532,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "vetbsc", @@ -14542,7 +14542,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "filbsc", @@ -14552,7 +14552,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "egldbsc", @@ -14562,7 +14562,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "axsbsc", @@ -14572,7 +14572,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tusdbsc", @@ -14582,7 +14582,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "eosbsc", @@ -14592,7 +14592,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mkrbsc", @@ -14602,7 +14602,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdpbsc", @@ -14612,7 +14612,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zecbsc", @@ -14622,7 +14622,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ftmbsc", @@ -14632,7 +14632,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "manabsc", @@ -14642,7 +14642,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "batbsc", @@ -14652,7 +14652,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zilbsc", @@ -14662,7 +14662,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "compbsc", @@ -14672,7 +14672,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "snxbsc", @@ -14682,7 +14682,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "solbsc", @@ -14692,7 +14692,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ceekerc20", @@ -14703,7 +14703,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "yfibsc", @@ -14713,7 +14713,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "kncbsc", @@ -14723,7 +14723,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "chrbsc", @@ -14733,7 +14733,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sushibsc", @@ -14743,7 +14743,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ankrbsc", @@ -14753,7 +14753,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "celrbsc", @@ -14763,7 +14763,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sandmatic", @@ -14774,7 +14774,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xcnbsc", @@ -14784,7 +14784,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "plamatic", @@ -14794,7 +14794,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "c98erc20", @@ -14804,7 +14804,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "momento", @@ -14814,7 +14814,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fire", @@ -14824,7 +14824,7 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ghc", @@ -14834,8 +14834,8 @@ const List> availableCurrenciesJSONFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true - } + "supportsFixedRate": true, + }, ]; const List> availableCurrenciesJSONActiveFixedRate = [ @@ -14847,7 +14847,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "eth", @@ -14857,7 +14857,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ethbsc", @@ -14867,7 +14867,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdt", @@ -14877,7 +14877,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdterc20", @@ -14888,7 +14888,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": true, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdttrc20", @@ -14899,7 +14899,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdtbsc", @@ -14909,7 +14909,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdc", @@ -14919,7 +14919,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": true, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdcmatic", @@ -14930,7 +14930,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bnbmainnet", @@ -14941,7 +14941,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bnbbsc", @@ -14951,7 +14951,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "busd", @@ -14961,7 +14961,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "busdbsc", @@ -14971,7 +14971,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xrp", @@ -14981,7 +14981,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xrpbsc", @@ -14991,7 +14991,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ada", @@ -15001,7 +15001,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "adabsc", @@ -15011,7 +15011,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sol", @@ -15021,7 +15021,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "doge", @@ -15031,7 +15031,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dot", @@ -15041,7 +15041,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dotbsc", @@ -15051,7 +15051,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dai", @@ -15061,7 +15061,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "matic", @@ -15071,7 +15071,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "maticmainnet", @@ -15082,7 +15082,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "shib", @@ -15092,7 +15092,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "shibbsc", @@ -15102,7 +15102,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "trx", @@ -15112,7 +15112,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avax", @@ -15122,7 +15122,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avaxc", @@ -15132,7 +15132,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "wbtc", @@ -15142,7 +15142,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "leo", @@ -15152,7 +15152,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "uni", @@ -15162,7 +15162,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "etc", @@ -15172,7 +15172,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ltc", @@ -15182,7 +15182,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ltcbsc", @@ -15192,7 +15192,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ftt", @@ -15202,7 +15202,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "link", @@ -15212,7 +15212,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "atom", @@ -15222,7 +15222,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cro", @@ -15232,7 +15232,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "near", @@ -15242,7 +15242,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xmr", @@ -15252,7 +15252,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xlm", @@ -15262,7 +15262,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bch", @@ -15272,7 +15272,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "algo", @@ -15282,7 +15282,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "flow", @@ -15292,7 +15292,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "vet", @@ -15302,7 +15302,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "icp", @@ -15312,7 +15312,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fil", @@ -15322,7 +15322,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ape", @@ -15332,7 +15332,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "eos", @@ -15342,7 +15342,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mana", @@ -15352,7 +15352,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sand", @@ -15362,7 +15362,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hbar", @@ -15372,7 +15372,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xtz", @@ -15382,7 +15382,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xtzbsc", @@ -15392,7 +15392,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "chz", @@ -15402,7 +15402,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "qnt", @@ -15412,7 +15412,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "egld", @@ -15422,7 +15422,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "aave", @@ -15432,7 +15432,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "theta", @@ -15442,7 +15442,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "axs", @@ -15452,7 +15452,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tusd", @@ -15462,7 +15462,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bsv", @@ -15472,7 +15472,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "okb", @@ -15482,7 +15482,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "galabsc", @@ -15492,7 +15492,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zec", @@ -15502,7 +15502,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdp", @@ -15512,7 +15512,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bttbsc", @@ -15522,7 +15522,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "iota", @@ -15532,7 +15532,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mkr", @@ -15542,7 +15542,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hnt", @@ -15552,7 +15552,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ht", @@ -15562,7 +15562,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "snx", @@ -15572,7 +15572,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "grt", @@ -15582,7 +15582,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "klay", @@ -15592,7 +15592,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ftm", @@ -15602,7 +15602,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ftmmainnet", @@ -15612,7 +15612,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "neo", @@ -15622,7 +15622,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "paxg", @@ -15632,7 +15632,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ldo", @@ -15642,7 +15642,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cake", @@ -15652,7 +15652,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "crv", @@ -15662,7 +15662,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nexo", @@ -15672,7 +15672,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bat", @@ -15682,7 +15682,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dash", @@ -15692,7 +15692,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "waves", @@ -15702,7 +15702,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zil", @@ -15712,7 +15712,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": true, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lrc", @@ -15722,7 +15722,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "enj", @@ -15732,7 +15732,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ksm", @@ -15742,7 +15742,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dcr", @@ -15752,7 +15752,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "btg", @@ -15762,7 +15762,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gmt", @@ -15772,7 +15772,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gno", @@ -15782,7 +15782,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "twt", @@ -15792,7 +15792,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xem", @@ -15802,7 +15802,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "1inch", @@ -15812,7 +15812,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "1inchbsc", @@ -15822,7 +15822,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "celo", @@ -15832,7 +15832,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hot", @@ -15842,7 +15842,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "galaerc20", @@ -15853,7 +15853,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ankr", @@ -15863,7 +15863,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "comp", @@ -15873,7 +15873,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cvx", @@ -15883,7 +15883,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "qtum", @@ -15893,7 +15893,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "yfi", @@ -15903,7 +15903,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xdc", @@ -15913,7 +15913,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "iotx", @@ -15923,7 +15923,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cel", @@ -15933,7 +15933,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gusd", @@ -15943,7 +15943,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": true, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tfuel", @@ -15953,7 +15953,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rvn", @@ -15963,7 +15963,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "flux", @@ -15973,7 +15973,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bal", @@ -15983,7 +15983,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "amp", @@ -15993,7 +15993,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "omg", @@ -16003,7 +16003,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zrx", @@ -16013,7 +16013,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rsr", @@ -16023,7 +16023,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "one", @@ -16033,7 +16033,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "jst", @@ -16043,7 +16043,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "icx", @@ -16053,7 +16053,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xym", @@ -16063,7 +16063,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "iost", @@ -16073,7 +16073,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ens", @@ -16083,7 +16083,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lpt", @@ -16093,7 +16093,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "glm", @@ -16103,7 +16103,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "audio", @@ -16113,7 +16113,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "storj", @@ -16123,7 +16123,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ont", @@ -16133,7 +16133,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ontbsc", @@ -16143,7 +16143,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "waxp", @@ -16153,7 +16153,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "srm", @@ -16163,7 +16163,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sc", @@ -16173,7 +16173,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "imx", @@ -16183,7 +16183,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zen", @@ -16193,7 +16193,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "uma", @@ -16203,7 +16203,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "scrt", @@ -16213,7 +16213,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "skl", @@ -16223,7 +16223,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "poly", @@ -16233,7 +16233,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "slp", @@ -16243,7 +16243,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "woobsc", @@ -16253,7 +16253,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "woo", @@ -16263,7 +16263,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "chsb", @@ -16273,7 +16273,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dgb", @@ -16283,7 +16283,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "elon", @@ -16293,7 +16293,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dao", @@ -16303,7 +16303,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pla", @@ -16313,7 +16313,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cvc", @@ -16323,7 +16323,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "spell", @@ -16333,7 +16333,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rndr", @@ -16343,7 +16343,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sushi", @@ -16353,7 +16353,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "btcst", @@ -16363,7 +16363,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lsk", @@ -16373,7 +16373,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "eps", @@ -16383,7 +16383,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pundix", @@ -16393,7 +16393,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "celr", @@ -16403,7 +16403,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ren", @@ -16413,7 +16413,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nano", @@ -16423,7 +16423,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xyo", @@ -16433,7 +16433,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "win", @@ -16443,7 +16443,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ong", @@ -16453,7 +16453,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "people", @@ -16463,7 +16463,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "uos", @@ -16473,7 +16473,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cfx", @@ -16483,7 +16483,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "req", @@ -16493,7 +16493,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dydx", @@ -16503,7 +16503,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ardr", @@ -16513,7 +16513,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rly", @@ -16523,7 +16523,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "coti", @@ -16533,7 +16533,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rlc", @@ -16543,7 +16543,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "powr", @@ -16553,7 +16553,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nmr", @@ -16563,7 +16563,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "snt", @@ -16573,7 +16573,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ocean", @@ -16583,7 +16583,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "chr", @@ -16593,7 +16593,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "api3", @@ -16603,7 +16603,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dent", @@ -16613,7 +16613,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bnt", @@ -16623,7 +16623,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fxs", @@ -16633,7 +16633,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hex", @@ -16643,7 +16643,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "steth", @@ -16653,7 +16653,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "btcb", @@ -16663,7 +16663,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lunc", @@ -16673,7 +16673,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dfi", @@ -16683,7 +16683,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bnx", @@ -16693,7 +16693,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rpl", @@ -16703,7 +16703,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "luna", @@ -16713,7 +16713,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "babydoge", @@ -16723,7 +16723,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "raca", @@ -16733,7 +16733,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sys", @@ -16743,7 +16743,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gal", @@ -16753,7 +16753,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bico", @@ -16763,7 +16763,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "steem", @@ -16773,7 +16773,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "c98", @@ -16783,7 +16783,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "susd", @@ -16793,7 +16793,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ctsi", @@ -16803,7 +16803,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hxro", @@ -16813,7 +16813,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rep", @@ -16823,7 +16823,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fun", @@ -16833,7 +16833,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pyr", @@ -16843,7 +16843,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "strax", @@ -16853,7 +16853,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bsw", @@ -16863,7 +16863,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lyxe", @@ -16873,7 +16873,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mtl", @@ -16883,7 +16883,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "stmx", @@ -16893,7 +16893,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "stpt", @@ -16903,7 +16903,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "elf", @@ -16913,7 +16913,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "oxt", @@ -16923,7 +16923,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ufo", @@ -16933,7 +16933,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ach", @@ -16943,7 +16943,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ogn", @@ -16953,7 +16953,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sfund", @@ -16963,7 +16963,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tlm", @@ -16973,7 +16973,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "loom", @@ -16983,7 +16983,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ant", @@ -16993,7 +16993,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "alice", @@ -17003,7 +17003,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fet", @@ -17013,7 +17013,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ygg", @@ -17023,7 +17023,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ark", @@ -17033,7 +17033,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "utk", @@ -17043,7 +17043,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "super", @@ -17053,7 +17053,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dusk", @@ -17063,7 +17063,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ilv", @@ -17073,7 +17073,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mbox", @@ -17083,7 +17083,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sun", @@ -17093,7 +17093,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "aergo", @@ -17103,7 +17103,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "vra", @@ -17113,7 +17113,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bake", @@ -17123,7 +17123,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xvg", @@ -17133,7 +17133,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dpi", @@ -17143,7 +17143,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pols", @@ -17153,7 +17153,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mln", @@ -17163,7 +17163,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xcad", @@ -17173,7 +17173,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "divi", @@ -17183,7 +17183,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sfp", @@ -17193,7 +17193,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tomo", @@ -17203,7 +17203,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "arpa", @@ -17213,7 +17213,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "band", @@ -17223,7 +17223,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bandmainnet", @@ -17234,7 +17234,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sps", @@ -17244,7 +17244,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ava", @@ -17254,7 +17254,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avaerc20", @@ -17264,7 +17264,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avabsc", @@ -17274,7 +17274,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "jasmy", @@ -17284,7 +17284,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cult", @@ -17294,7 +17294,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "starl", @@ -17304,7 +17304,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "alpaca", @@ -17314,7 +17314,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "blz", @@ -17324,7 +17324,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "kmd", @@ -17334,7 +17334,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "alcx", @@ -17344,7 +17344,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "yfii", @@ -17354,7 +17354,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bel", @@ -17364,7 +17364,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mc", @@ -17374,7 +17374,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dia", @@ -17384,7 +17384,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tko", @@ -17394,7 +17394,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bcd", @@ -17404,7 +17404,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "farm", @@ -17414,7 +17414,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ata", @@ -17424,7 +17424,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fio", @@ -17434,7 +17434,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ubt", @@ -17444,7 +17444,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dnt", @@ -17454,7 +17454,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "grs", @@ -17464,7 +17464,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "om", @@ -17474,7 +17474,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gas", @@ -17484,7 +17484,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fox", @@ -17494,7 +17494,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "firo", @@ -17504,7 +17504,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "aion", @@ -17514,7 +17514,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "adx", @@ -17524,7 +17524,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nwc", @@ -17535,7 +17535,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cudos", @@ -17545,7 +17545,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "solve", @@ -17555,7 +17555,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "klv", @@ -17565,7 +17565,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "rook", @@ -17575,7 +17575,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "front", @@ -17585,7 +17585,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "wtc", @@ -17595,7 +17595,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "beam", @@ -17605,7 +17605,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gto", @@ -17615,7 +17615,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "akro", @@ -17625,7 +17625,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mdt", @@ -17635,7 +17635,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hez", @@ -17645,7 +17645,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pnk", @@ -17655,7 +17655,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ast", @@ -17665,7 +17665,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "snm", @@ -17675,7 +17675,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "pivx", @@ -17685,7 +17685,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xdb", @@ -17695,7 +17695,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mir", @@ -17705,7 +17705,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "perl", @@ -17715,7 +17715,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "go", @@ -17725,7 +17725,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "urus", @@ -17735,7 +17735,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "arv", @@ -17745,7 +17745,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cell", @@ -17755,7 +17755,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "caps", @@ -17765,7 +17765,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "wabi", @@ -17775,7 +17775,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "shr", @@ -17785,7 +17785,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "san", @@ -17795,7 +17795,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fuse", @@ -17805,7 +17805,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "poolz", @@ -17815,7 +17815,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "vib", @@ -17825,7 +17825,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "now", @@ -17835,7 +17835,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "muse", @@ -17845,7 +17845,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mint", @@ -17855,7 +17855,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xor", @@ -17865,7 +17865,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mtv", @@ -17875,7 +17875,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "spi", @@ -17885,7 +17885,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "belt", @@ -17895,7 +17895,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ppt", @@ -17905,7 +17905,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "awc", @@ -17915,7 +17915,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "defit", @@ -17925,7 +17925,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "srk", @@ -17935,7 +17935,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lgcy", @@ -17945,7 +17945,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nftb", @@ -17955,7 +17955,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "hotcross", @@ -17965,7 +17965,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bin", @@ -17975,7 +17975,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tking", @@ -17985,7 +17985,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mph", @@ -17995,7 +17995,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "skill", @@ -18005,7 +18005,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xio", @@ -18015,7 +18015,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zoon", @@ -18025,7 +18025,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "naft", @@ -18035,7 +18035,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "marsh", @@ -18045,7 +18045,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "spo", @@ -18055,7 +18055,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "eved", @@ -18065,7 +18065,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lead", @@ -18075,7 +18075,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "cns", @@ -18085,7 +18085,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sfuel", @@ -18095,7 +18095,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "leash", @@ -18105,7 +18105,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "flokibsc", @@ -18115,7 +18115,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "floki", @@ -18125,7 +18125,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "volt", @@ -18135,7 +18135,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "brise", @@ -18145,7 +18145,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "kishu", @@ -18155,7 +18155,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "shinja", @@ -18165,7 +18165,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ntvrk", @@ -18175,7 +18175,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "akita", @@ -18185,7 +18185,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zinu", @@ -18195,7 +18195,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gafa", @@ -18205,7 +18205,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "trvl", @@ -18215,7 +18215,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "kibabsc", @@ -18225,7 +18225,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "kiba", @@ -18235,7 +18235,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "guard", @@ -18245,7 +18245,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "blocks", @@ -18255,7 +18255,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "copi", @@ -18265,7 +18265,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "dogecoin", @@ -18275,7 +18275,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lblock", @@ -18285,7 +18285,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gspi", @@ -18295,7 +18295,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "asia", @@ -18305,7 +18305,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "wise", @@ -18315,7 +18315,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "gmr", @@ -18325,7 +18325,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "knc", @@ -18335,7 +18335,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fjb", @@ -18345,7 +18345,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tenfi", @@ -18355,7 +18355,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "btfa", @@ -18365,7 +18365,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "aquagoat", @@ -18375,7 +18375,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avn", @@ -18385,7 +18385,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tenshi", @@ -18395,7 +18395,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "poodl", @@ -18405,7 +18405,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "geth", @@ -18415,7 +18415,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fluf", @@ -18425,7 +18425,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "lof", @@ -18435,7 +18435,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nyxt", @@ -18445,7 +18445,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fetbsc", @@ -18455,7 +18455,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mononoke", @@ -18466,7 +18466,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "luffy", @@ -18476,7 +18476,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "vgx", @@ -18486,7 +18486,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdtsol", @@ -18497,7 +18497,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "nearbsc", @@ -18507,7 +18507,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "iotxbsc", @@ -18517,7 +18517,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "metiserc20", @@ -18527,7 +18527,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdcsol", @@ -18537,7 +18537,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "clear", @@ -18547,7 +18547,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdcbsc", @@ -18557,7 +18557,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bttcbsc", @@ -18567,7 +18567,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "maticbsc", @@ -18577,7 +18577,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "avaxbsc", @@ -18587,7 +18587,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ppm", @@ -18597,7 +18597,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bttc", @@ -18607,7 +18607,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "trxbsc", @@ -18617,7 +18617,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "etcbsc", @@ -18627,7 +18627,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "atombsc", @@ -18637,7 +18637,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "bchbsc", @@ -18647,7 +18647,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "vetbsc", @@ -18657,7 +18657,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "filbsc", @@ -18667,7 +18667,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "egldbsc", @@ -18677,7 +18677,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "axsbsc", @@ -18687,7 +18687,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "tusdbsc", @@ -18697,7 +18697,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "eosbsc", @@ -18707,7 +18707,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "mkrbsc", @@ -18717,7 +18717,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "usdpbsc", @@ -18727,7 +18727,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zecbsc", @@ -18737,7 +18737,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ftmbsc", @@ -18747,7 +18747,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "manabsc", @@ -18757,7 +18757,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "batbsc", @@ -18767,7 +18767,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "zilbsc", @@ -18777,7 +18777,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "compbsc", @@ -18787,7 +18787,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "snxbsc", @@ -18797,7 +18797,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "solbsc", @@ -18807,7 +18807,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ceekerc20", @@ -18818,7 +18818,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "yfibsc", @@ -18828,7 +18828,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "kncbsc", @@ -18838,7 +18838,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "chrbsc", @@ -18848,7 +18848,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sushibsc", @@ -18858,7 +18858,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ankrbsc", @@ -18868,7 +18868,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "celrbsc", @@ -18878,7 +18878,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "sandmatic", @@ -18889,7 +18889,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "xcnbsc", @@ -18899,7 +18899,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "plamatic", @@ -18909,7 +18909,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "c98erc20", @@ -18919,7 +18919,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "momento", @@ -18929,7 +18929,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "fire", @@ -18939,7 +18939,7 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true + "supportsFixedRate": true, }, { "ticker": "ghc", @@ -18949,8 +18949,8 @@ const List> availableCurrenciesJSONActiveFixedRate = [ "isFiat": false, "featured": false, "isStable": false, - "supportsFixedRate": true - } + "supportsFixedRate": true, + }, ]; const List> getPairedCurrenciesJSON = [ @@ -18963,7 +18963,7 @@ const List> getPairedCurrenciesJSON = [ "featured": true, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "eth", @@ -18974,7 +18974,7 @@ const List> getPairedCurrenciesJSON = [ "featured": true, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ethbsc", @@ -18985,7 +18985,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdt", @@ -18996,7 +18996,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": true, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdterc20", @@ -19008,7 +19008,7 @@ const List> getPairedCurrenciesJSON = [ "featured": true, "isStable": true, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdttrc20", @@ -19020,7 +19020,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": true, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdtbsc", @@ -19031,7 +19031,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdc", @@ -19042,7 +19042,7 @@ const List> getPairedCurrenciesJSON = [ "featured": true, "isStable": true, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdcmatic", @@ -19054,7 +19054,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": true, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bnbmainnet", @@ -19066,7 +19066,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bnbbsc", @@ -19077,7 +19077,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "busd", @@ -19088,7 +19088,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": true, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "busdbsc", @@ -19099,7 +19099,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xrp", @@ -19110,7 +19110,7 @@ const List> getPairedCurrenciesJSON = [ "featured": true, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xrpbsc", @@ -19121,7 +19121,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ada", @@ -19132,7 +19132,7 @@ const List> getPairedCurrenciesJSON = [ "featured": true, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "adabsc", @@ -19143,7 +19143,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sol", @@ -19154,7 +19154,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "doge", @@ -19165,7 +19165,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dot", @@ -19176,7 +19176,7 @@ const List> getPairedCurrenciesJSON = [ "featured": true, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dotbsc", @@ -19187,7 +19187,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dai", @@ -19198,7 +19198,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": true, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "matic", @@ -19209,7 +19209,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "maticmainnet", @@ -19221,7 +19221,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "shib", @@ -19232,7 +19232,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "shibbsc", @@ -19243,7 +19243,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "trx", @@ -19254,7 +19254,7 @@ const List> getPairedCurrenciesJSON = [ "featured": true, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "avax", @@ -19265,7 +19265,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "avaxc", @@ -19276,7 +19276,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "wbtc", @@ -19287,7 +19287,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "leo", @@ -19298,7 +19298,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "uni", @@ -19309,7 +19309,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "etc", @@ -19320,7 +19320,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ltc", @@ -19331,7 +19331,7 @@ const List> getPairedCurrenciesJSON = [ "featured": true, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ltcbsc", @@ -19342,7 +19342,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ftt", @@ -19353,7 +19353,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "link", @@ -19364,7 +19364,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "atom", @@ -19375,7 +19375,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "cro", @@ -19386,7 +19386,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "near", @@ -19397,7 +19397,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xlm", @@ -19408,7 +19408,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bch", @@ -19419,7 +19419,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "algo", @@ -19430,7 +19430,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "flow", @@ -19441,7 +19441,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "vet", @@ -19452,7 +19452,7 @@ const List> getPairedCurrenciesJSON = [ "featured": true, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "icp", @@ -19463,7 +19463,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fil", @@ -19474,7 +19474,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ape", @@ -19485,7 +19485,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "eos", @@ -19496,7 +19496,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mana", @@ -19507,7 +19507,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sand", @@ -19518,7 +19518,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "hbar", @@ -19529,7 +19529,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xtz", @@ -19540,7 +19540,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xtzbsc", @@ -19551,7 +19551,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "chz", @@ -19562,7 +19562,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "qnt", @@ -19573,7 +19573,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "egld", @@ -19584,7 +19584,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "aave", @@ -19595,7 +19595,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "theta", @@ -19606,7 +19606,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "axs", @@ -19617,7 +19617,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "tusd", @@ -19628,7 +19628,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": true, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bsv", @@ -19639,7 +19639,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "okb", @@ -19650,7 +19650,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "galabsc", @@ -19661,7 +19661,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "zec", @@ -19672,7 +19672,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdp", @@ -19683,7 +19683,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bttbsc", @@ -19694,7 +19694,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "iota", @@ -19705,7 +19705,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mkr", @@ -19716,7 +19716,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "hnt", @@ -19727,7 +19727,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "snx", @@ -19738,7 +19738,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ht", @@ -19749,7 +19749,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "grt", @@ -19760,7 +19760,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ftm", @@ -19771,7 +19771,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ftmmainnet", @@ -19782,7 +19782,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "klay", @@ -19793,7 +19793,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "neo", @@ -19804,7 +19804,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "rune", @@ -19815,7 +19815,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "paxg", @@ -19826,7 +19826,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ldo", @@ -19837,7 +19837,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "cake", @@ -19848,7 +19848,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "crv", @@ -19859,7 +19859,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "nexo", @@ -19870,7 +19870,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bat", @@ -19881,7 +19881,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dash", @@ -19892,7 +19892,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "waves", @@ -19903,7 +19903,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "zil", @@ -19914,7 +19914,7 @@ const List> getPairedCurrenciesJSON = [ "featured": true, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "lrc", @@ -19925,7 +19925,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "enj", @@ -19936,7 +19936,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ksm", @@ -19947,7 +19947,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dcr", @@ -19958,7 +19958,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "btg", @@ -19969,7 +19969,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "gmt", @@ -19980,7 +19980,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "twt", @@ -19991,7 +19991,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "gno", @@ -20002,7 +20002,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xem", @@ -20013,7 +20013,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "1inch", @@ -20024,7 +20024,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "1inchbsc", @@ -20035,7 +20035,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "celo", @@ -20046,7 +20046,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "hot", @@ -20057,7 +20057,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ust", @@ -20069,7 +20069,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "galaerc20", @@ -20081,7 +20081,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ankr", @@ -20092,7 +20092,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "comp", @@ -20103,7 +20103,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "gt", @@ -20114,7 +20114,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "cvx", @@ -20125,7 +20125,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "qtum", @@ -20136,7 +20136,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "yfi", @@ -20147,7 +20147,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xdc", @@ -20158,7 +20158,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "kda", @@ -20169,7 +20169,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "iotx", @@ -20180,7 +20180,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "cel", @@ -20191,7 +20191,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "gusd", @@ -20202,7 +20202,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": true, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "tfuel", @@ -20213,7 +20213,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "rvn", @@ -20224,7 +20224,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "flux", @@ -20235,7 +20235,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bal", @@ -20246,7 +20246,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "amp", @@ -20257,7 +20257,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "op", @@ -20268,7 +20268,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "omg", @@ -20279,7 +20279,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "zrx", @@ -20290,7 +20290,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "one", @@ -20301,7 +20301,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "rsr", @@ -20312,7 +20312,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "icx", @@ -20323,7 +20323,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ens", @@ -20334,7 +20334,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "jst", @@ -20345,7 +20345,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xym", @@ -20356,7 +20356,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "iost", @@ -20367,7 +20367,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "lpt", @@ -20378,7 +20378,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "glm", @@ -20389,7 +20389,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "audio", @@ -20400,7 +20400,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "storj", @@ -20411,7 +20411,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ont", @@ -20422,7 +20422,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ontbsc", @@ -20433,7 +20433,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "waxp", @@ -20444,7 +20444,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "srm", @@ -20455,7 +20455,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sc", @@ -20466,7 +20466,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "imx", @@ -20477,7 +20477,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "zen", @@ -20488,7 +20488,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "uma", @@ -20499,7 +20499,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "scrt", @@ -20510,7 +20510,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mxc", @@ -20521,7 +20521,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "btrst", @@ -20532,7 +20532,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "skl", @@ -20543,7 +20543,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "poly", @@ -20554,7 +20554,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "slp", @@ -20565,7 +20565,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "woobsc", @@ -20576,7 +20576,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "woo", @@ -20587,7 +20587,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "chsb", @@ -20598,7 +20598,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "cspr", @@ -20609,7 +20609,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dgb", @@ -20620,7 +20620,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "eur", @@ -20631,7 +20631,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "elon", @@ -20642,7 +20642,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dao", @@ -20653,7 +20653,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "pla", @@ -20664,7 +20664,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "cvc", @@ -20675,7 +20675,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ceek", @@ -20686,7 +20686,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "spell", @@ -20697,7 +20697,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sushi", @@ -20708,7 +20708,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "rndr", @@ -20719,7 +20719,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "lsk", @@ -20730,7 +20730,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "btcst", @@ -20741,7 +20741,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "eps", @@ -20752,7 +20752,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "pundix", @@ -20763,7 +20763,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "celr", @@ -20774,7 +20774,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ren", @@ -20785,7 +20785,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "nano", @@ -20796,7 +20796,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xyo", @@ -20807,7 +20807,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "win", @@ -20818,7 +20818,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ong", @@ -20829,7 +20829,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "people", @@ -20840,7 +20840,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "uos", @@ -20851,7 +20851,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "cfx", @@ -20862,7 +20862,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "req", @@ -20873,7 +20873,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "tribe", @@ -20884,7 +20884,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dydx", @@ -20895,7 +20895,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ardr", @@ -20906,7 +20906,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "rly", @@ -20917,7 +20917,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "powr", @@ -20928,7 +20928,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "rlc", @@ -20939,7 +20939,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "coti", @@ -20950,7 +20950,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mx", @@ -20961,7 +20961,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "nmr", @@ -20972,7 +20972,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "snt", @@ -20983,7 +20983,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ocean", @@ -20994,7 +20994,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "api3", @@ -21005,7 +21005,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "chr", @@ -21016,7 +21016,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dent", @@ -21027,7 +21027,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bnt", @@ -21038,7 +21038,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fxs", @@ -21049,7 +21049,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "hex", @@ -21060,7 +21060,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "steth", @@ -21071,7 +21071,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "btcb", @@ -21082,7 +21082,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "frax", @@ -21093,7 +21093,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "lunc", @@ -21104,7 +21104,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dfi", @@ -21115,7 +21115,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bnx", @@ -21126,7 +21126,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "rpl", @@ -21137,7 +21137,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "luna", @@ -21148,7 +21148,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "husd", @@ -21159,7 +21159,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": true, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "babydoge", @@ -21170,7 +21170,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "metis", @@ -21181,7 +21181,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "raca", @@ -21192,7 +21192,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "prom", @@ -21203,7 +21203,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sys", @@ -21214,7 +21214,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "gal", @@ -21225,7 +21225,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bico", @@ -21236,7 +21236,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "c98", @@ -21247,7 +21247,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "steem", @@ -21258,7 +21258,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "susd", @@ -21269,7 +21269,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ctsi", @@ -21280,7 +21280,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "hxro", @@ -21291,7 +21291,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "rep", @@ -21302,7 +21302,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fun", @@ -21313,7 +21313,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "pyr", @@ -21324,7 +21324,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bsw", @@ -21335,7 +21335,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "strax", @@ -21346,7 +21346,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "lyxe", @@ -21357,7 +21357,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mtl", @@ -21368,7 +21368,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "stmx", @@ -21379,7 +21379,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "stpt", @@ -21390,7 +21390,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "elf", @@ -21401,7 +21401,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "oxt", @@ -21412,7 +21412,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ufo", @@ -21423,7 +21423,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ach", @@ -21434,7 +21434,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ogn", @@ -21445,7 +21445,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sfund", @@ -21456,7 +21456,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "tlm", @@ -21467,7 +21467,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "loom", @@ -21478,7 +21478,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ant", @@ -21489,7 +21489,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "alice", @@ -21500,7 +21500,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fet", @@ -21511,7 +21511,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ygg", @@ -21522,7 +21522,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ark", @@ -21533,7 +21533,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "utk", @@ -21544,7 +21544,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "super", @@ -21555,7 +21555,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dusk", @@ -21566,7 +21566,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ilv", @@ -21577,7 +21577,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mbox", @@ -21588,7 +21588,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sun", @@ -21599,7 +21599,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "aergo", @@ -21610,7 +21610,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "vra", @@ -21621,7 +21621,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bake", @@ -21632,7 +21632,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xvg", @@ -21643,7 +21643,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dpi", @@ -21654,7 +21654,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "pols", @@ -21665,7 +21665,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mln", @@ -21676,7 +21676,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xcad", @@ -21687,7 +21687,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "divi", @@ -21698,7 +21698,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "divierc20", @@ -21710,7 +21710,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "tomo", @@ -21721,7 +21721,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sfp", @@ -21732,7 +21732,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "arpa", @@ -21743,7 +21743,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "band", @@ -21754,7 +21754,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bandmainnet", @@ -21766,7 +21766,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sps", @@ -21777,7 +21777,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ava", @@ -21788,7 +21788,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "avaerc20", @@ -21799,7 +21799,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "avabsc", @@ -21810,7 +21810,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "jasmy", @@ -21821,7 +21821,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "cult", @@ -21832,7 +21832,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "kmd", @@ -21843,7 +21843,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "starl", @@ -21854,7 +21854,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "aioz", @@ -21865,7 +21865,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "alpaca", @@ -21876,7 +21876,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "blz", @@ -21887,7 +21887,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "alcx", @@ -21898,7 +21898,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "yfii", @@ -21909,7 +21909,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "unfi", @@ -21920,7 +21920,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bel", @@ -21931,7 +21931,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mc", @@ -21942,7 +21942,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dia", @@ -21953,7 +21953,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "tko", @@ -21964,7 +21964,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bcd", @@ -21975,7 +21975,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "anc", @@ -21986,7 +21986,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "farm", @@ -21997,7 +21997,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bifi", @@ -22008,7 +22008,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "ata", @@ -22019,7 +22019,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fio", @@ -22030,7 +22030,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ubt", @@ -22041,7 +22041,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dnt", @@ -22052,7 +22052,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "pit", @@ -22063,7 +22063,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "burger", @@ -22074,7 +22074,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "om", @@ -22085,7 +22085,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "grs", @@ -22096,7 +22096,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "gas", @@ -22107,7 +22107,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "hoge", @@ -22118,7 +22118,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fox", @@ -22129,7 +22129,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "firo", @@ -22140,7 +22140,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "aion", @@ -22151,7 +22151,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "adx", @@ -22162,7 +22162,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "solve", @@ -22173,7 +22173,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "nwc", @@ -22185,7 +22185,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "rook", @@ -22196,7 +22196,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "cudos", @@ -22207,7 +22207,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "klv", @@ -22218,7 +22218,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "front", @@ -22229,7 +22229,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "wtc", @@ -22240,7 +22240,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "beam", @@ -22251,7 +22251,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "gto", @@ -22262,7 +22262,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "akro", @@ -22273,7 +22273,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mdt", @@ -22284,7 +22284,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "hez", @@ -22295,7 +22295,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "pnk", @@ -22306,7 +22306,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ast", @@ -22317,7 +22317,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "snm", @@ -22328,7 +22328,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "qsp", @@ -22339,7 +22339,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "pivx", @@ -22350,7 +22350,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xdb", @@ -22361,7 +22361,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mir", @@ -22372,7 +22372,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "perl", @@ -22383,7 +22383,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "go", @@ -22394,7 +22394,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "urus", @@ -22405,7 +22405,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "arv", @@ -22416,7 +22416,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "cell", @@ -22427,7 +22427,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "caps", @@ -22438,7 +22438,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "wabi", @@ -22449,7 +22449,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "swftc", @@ -22460,7 +22460,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "shr", @@ -22471,7 +22471,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "san", @@ -22482,7 +22482,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dobo", @@ -22493,7 +22493,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "hc", @@ -22504,7 +22504,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fuse", @@ -22515,7 +22515,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dogedash", @@ -22526,7 +22526,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "poolz", @@ -22537,7 +22537,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "vib", @@ -22548,7 +22548,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "now", @@ -22559,7 +22559,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "muse", @@ -22570,7 +22570,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mint", @@ -22581,7 +22581,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xor", @@ -22592,7 +22592,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mtv", @@ -22603,7 +22603,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "spi", @@ -22614,7 +22614,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "belt", @@ -22625,7 +22625,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ppt", @@ -22636,7 +22636,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "awc", @@ -22647,7 +22647,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "defit", @@ -22658,7 +22658,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "srk", @@ -22669,7 +22669,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "swrv", @@ -22680,7 +22680,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "pay", @@ -22691,7 +22691,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "lgcy", @@ -22702,7 +22702,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "nftb", @@ -22713,7 +22713,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "open", @@ -22724,7 +22724,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "hotcross", @@ -22735,7 +22735,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bin", @@ -22746,7 +22746,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "rcn", @@ -22757,7 +22757,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "srn", @@ -22768,7 +22768,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "tking", @@ -22779,7 +22779,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mph", @@ -22790,7 +22790,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "skill", @@ -22801,7 +22801,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mda", @@ -22812,7 +22812,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xio", @@ -22823,7 +22823,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "zoon", @@ -22834,7 +22834,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "naft", @@ -22845,7 +22845,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "lxt", @@ -22856,7 +22856,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "marsh", @@ -22867,7 +22867,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "rainbow", @@ -22878,7 +22878,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "spo", @@ -22889,7 +22889,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "brd", @@ -22900,7 +22900,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "eved", @@ -22911,7 +22911,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "lead", @@ -22922,7 +22922,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "cns", @@ -22933,7 +22933,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sfuel", @@ -22944,7 +22944,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bunny", @@ -22955,7 +22955,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "leash", @@ -22966,7 +22966,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "flokibsc", @@ -22977,7 +22977,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "floki", @@ -22988,7 +22988,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "volt", @@ -22999,7 +22999,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "brise", @@ -23010,7 +23010,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "kishu", @@ -23021,7 +23021,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "shinja", @@ -23032,7 +23032,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ntvrk", @@ -23043,7 +23043,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "akita", @@ -23054,7 +23054,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "zinu", @@ -23065,7 +23065,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "gafa", @@ -23076,7 +23076,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "rbif", @@ -23087,7 +23087,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "trvl", @@ -23098,7 +23098,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "kibabsc", @@ -23109,7 +23109,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "kiba", @@ -23120,7 +23120,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "guard", @@ -23131,7 +23131,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "feg", @@ -23142,7 +23142,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fegbsc", @@ -23153,7 +23153,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "blocks", @@ -23164,7 +23164,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "copi", @@ -23175,7 +23175,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dogecoin", @@ -23186,7 +23186,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "klee", @@ -23197,7 +23197,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "lblock", @@ -23208,7 +23208,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "gspi", @@ -23219,7 +23219,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "gmr", @@ -23230,7 +23230,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "asia", @@ -23241,7 +23241,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "knc", @@ -23252,7 +23252,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fjb", @@ -23263,7 +23263,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "wise", @@ -23274,7 +23274,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "tenfi", @@ -23285,7 +23285,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "btfa", @@ -23296,7 +23296,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "aquagoat", @@ -23307,7 +23307,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "titano", @@ -23318,7 +23318,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sanshu", @@ -23329,7 +23329,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "avn", @@ -23340,7 +23340,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "tenshi", @@ -23351,7 +23351,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "poodl", @@ -23362,7 +23362,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "pika", @@ -23373,7 +23373,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "geth", @@ -23384,7 +23384,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "defc", @@ -23395,7 +23395,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "keanu", @@ -23406,7 +23406,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "rxcg", @@ -23417,7 +23417,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "dgmoon", @@ -23428,7 +23428,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "koromaru", @@ -23439,7 +23439,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "nsh", @@ -23450,7 +23450,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fluf", @@ -23461,7 +23461,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "hmc", @@ -23472,7 +23472,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "nyxt", @@ -23483,7 +23483,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "lof", @@ -23494,7 +23494,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usd", @@ -23505,7 +23505,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "gbp", @@ -23516,7 +23516,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "cad", @@ -23527,7 +23527,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "jpy", @@ -23538,7 +23538,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "rub", @@ -23549,7 +23549,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "aud", @@ -23560,7 +23560,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "chf", @@ -23571,7 +23571,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "czk", @@ -23582,7 +23582,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "dkk", @@ -23593,7 +23593,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "nok", @@ -23604,7 +23604,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "nzd", @@ -23615,7 +23615,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "pln", @@ -23626,7 +23626,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "sek", @@ -23637,7 +23637,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "try", @@ -23648,7 +23648,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "zar", @@ -23659,7 +23659,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "huf", @@ -23670,7 +23670,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "ils", @@ -23681,7 +23681,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "brl", @@ -23692,7 +23692,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "fetbsc", @@ -23703,7 +23703,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mononoke", @@ -23715,7 +23715,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "daibsc", @@ -23726,7 +23726,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "miota", @@ -23737,7 +23737,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "luffy", @@ -23748,7 +23748,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "vgx", @@ -23759,7 +23759,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdtsol", @@ -23771,7 +23771,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "nearbsc", @@ -23782,7 +23782,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "iotxbsc", @@ -23793,7 +23793,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "metiserc20", @@ -23804,7 +23804,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "nowbep2", @@ -23815,7 +23815,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "saitamav2", @@ -23826,7 +23826,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "vlxbsc", @@ -23837,7 +23837,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dfibsc", @@ -23848,7 +23848,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdcsol", @@ -23859,7 +23859,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "clear", @@ -23870,7 +23870,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdcbsc", @@ -23881,7 +23881,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bttcbsc", @@ -23892,7 +23892,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "maticbsc", @@ -23903,7 +23903,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "avaxbsc", @@ -23914,7 +23914,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ppm", @@ -23925,7 +23925,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bttc", @@ -23936,7 +23936,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "trxbsc", @@ -23947,7 +23947,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "etcbsc", @@ -23958,7 +23958,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "atombsc", @@ -23969,7 +23969,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bchbsc", @@ -23980,7 +23980,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "vetbsc", @@ -23991,7 +23991,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "filbsc", @@ -24002,7 +24002,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "egldbsc", @@ -24013,7 +24013,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "axsbsc", @@ -24024,7 +24024,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "tusdbsc", @@ -24035,7 +24035,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "eosbsc", @@ -24046,7 +24046,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mkrbsc", @@ -24057,7 +24057,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdpbsc", @@ -24068,7 +24068,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "daimatic", @@ -24079,7 +24079,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "zecbsc", @@ -24090,7 +24090,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ftmbsc", @@ -24101,7 +24101,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "manabsc", @@ -24112,7 +24112,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "batbsc", @@ -24123,7 +24123,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sxpmainnet", @@ -24135,7 +24135,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "zilbsc", @@ -24146,7 +24146,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "compbsc", @@ -24157,7 +24157,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "snxbsc", @@ -24168,7 +24168,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "solbsc", @@ -24179,7 +24179,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ceekerc20", @@ -24191,7 +24191,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "yfibsc", @@ -24202,7 +24202,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "kncbsc", @@ -24213,7 +24213,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "chrbsc", @@ -24224,7 +24224,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sushibsc", @@ -24235,7 +24235,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdtmatic", @@ -24246,7 +24246,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ankrbsc", @@ -24257,7 +24257,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "celrbsc", @@ -24268,7 +24268,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sandmatic", @@ -24280,7 +24280,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "busdbnb", @@ -24291,7 +24291,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "xcnbsc", @@ -24302,7 +24302,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "plamatic", @@ -24313,7 +24313,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fluxerc20", @@ -24324,7 +24324,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "c98erc20", @@ -24335,7 +24335,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "krw", @@ -24346,7 +24346,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "world", @@ -24357,7 +24357,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": true + "isAvailable": true, }, { "ticker": "all", @@ -24368,7 +24368,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "amd", @@ -24379,7 +24379,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "ang", @@ -24390,7 +24390,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "bam", @@ -24401,7 +24401,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "bbd", @@ -24412,7 +24412,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "bdt", @@ -24423,7 +24423,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "bmd", @@ -24434,7 +24434,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "bnd", @@ -24445,7 +24445,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "bob", @@ -24456,7 +24456,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "bwp", @@ -24467,7 +24467,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "byn", @@ -24478,7 +24478,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "cny", @@ -24489,7 +24489,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "djf", @@ -24500,7 +24500,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "egp", @@ -24511,7 +24511,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "ghs", @@ -24522,7 +24522,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "gtq", @@ -24533,7 +24533,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "hnl", @@ -24544,7 +24544,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "hrk", @@ -24555,7 +24555,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "isk", @@ -24566,7 +24566,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "jmd", @@ -24577,7 +24577,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "kes", @@ -24588,7 +24588,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "kgs", @@ -24599,7 +24599,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "khr", @@ -24610,7 +24610,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "kyd", @@ -24621,7 +24621,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "lbp", @@ -24632,7 +24632,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "lkr", @@ -24643,7 +24643,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "mkd", @@ -24654,7 +24654,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "mnt", @@ -24665,7 +24665,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "mop", @@ -24676,7 +24676,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "mur", @@ -24687,7 +24687,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "mzn", @@ -24698,7 +24698,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "pab", @@ -24709,7 +24709,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "pgk", @@ -24720,7 +24720,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "pkr", @@ -24731,7 +24731,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "pyg", @@ -24742,7 +24742,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "rsd", @@ -24753,7 +24753,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "sos", @@ -24764,7 +24764,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "thb", @@ -24775,7 +24775,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "ttd", @@ -24786,7 +24786,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "tzs", @@ -24797,7 +24797,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "ugx", @@ -24808,7 +24808,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "xaf", @@ -24819,7 +24819,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "xof", @@ -24830,7 +24830,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "zmw", @@ -24841,7 +24841,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": false, - "isAvailable": false + "isAvailable": false, }, { "ticker": "momento", @@ -24852,7 +24852,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fire", @@ -24863,7 +24863,7 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ghc", @@ -24874,8 +24874,8 @@ const List> getPairedCurrenciesJSON = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true - } + "isAvailable": true, + }, ]; const List> getPairedCurrenciesJSONFixedRate = [ @@ -24888,7 +24888,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": true, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "eth", @@ -24899,7 +24899,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": true, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ethbsc", @@ -24910,7 +24910,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdt", @@ -24921,7 +24921,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": true, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdterc20", @@ -24933,7 +24933,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": true, "isStable": true, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdttrc20", @@ -24945,7 +24945,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": true, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdtbsc", @@ -24956,7 +24956,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdc", @@ -24967,7 +24967,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": true, "isStable": true, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdcmatic", @@ -24979,7 +24979,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": true, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bnbmainnet", @@ -24991,7 +24991,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bnbbsc", @@ -25002,7 +25002,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "busd", @@ -25013,7 +25013,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": true, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "busdbsc", @@ -25024,7 +25024,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xrp", @@ -25035,7 +25035,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": true, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xrpbsc", @@ -25046,7 +25046,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ada", @@ -25057,7 +25057,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": true, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "adabsc", @@ -25068,7 +25068,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sol", @@ -25079,7 +25079,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "doge", @@ -25090,7 +25090,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dot", @@ -25101,7 +25101,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": true, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dotbsc", @@ -25112,7 +25112,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dai", @@ -25123,7 +25123,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": true, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "matic", @@ -25134,7 +25134,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "maticmainnet", @@ -25146,7 +25146,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "shib", @@ -25157,7 +25157,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "shibbsc", @@ -25168,7 +25168,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "trx", @@ -25179,7 +25179,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": true, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "avax", @@ -25190,7 +25190,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "avaxc", @@ -25201,7 +25201,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "leo", @@ -25212,7 +25212,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "wbtc", @@ -25223,7 +25223,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "uni", @@ -25234,7 +25234,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "etc", @@ -25245,7 +25245,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ltc", @@ -25256,7 +25256,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": true, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ltcbsc", @@ -25267,7 +25267,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ftt", @@ -25278,7 +25278,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "link", @@ -25289,7 +25289,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "atom", @@ -25300,7 +25300,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "cro", @@ -25311,7 +25311,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "near", @@ -25322,7 +25322,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xlm", @@ -25333,7 +25333,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bch", @@ -25344,7 +25344,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "algo", @@ -25355,7 +25355,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "flow", @@ -25366,7 +25366,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "vet", @@ -25377,7 +25377,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": true, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "icp", @@ -25388,7 +25388,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fil", @@ -25399,7 +25399,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ape", @@ -25410,7 +25410,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "eos", @@ -25421,7 +25421,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mana", @@ -25432,7 +25432,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sand", @@ -25443,7 +25443,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "hbar", @@ -25454,7 +25454,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xtz", @@ -25465,7 +25465,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xtzbsc", @@ -25476,7 +25476,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "chz", @@ -25487,7 +25487,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "qnt", @@ -25498,7 +25498,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "egld", @@ -25509,7 +25509,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "aave", @@ -25520,7 +25520,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "theta", @@ -25531,7 +25531,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "axs", @@ -25542,7 +25542,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "tusd", @@ -25553,7 +25553,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": true, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bsv", @@ -25564,7 +25564,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "okb", @@ -25575,7 +25575,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "galabsc", @@ -25586,7 +25586,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "zec", @@ -25597,7 +25597,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdp", @@ -25608,7 +25608,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "snx", @@ -25619,7 +25619,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bttbsc", @@ -25630,7 +25630,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "iota", @@ -25641,7 +25641,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mkr", @@ -25652,7 +25652,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "hnt", @@ -25663,7 +25663,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ht", @@ -25674,7 +25674,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "grt", @@ -25685,7 +25685,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "klay", @@ -25696,7 +25696,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ftm", @@ -25707,7 +25707,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ftmmainnet", @@ -25718,7 +25718,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "neo", @@ -25729,7 +25729,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "paxg", @@ -25740,7 +25740,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ldo", @@ -25751,7 +25751,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "cake", @@ -25762,7 +25762,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "crv", @@ -25773,7 +25773,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "nexo", @@ -25784,7 +25784,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bat", @@ -25795,7 +25795,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dash", @@ -25806,7 +25806,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "waves", @@ -25817,7 +25817,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "zil", @@ -25828,7 +25828,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": true, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "lrc", @@ -25839,7 +25839,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "enj", @@ -25850,7 +25850,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ksm", @@ -25861,7 +25861,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dcr", @@ -25872,7 +25872,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "btg", @@ -25883,7 +25883,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "gmt", @@ -25894,7 +25894,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "gno", @@ -25905,7 +25905,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xem", @@ -25916,7 +25916,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "twt", @@ -25927,7 +25927,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "1inch", @@ -25938,7 +25938,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "1inchbsc", @@ -25949,7 +25949,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "celo", @@ -25960,7 +25960,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "hot", @@ -25971,7 +25971,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "galaerc20", @@ -25983,7 +25983,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ankr", @@ -25994,7 +25994,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "comp", @@ -26005,7 +26005,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "cvx", @@ -26016,7 +26016,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "qtum", @@ -26027,7 +26027,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "yfi", @@ -26038,7 +26038,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xdc", @@ -26049,7 +26049,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "iotx", @@ -26060,7 +26060,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "cel", @@ -26071,7 +26071,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "gusd", @@ -26082,7 +26082,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": true, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "tfuel", @@ -26093,7 +26093,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "rvn", @@ -26104,7 +26104,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "flux", @@ -26115,7 +26115,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bal", @@ -26126,7 +26126,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "amp", @@ -26137,7 +26137,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "omg", @@ -26148,7 +26148,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "zrx", @@ -26159,7 +26159,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "one", @@ -26170,7 +26170,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "rsr", @@ -26181,7 +26181,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "icx", @@ -26192,7 +26192,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ens", @@ -26203,7 +26203,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "jst", @@ -26214,7 +26214,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xym", @@ -26225,7 +26225,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "iost", @@ -26236,7 +26236,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "lpt", @@ -26247,7 +26247,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "glm", @@ -26258,7 +26258,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "audio", @@ -26269,7 +26269,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "storj", @@ -26280,7 +26280,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ont", @@ -26291,7 +26291,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ontbsc", @@ -26302,7 +26302,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "waxp", @@ -26313,7 +26313,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sc", @@ -26324,7 +26324,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "srm", @@ -26335,7 +26335,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "zen", @@ -26346,7 +26346,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "imx", @@ -26357,7 +26357,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "uma", @@ -26368,7 +26368,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "scrt", @@ -26379,7 +26379,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "skl", @@ -26390,7 +26390,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "poly", @@ -26401,7 +26401,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "slp", @@ -26412,7 +26412,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "woobsc", @@ -26423,7 +26423,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "woo", @@ -26434,7 +26434,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "chsb", @@ -26445,7 +26445,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "elon", @@ -26456,7 +26456,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dgb", @@ -26467,7 +26467,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dao", @@ -26478,7 +26478,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "pla", @@ -26489,7 +26489,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "cvc", @@ -26500,7 +26500,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "spell", @@ -26511,7 +26511,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sushi", @@ -26522,7 +26522,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "rndr", @@ -26533,7 +26533,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "lsk", @@ -26544,7 +26544,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "btcst", @@ -26555,7 +26555,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "eps", @@ -26566,7 +26566,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "pundix", @@ -26577,7 +26577,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "celr", @@ -26588,7 +26588,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ren", @@ -26599,7 +26599,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "nano", @@ -26610,7 +26610,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xyo", @@ -26621,7 +26621,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "win", @@ -26632,7 +26632,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ong", @@ -26643,7 +26643,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "people", @@ -26654,7 +26654,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "uos", @@ -26665,7 +26665,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "cfx", @@ -26676,7 +26676,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "req", @@ -26687,7 +26687,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dydx", @@ -26698,7 +26698,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ardr", @@ -26709,7 +26709,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "rly", @@ -26720,7 +26720,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "powr", @@ -26731,7 +26731,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "nmr", @@ -26742,7 +26742,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "coti", @@ -26753,7 +26753,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "rlc", @@ -26764,7 +26764,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "snt", @@ -26775,7 +26775,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ocean", @@ -26786,7 +26786,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "chr", @@ -26797,7 +26797,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "api3", @@ -26808,7 +26808,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dent", @@ -26819,7 +26819,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bnt", @@ -26830,7 +26830,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fxs", @@ -26841,7 +26841,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "hex", @@ -26852,7 +26852,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "steth", @@ -26863,7 +26863,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "btcb", @@ -26874,7 +26874,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "lunc", @@ -26885,7 +26885,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dfi", @@ -26896,7 +26896,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bnx", @@ -26907,7 +26907,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "rpl", @@ -26918,7 +26918,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "luna", @@ -26929,7 +26929,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "babydoge", @@ -26940,7 +26940,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "raca", @@ -26951,7 +26951,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "prom", @@ -26962,7 +26962,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sys", @@ -26973,7 +26973,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "c98", @@ -26984,7 +26984,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "gal", @@ -26995,7 +26995,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bico", @@ -27006,7 +27006,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "steem", @@ -27017,7 +27017,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "susd", @@ -27028,7 +27028,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ctsi", @@ -27039,7 +27039,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "hxro", @@ -27050,7 +27050,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fun", @@ -27061,7 +27061,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "rep", @@ -27072,7 +27072,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "strax", @@ -27083,7 +27083,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "pyr", @@ -27094,7 +27094,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bsw", @@ -27105,7 +27105,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "lyxe", @@ -27116,7 +27116,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mtl", @@ -27127,7 +27127,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "stmx", @@ -27138,7 +27138,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "stpt", @@ -27149,7 +27149,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ufo", @@ -27160,7 +27160,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "elf", @@ -27171,7 +27171,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "oxt", @@ -27182,7 +27182,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ach", @@ -27193,7 +27193,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ogn", @@ -27204,7 +27204,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sfund", @@ -27215,7 +27215,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "tlm", @@ -27226,7 +27226,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "loom", @@ -27237,7 +27237,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ant", @@ -27248,7 +27248,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "alice", @@ -27259,7 +27259,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fet", @@ -27270,7 +27270,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ygg", @@ -27281,7 +27281,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ark", @@ -27292,7 +27292,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "utk", @@ -27303,7 +27303,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "super", @@ -27314,7 +27314,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dusk", @@ -27325,7 +27325,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ilv", @@ -27336,7 +27336,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mbox", @@ -27347,7 +27347,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sun", @@ -27358,7 +27358,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "aergo", @@ -27369,7 +27369,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "vra", @@ -27380,7 +27380,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xvg", @@ -27391,7 +27391,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bake", @@ -27402,7 +27402,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dpi", @@ -27413,7 +27413,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "pols", @@ -27424,7 +27424,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mln", @@ -27435,7 +27435,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xcad", @@ -27446,7 +27446,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "divi", @@ -27457,7 +27457,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "tomo", @@ -27468,7 +27468,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "arpa", @@ -27479,7 +27479,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sfp", @@ -27490,7 +27490,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "band", @@ -27501,7 +27501,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bandmainnet", @@ -27513,7 +27513,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sps", @@ -27524,7 +27524,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ava", @@ -27535,7 +27535,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "avaerc20", @@ -27546,7 +27546,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "avabsc", @@ -27557,7 +27557,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "jasmy", @@ -27568,7 +27568,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "cult", @@ -27579,7 +27579,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "starl", @@ -27590,7 +27590,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "kmd", @@ -27601,7 +27601,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "alpaca", @@ -27612,7 +27612,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "blz", @@ -27623,7 +27623,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "alcx", @@ -27634,7 +27634,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "yfii", @@ -27645,7 +27645,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bel", @@ -27656,7 +27656,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mc", @@ -27667,7 +27667,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dia", @@ -27678,7 +27678,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "tko", @@ -27689,7 +27689,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bcd", @@ -27700,7 +27700,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "farm", @@ -27711,7 +27711,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ata", @@ -27722,7 +27722,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fio", @@ -27733,7 +27733,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ubt", @@ -27744,7 +27744,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dnt", @@ -27755,7 +27755,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "om", @@ -27766,7 +27766,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "grs", @@ -27777,7 +27777,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "gas", @@ -27788,7 +27788,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fox", @@ -27799,7 +27799,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "firo", @@ -27810,7 +27810,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "aion", @@ -27821,7 +27821,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "adx", @@ -27832,7 +27832,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "cudos", @@ -27843,7 +27843,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "nwc", @@ -27855,7 +27855,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "rook", @@ -27866,7 +27866,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "solve", @@ -27877,7 +27877,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "klv", @@ -27888,7 +27888,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "front", @@ -27899,7 +27899,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "wtc", @@ -27910,7 +27910,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "beam", @@ -27921,7 +27921,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "gto", @@ -27932,7 +27932,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "akro", @@ -27943,7 +27943,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "hez", @@ -27954,7 +27954,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mdt", @@ -27965,7 +27965,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "pnk", @@ -27976,7 +27976,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ast", @@ -27987,7 +27987,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "snm", @@ -27998,7 +27998,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xdb", @@ -28009,7 +28009,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "pivx", @@ -28020,7 +28020,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mir", @@ -28031,7 +28031,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "perl", @@ -28042,7 +28042,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "go", @@ -28053,7 +28053,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "urus", @@ -28064,7 +28064,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "arv", @@ -28075,7 +28075,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "cell", @@ -28086,7 +28086,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "caps", @@ -28097,7 +28097,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "wabi", @@ -28108,7 +28108,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "shr", @@ -28119,7 +28119,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "san", @@ -28130,7 +28130,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fuse", @@ -28141,7 +28141,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "poolz", @@ -28152,7 +28152,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "vib", @@ -28163,7 +28163,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "now", @@ -28174,7 +28174,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "muse", @@ -28185,7 +28185,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mint", @@ -28196,7 +28196,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xor", @@ -28207,7 +28207,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mtv", @@ -28218,7 +28218,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ppt", @@ -28229,7 +28229,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "spi", @@ -28240,7 +28240,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "belt", @@ -28251,7 +28251,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "awc", @@ -28262,7 +28262,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "defit", @@ -28273,7 +28273,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "srk", @@ -28284,7 +28284,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "lgcy", @@ -28295,7 +28295,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "nftb", @@ -28306,7 +28306,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "hotcross", @@ -28317,7 +28317,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bin", @@ -28328,7 +28328,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "tking", @@ -28339,7 +28339,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mph", @@ -28350,7 +28350,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "skill", @@ -28361,7 +28361,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xio", @@ -28372,7 +28372,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "zoon", @@ -28383,7 +28383,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "naft", @@ -28394,7 +28394,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "marsh", @@ -28405,7 +28405,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "spo", @@ -28416,7 +28416,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "eved", @@ -28427,7 +28427,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "lead", @@ -28438,7 +28438,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "cns", @@ -28449,7 +28449,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sfuel", @@ -28460,7 +28460,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "leash", @@ -28471,7 +28471,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "flokibsc", @@ -28482,7 +28482,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "floki", @@ -28493,7 +28493,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "volt", @@ -28504,7 +28504,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "brise", @@ -28515,7 +28515,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "kishu", @@ -28526,7 +28526,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "shinja", @@ -28537,7 +28537,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ntvrk", @@ -28548,7 +28548,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "akita", @@ -28559,7 +28559,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "zinu", @@ -28570,7 +28570,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "gafa", @@ -28581,7 +28581,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "trvl", @@ -28592,7 +28592,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "kibabsc", @@ -28603,7 +28603,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "kiba", @@ -28614,7 +28614,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "guard", @@ -28625,7 +28625,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "blocks", @@ -28636,7 +28636,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "copi", @@ -28647,7 +28647,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "dogecoin", @@ -28658,7 +28658,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "lblock", @@ -28669,7 +28669,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "asia", @@ -28680,7 +28680,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "gspi", @@ -28691,7 +28691,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "gmr", @@ -28702,7 +28702,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "knc", @@ -28713,7 +28713,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "btfa", @@ -28724,7 +28724,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fjb", @@ -28735,7 +28735,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "wise", @@ -28746,7 +28746,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "tenfi", @@ -28757,7 +28757,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "aquagoat", @@ -28768,7 +28768,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "avn", @@ -28779,7 +28779,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "geth", @@ -28790,7 +28790,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "tenshi", @@ -28801,7 +28801,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "poodl", @@ -28812,7 +28812,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fluf", @@ -28823,7 +28823,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "nyxt", @@ -28834,7 +28834,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "lof", @@ -28845,7 +28845,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fetbsc", @@ -28856,7 +28856,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mononoke", @@ -28868,7 +28868,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "luffy", @@ -28879,7 +28879,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "vgx", @@ -28890,7 +28890,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdtsol", @@ -28902,7 +28902,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "nearbsc", @@ -28913,7 +28913,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "iotxbsc", @@ -28924,7 +28924,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "metiserc20", @@ -28935,7 +28935,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdcsol", @@ -28946,7 +28946,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "clear", @@ -28957,7 +28957,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdcbsc", @@ -28968,7 +28968,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bttcbsc", @@ -28979,7 +28979,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "maticbsc", @@ -28990,7 +28990,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "avaxbsc", @@ -29001,7 +29001,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ppm", @@ -29012,7 +29012,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bttc", @@ -29023,7 +29023,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "trxbsc", @@ -29034,7 +29034,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "etcbsc", @@ -29045,7 +29045,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "atombsc", @@ -29056,7 +29056,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "bchbsc", @@ -29067,7 +29067,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "vetbsc", @@ -29078,7 +29078,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "filbsc", @@ -29089,7 +29089,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "egldbsc", @@ -29100,7 +29100,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "axsbsc", @@ -29111,7 +29111,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "tusdbsc", @@ -29122,7 +29122,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "eosbsc", @@ -29133,7 +29133,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "mkrbsc", @@ -29144,7 +29144,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "usdpbsc", @@ -29155,7 +29155,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "zecbsc", @@ -29166,7 +29166,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ftmbsc", @@ -29177,7 +29177,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "manabsc", @@ -29188,7 +29188,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "batbsc", @@ -29199,7 +29199,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "zilbsc", @@ -29210,7 +29210,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "compbsc", @@ -29221,7 +29221,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "snxbsc", @@ -29232,7 +29232,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "solbsc", @@ -29243,7 +29243,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ceekerc20", @@ -29255,7 +29255,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "yfibsc", @@ -29266,7 +29266,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "kncbsc", @@ -29277,7 +29277,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "chrbsc", @@ -29288,7 +29288,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sushibsc", @@ -29299,7 +29299,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ankrbsc", @@ -29310,7 +29310,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "celrbsc", @@ -29321,7 +29321,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "sandmatic", @@ -29333,7 +29333,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "xcnbsc", @@ -29344,7 +29344,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "plamatic", @@ -29355,7 +29355,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "c98erc20", @@ -29366,7 +29366,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "momento", @@ -29377,7 +29377,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "fire", @@ -29388,7 +29388,7 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true + "isAvailable": true, }, { "ticker": "ghc", @@ -29399,8 +29399,8 @@ const List> getPairedCurrenciesJSONFixedRate = [ "featured": false, "isStable": false, "supportsFixedRate": true, - "isAvailable": true - } + "isAvailable": true, + }, ]; const Map estFixedRateExchangeAmountJSON = { @@ -29409,7 +29409,7 @@ const Map estFixedRateExchangeAmountJSON = { "transactionSpeedForecast": "10-60", "warningMessage": null, "rateId": "1t2W5KBPqhycSJVYpaNZzYWLfMr0kSFe", - "validUntil": "2022-08-29T18:42:12.940Z" + "validUntil": "2022-08-29T18:42:12.940Z", }; const List> fixedRateMarketsJSON = [ @@ -29419,7 +29419,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.978, "minerFee": 0.00032, "min": 0.0880393, - "max": 83.33363733 + "max": 83.33363733, }, { "from": "btg", @@ -29427,7 +29427,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.14941302599839185, "minerFee": 0.0010244438488340927, "min": 0.09442316, - "max": 83.339702 + "max": 83.339702, }, { "from": "btg", @@ -29435,7 +29435,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.00111492, "minerFee": 0.0000339324, "min": 0.09972141, - "max": 83.34848533 + "max": 83.34848533, }, { "from": "btg", @@ -29443,7 +29443,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.35757536882617064, "minerFee": 0.0001584990378447723, "min": 0.08815272, - "max": 83.33374508 + "max": 83.33374508, }, { "from": "btg", @@ -29451,7 +29451,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.6819082568807339, "minerFee": 0.0009035596330275229, "min": 0.08901381, - "max": 83.33456311 + "max": 83.33456311, }, { "from": "btg", @@ -29459,7 +29459,7 @@ const List> fixedRateMarketsJSON = [ "rate": 7587.544889696968, "minerFee": 1.5413161373737372, "min": 0.08791797, - "max": 83.33352206 + "max": 83.33352206, }, { "from": "btg", @@ -29467,7 +29467,7 @@ const List> fixedRateMarketsJSON = [ "rate": 50.35772357723577, "minerFee": 0.40823848238482385, "min": 0.09564422, - "max": 83.340862 + "max": 83.340862, }, { "from": "btg", @@ -29475,7 +29475,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.4083956043956044, "minerFee": 0.0008668131868131869, "min": 0.08979579, - "max": 83.335306 + "max": 83.335306, }, { "from": "btg", @@ -29483,7 +29483,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.1899352640545145, "minerFee": 0.0001314752538330494, "min": 0.08839629, - "max": 83.33397646 + "max": 83.33397646, }, { "from": "btg", @@ -29491,7 +29491,7 @@ const List> fixedRateMarketsJSON = [ "rate": 7.1099066615678765, "minerFee": 0.011163174913957935, "min": 0.08925485, - "max": 83.3347921 + "max": 83.3347921, }, { "from": "btg", @@ -29499,7 +29499,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.4807761966364812, "minerFee": 0.00017865459249676583, "min": 0.08808272, - "max": 83.33367858 + "max": 83.33367858, }, { "from": "btg", @@ -29507,7 +29507,7 @@ const List> fixedRateMarketsJSON = [ "rate": 215.27520369124952, "minerFee": 0.05521884722965227, "min": 0.08797014, - "max": 83.33357162 + "max": 83.33357162, }, { "from": "btg", @@ -29515,7 +29515,7 @@ const List> fixedRateMarketsJSON = [ "rate": 68.61046153846155, "minerFee": 0.3112246153846154, "min": 0.09215562, - "max": 83.33754783 + "max": 83.33754783, }, { "from": "btg", @@ -29523,7 +29523,7 @@ const List> fixedRateMarketsJSON = [ "rate": 506.7818181818181, "minerFee": 0.33290909090909093, "min": 0.08807229, - "max": 83.33394366 + "max": 83.33394366, }, { "from": "btg", @@ -29531,7 +29531,7 @@ const List> fixedRateMarketsJSON = [ "rate": 12.238419319429198, "minerFee": 1.5140897553896817, "min": 0.19528785, - "max": 83.43552345 + "max": 83.43552345, }, { "from": "btg", @@ -29539,7 +29539,7 @@ const List> fixedRateMarketsJSON = [ "rate": 2144.076923076923, "minerFee": 0.35776923076923073, "min": 0.0878825, - "max": 83.33348836 + "max": 83.33348836, }, { "from": "btg", @@ -29547,7 +29547,7 @@ const List> fixedRateMarketsJSON = [ "rate": 217.71801333333332, "minerFee": 2.035618488888889, "min": 0.0968634, - "max": 83.34202022 + "max": 83.34202022, }, { "from": "btg", @@ -29555,7 +29555,7 @@ const List> fixedRateMarketsJSON = [ "rate": 107.9303000968054, "minerFee": 12.200577818809293, "min": 0.18600747, - "max": 83.42670709 + "max": 83.42670709, }, { "from": "btg", @@ -29563,7 +29563,7 @@ const List> fixedRateMarketsJSON = [ "rate": 153.35900962861072, "minerFee": 18.1227649785282, "min": 0.19046837, - "max": 83.43094494 + "max": 83.43094494, }, { "from": "btg", @@ -29571,7 +29571,7 @@ const List> fixedRateMarketsJSON = [ "rate": 166.4059701492537, "minerFee": 1.0272238805970149, "min": 0.0937565, - "max": 83.33906866 + "max": 83.33906866, }, { "from": "btg", @@ -29579,7 +29579,7 @@ const List> fixedRateMarketsJSON = [ "rate": 41.2170055452865, "minerFee": 4.792601348391867, "min": 0.18882058, - "max": 83.42937954 + "max": 83.42937954, }, { "from": "btg", @@ -29587,7 +29587,7 @@ const List> fixedRateMarketsJSON = [ "rate": 774.2499999999999, "minerFee": 282.42956266666664, "min": 0.40485075, - "max": 83.63460821 + "max": 83.63460821, }, { "from": "btg", @@ -29595,7 +29595,7 @@ const List> fixedRateMarketsJSON = [ "rate": 74.59896160535116, "minerFee": 8.56401184909699, "min": 0.18755538, - "max": 83.4281776 + "max": 83.4281776, }, { "from": "btg", @@ -29603,7 +29603,7 @@ const List> fixedRateMarketsJSON = [ "rate": 21.37588053215926, "minerFee": 0.10349707656967841, "min": 0.09245448, - "max": 83.33783174 + "max": 83.33783174, }, { "from": "btg", @@ -29611,7 +29611,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.7817253376313944, "minerFee": 0.0002278896257883672, "min": 0.08800441, - "max": 83.33360418 + "max": 83.33360418, }, { "from": "btg", @@ -29619,7 +29619,7 @@ const List> fixedRateMarketsJSON = [ "rate": 356.2044728434505, "minerFee": 14.058274760383387, "min": 0.1263179, - "max": 83.370002 + "max": 83.370002, }, { "from": "btg", @@ -29627,7 +29627,7 @@ const List> fixedRateMarketsJSON = [ "rate": 357.3461538461538, "minerFee": 0.15846153846153846, "min": 0.08815299, - "max": 83.33374533 + "max": 83.33374533, }, { "from": "btg", @@ -29635,7 +29635,7 @@ const List> fixedRateMarketsJSON = [ "rate": 81.91917707567963, "minerFee": 0.015901910360029387, "min": 0.08790941, - "max": 83.33351393 + "max": 83.33351393, }, { "from": "btg", @@ -29643,7 +29643,7 @@ const List> fixedRateMarketsJSON = [ "rate": 62.286033519553065, "minerFee": 7.322690224134078, "min": 0.18994093, - "max": 83.43044388 + "max": 83.43044388, }, { "from": "btg", @@ -29651,7 +29651,7 @@ const List> fixedRateMarketsJSON = [ "rate": 511.4311926605504, "minerFee": 181.11263586477062, "min": 0.39559318, - "max": 83.62581351 + "max": 83.62581351, }, { "from": "btg", @@ -29659,7 +29659,7 @@ const List> fixedRateMarketsJSON = [ "rate": 141.1291139240506, "minerFee": 23.490017377594935, "min": 0.23316384, - "max": 83.47150564 + "max": 83.47150564, }, { "from": "btg", @@ -29667,7 +29667,7 @@ const List> fixedRateMarketsJSON = [ "rate": 2.913641091298667, "minerFee": 0.43348143929919, "min": 0.2171297, - "max": 8.4562732 + "max": 8.4562732, }, { "from": "btg", @@ -29675,7 +29675,7 @@ const List> fixedRateMarketsJSON = [ "rate": 49.07218309859154, "minerFee": 4.7917473090140845, "min": 0.1726251, - "max": 83.41399383 + "max": 83.41399383, }, { "from": "btg", @@ -29683,7 +29683,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.14445518155384615, "minerFee": 0.017155832749538462, "min": 0.19090617, - "max": 8.43136085 + "max": 8.43136085, }, { "from": "btg", @@ -29691,7 +29691,7 @@ const List> fixedRateMarketsJSON = [ "rate": 65.47258041103933, "minerFee": 7.542106490598943, "min": 0.18776946, - "max": 83.42838098 + "max": 83.42838098, }, { "from": "btg", @@ -29699,7 +29699,7 @@ const List> fixedRateMarketsJSON = [ "rate": 18.0982949469242, "minerFee": 2.2297705262489855, "min": 0.19484193, - "max": 83.43509983 + "max": 83.43509983, }, { "from": "btg", @@ -29707,7 +29707,7 @@ const List> fixedRateMarketsJSON = [ "rate": 15.314835164835165, "minerFee": 0.0025054945054945057, "min": 0.0878793, - "max": 83.33348533 + "max": 83.33348533, }, { "from": "btg", @@ -29715,7 +29715,7 @@ const List> fixedRateMarketsJSON = [ "rate": 78.34996486296556, "minerFee": 0.5128179901616303, "min": 0.9276065, - "max": 83.3394145 + "max": 83.3394145, }, { "from": "btg", @@ -29723,7 +29723,7 @@ const List> fixedRateMarketsJSON = [ "rate": 28.6464542651593, "minerFee": 3.5423108464850976, "min": 0.19520763, - "max": 83.43544724 + "max": 83.43544724, }, { "from": "btg", @@ -29731,7 +29731,7 @@ const List> fixedRateMarketsJSON = [ "rate": 5591.813479503722, "minerFee": 1.0148161111662533, "min": 0.08789679, - "max": 83.33350194 + "max": 83.33350194, }, { "from": "btg", @@ -29739,7 +29739,7 @@ const List> fixedRateMarketsJSON = [ "rate": 54.12233009708737, "minerFee": 0.10885436893203884, "min": 0.08968632, - "max": 83.335202 + "max": 83.335202, }, { "from": "btg", @@ -29747,7 +29747,7 @@ const List> fixedRateMarketsJSON = [ "rate": 64.0758620689655, "minerFee": 6.101253498620689, "min": 0.17046022, - "max": 83.4119372 + "max": 83.4119372, }, { "from": "btg", @@ -29755,7 +29755,7 @@ const List> fixedRateMarketsJSON = [ "rate": 4.952998667258995, "minerFee": 0.0018103065304309195, "min": 0.08807676, - "max": 83.33367291 + "max": 83.33367291, }, { "from": "btg", @@ -29763,7 +29763,7 @@ const List> fixedRateMarketsJSON = [ "rate": 122.65346534653465, "minerFee": 0.22006600660066009, "min": 0.08947404, - "max": 83.33500033 + "max": 83.33500033, }, { "from": "btg", @@ -29771,7 +29771,7 @@ const List> fixedRateMarketsJSON = [ "rate": 25.127789046653138, "minerFee": 0.004110885733603786, "min": 0.0878793, - "max": 83.33348533 + "max": 83.33348533, }, { "from": "btg", @@ -29779,7 +29779,7 @@ const List> fixedRateMarketsJSON = [ "rate": 22.531585517999996, "minerFee": 3.32270418896, "min": 0.21594896, - "max": 83.4551515 + "max": 83.4551515, }, { "from": "btg", @@ -29787,7 +29787,7 @@ const List> fixedRateMarketsJSON = [ "rate": 913.8688524590164, "minerFee": 3.1495081967213117, "min": 0.09108983, - "max": 83.33653533 + "max": 83.33653533, }, { "from": "btg", @@ -29795,7 +29795,7 @@ const List> fixedRateMarketsJSON = [ "rate": 1.4220918367346935, "minerFee": 0.0003326530612244898, "min": 0.08794808, - "max": 83.33355066 + "max": 83.33355066, }, { "from": "btg", @@ -29803,7 +29803,7 @@ const List> fixedRateMarketsJSON = [ "rate": 66.9421016826923, "minerFee": 0.011451673076923076, "min": 0.08788656, - "max": 83.33349223 + "max": 83.33349223, }, { "from": "btg", @@ -29811,7 +29811,7 @@ const List> fixedRateMarketsJSON = [ "rate": 2831.0311962814067, "minerFee": 512.7883001779396, "min": 0.24360594, - "max": 83.48142563 + "max": 83.48142563, }, { "from": "btg", @@ -29819,7 +29819,7 @@ const List> fixedRateMarketsJSON = [ "rate": 2.4237391304347824, "minerFee": 0.0003965217391304348, "min": 0.0878793, - "max": 83.33348533 + "max": 83.33348533, }, { "from": "btg", @@ -29827,7 +29827,7 @@ const List> fixedRateMarketsJSON = [ "rate": 478.5064377682403, "minerFee": 0.17828326180257512, "min": 0.08808369, - "max": 83.33367949 + "max": 83.33367949, }, { "from": "btg", @@ -29835,7 +29835,7 @@ const List> fixedRateMarketsJSON = [ "rate": 9.138688524590162, "minerFee": 0.022855081967213114, "min": 0.11150935, - "max": 83.33565693 + "max": 83.33565693, }, { "from": "btg", @@ -29843,7 +29843,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.4225770628636363, "minerFee": 0.00026913326181818184, "min": 0.08834211, - "max": 83.333925 + "max": 83.333925, }, { "from": "btg", @@ -29851,7 +29851,7 @@ const List> fixedRateMarketsJSON = [ "rate": 22.533179853599997, "minerFee": 2.281977409792, "min": 0.17577619, - "max": 83.41698737 + "max": 83.41698737, }, { "from": "btg", @@ -29859,7 +29859,7 @@ const List> fixedRateMarketsJSON = [ "rate": 1664.059701492537, "minerFee": 344.29837234597017, "min": 0.2900701, - "max": 83.52556659 + "max": 83.52556659, }, { "from": "btg", @@ -29867,7 +29867,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.9460540857430729, "minerFee": 0.22812232367455917, "min": 0.29734157, - "max": 8.53247448 + "max": 8.53247448, }, { "from": "btg", @@ -29875,7 +29875,7 @@ const List> fixedRateMarketsJSON = [ "rate": 13.894815553339976, "minerFee": 1.6320535804586243, "min": 0.18986001, - "max": 83.430367 + "max": 83.430367, }, { "from": "btg", @@ -29883,7 +29883,7 @@ const List> fixedRateMarketsJSON = [ "rate": 1.3132155477031802, "minerFee": 0.1431611909893993, "min": 0.18250796, - "max": 41.75671589 + "max": 41.75671589, }, { "from": "btg", @@ -29891,7 +29891,7 @@ const List> fixedRateMarketsJSON = [ "rate": 92.44776119402984, "minerFee": 0.015124378109452736, "min": 0.0878793, - "max": 83.33348533 + "max": 83.33348533, }, { "from": "btg", @@ -29899,7 +29899,7 @@ const List> fixedRateMarketsJSON = [ "rate": 14.913322632423755, "minerFee": 0.0074398073836276085, "min": 0.08820715, - "max": 83.33379679 + "max": 83.33379679, }, { "from": "btg", @@ -29907,7 +29907,7 @@ const List> fixedRateMarketsJSON = [ "rate": 23400.83937943925, "minerFee": 2345.177409170685, "min": 0.17503259, - "max": 8.41628095 + "max": 8.41628095, }, { "from": "btg", @@ -29915,7 +29915,7 @@ const List> fixedRateMarketsJSON = [ "rate": 157.2524682651622, "minerFee": 18.869392025176303, "min": 0.19205238, - "max": 83.43244975 + "max": 83.43244975, }, { "from": "btg", @@ -29923,7 +29923,7 @@ const List> fixedRateMarketsJSON = [ "rate": 2658.445121688583, "minerFee": 0.44491944731101574, "min": 0.08788298, - "max": 83.33348882 + "max": 83.33348882, }, { "from": "btg", @@ -29931,7 +29931,7 @@ const List> fixedRateMarketsJSON = [ "rate": 1161.375, "minerFee": 280.20967087, "min": 0.88734432, - "max": 8.53261043 + "max": 8.53261043, }, { "from": "btg", @@ -29939,7 +29939,7 @@ const List> fixedRateMarketsJSON = [ "rate": 10729.367205683944, "minerFee": 1270.1233723482715, "min": 0.1906056, - "max": 83.43107531 + "max": 83.43107531, }, { "from": "btg", @@ -29947,7 +29947,7 @@ const List> fixedRateMarketsJSON = [ "rate": 81.91917707567966, "minerFee": 0.014651910360029392, "min": 0.08789423, - "max": 83.33349951 + "max": 83.33349951, }, { "from": "btg", @@ -29955,7 +29955,7 @@ const List> fixedRateMarketsJSON = [ "rate": 667.6167664670658, "minerFee": 0.11922155688622754, "min": 0.08789395, - "max": 83.33349924 + "max": 83.33349924, }, { "from": "btg", @@ -29963,7 +29963,7 @@ const List> fixedRateMarketsJSON = [ "rate": 13.291845493562231, "minerFee": 1.8319124050500715, "min": 0.20750763, - "max": 83.44713224 + "max": 83.44713224, }, { "from": "btg", @@ -29971,7 +29971,7 @@ const List> fixedRateMarketsJSON = [ "rate": 3.4347504621072082, "minerFee": 0.4073198423659889, "min": 0.19079756, - "max": 83.43125767 + "max": 83.43125767, }, { "from": "btg", @@ -29979,7 +29979,7 @@ const List> fixedRateMarketsJSON = [ "rate": 453.2195121951219, "minerFee": 39.28528055146341, "min": 2.90652983, - "max": 83.40493666 + "max": 83.40493666, }, { "from": "btg", @@ -29987,7 +29987,7 @@ const List> fixedRateMarketsJSON = [ "rate": 774.2499999999999, "minerFee": 0.13666666666666666, "min": 0.08789193, - "max": 83.33349733 + "max": 83.33349733, }, { "from": "btg", @@ -29995,7 +29995,7 @@ const List> fixedRateMarketsJSON = [ "rate": 271.2700729927007, "minerFee": 27.484418842043794, "min": 0.17581551, - "max": 83.41702472 + "max": 83.41702472, }, { "from": "btg", @@ -30003,7 +30003,7 @@ const List> fixedRateMarketsJSON = [ "rate": 62.460504201680656, "minerFee": 0.010218487394957981, "min": 0.0878793, - "max": 83.33348533 + "max": 83.33348533, }, { "from": "btg", @@ -30011,7 +30011,7 @@ const List> fixedRateMarketsJSON = [ "rate": 43.36522753792299, "minerFee": 5.322881865752626, "min": 0.19444359, - "max": 83.4347214 + "max": 83.4347214, }, { "from": "btg", @@ -30019,7 +30019,7 @@ const List> fixedRateMarketsJSON = [ "rate": 118.48246546227416, "minerFee": 13.629062924431457, "min": 0.18784309, - "max": 83.42845093 + "max": 83.42845093, }, { "from": "btg", @@ -30027,7 +30027,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.08000287026406429, "minerFee": 0.00041308840413318025, "min": 0.09277544, - "max": 83.33813666 + "max": 83.33813666, }, { "from": "btg", @@ -30035,7 +30035,7 @@ const List> fixedRateMarketsJSON = [ "rate": 19.067057233715165, "minerFee": 1.784705334966661, "min": 0.1691243, - "max": 18.41066808 + "max": 18.41066808, }, { "from": "btg", @@ -30043,7 +30043,7 @@ const List> fixedRateMarketsJSON = [ "rate": 952.9230769230768, "minerFee": 79.44165195589743, "min": 0.16021002, - "max": 83.40219951 + "max": 83.40219951, }, { "from": "btg", @@ -30051,7 +30051,7 @@ const List> fixedRateMarketsJSON = [ "rate": 100.62454873646209, "minerFee": 0.026462093862815887, "min": 0.0879765, - "max": 83.33357766 + "max": 83.33357766, }, { "from": "btg", @@ -30059,7 +30059,7 @@ const List> fixedRateMarketsJSON = [ "rate": 3454.707273038916, "minerFee": 1092.0153549739327, "min": 0.36241721, - "max": 8.59429634 + "max": 8.59429634, }, { "from": "btg", @@ -30067,7 +30067,7 @@ const List> fixedRateMarketsJSON = [ "rate": 211.1590909090909, "minerFee": 24.591947434545457, "min": 0.1889809, - "max": 83.42953184 + "max": 83.42953184, }, { "from": "btg", @@ -30075,7 +30075,7 @@ const List> fixedRateMarketsJSON = [ "rate": 138.47908622587357, "minerFee": 0.023055065231226766, "min": 0.08788213, - "max": 8.33348801 + "max": 8.33348801, }, { "from": "btg", @@ -30083,7 +30083,7 @@ const List> fixedRateMarketsJSON = [ "rate": 282.56551392615404, "minerFee": 37.998834916940886, "min": 0.20448872, - "max": 83.44426427 + "max": 83.44426427, }, { "from": "btg", @@ -30091,7 +30091,7 @@ const List> fixedRateMarketsJSON = [ "rate": 83.82857142857142, "minerFee": 4.098232985714286, "min": 0.13546036, - "max": 83.37868734 + "max": 83.37868734, }, { "from": "btg", @@ -30099,7 +30099,7 @@ const List> fixedRateMarketsJSON = [ "rate": 22.535008322399996, "minerFee": 0.0037365089279999997, "min": 10.1861435, - "max": 83.33348738 + "max": 83.33348738, }, { "from": "btg", @@ -30107,7 +30107,7 @@ const List> fixedRateMarketsJSON = [ "rate": 2.0135813617482388, "minerFee": 0.0028294202636806936, "min": 0.08909487, - "max": 83.33464012 + "max": 83.33464012, }, { "from": "btg", @@ -30115,7 +30115,7 @@ const List> fixedRateMarketsJSON = [ "rate": 22.535543483999994, "minerFee": 2.2802294764799997, "min": 0.17570083, - "max": 83.41691578 + "max": 83.41691578, }, { "from": "btg", @@ -30123,7 +30123,7 @@ const List> fixedRateMarketsJSON = [ "rate": 184.10954511764703, "minerFee": 23.178051790980394, "min": 0.19717028, - "max": 83.43731176 + "max": 83.43731176, }, { "from": "btg", @@ -30131,7 +30131,7 @@ const List> fixedRateMarketsJSON = [ "rate": 22.534116386399997, "minerFee": 2.616215373008, "min": 0.18866975, - "max": 83.42923625 + "max": 83.42923625, }, { "from": "btg", @@ -30139,7 +30139,7 @@ const List> fixedRateMarketsJSON = [ "rate": 344.1111111111111, "minerFee": 0.0762962962962963, "min": 0.08793615, - "max": 83.33353933 + "max": 83.33353933, }, { "from": "btg", @@ -30147,7 +30147,7 @@ const List> fixedRateMarketsJSON = [ "rate": 4.621101729931549, "minerFee": 0.4417639784629745, "min": 0.17093561, - "max": 83.41238882 + "max": 83.41238882, }, { "from": "btg", @@ -30155,7 +30155,7 @@ const List> fixedRateMarketsJSON = [ "rate": 300.7639445822102, "minerFee": 32.60671772530997, "min": 0.18198388, - "max": 83.42288468 + "max": 83.42288468, }, { "from": "btg", @@ -30163,7 +30163,7 @@ const List> fixedRateMarketsJSON = [ "rate": 1041.981308411215, "minerFee": 77.64060070971962, "min": 0.15251296, - "max": 83.3948873 + "max": 83.3948873, }, { "from": "btg", @@ -30171,7 +30171,7 @@ const List> fixedRateMarketsJSON = [ "rate": 27.866033491627093, "minerFee": 3.484827340284929, "min": 0.19637156, - "max": 83.43655298 + "max": 83.43655298, }, { "from": "btg", @@ -30179,7 +30179,7 @@ const List> fixedRateMarketsJSON = [ "rate": 23.179209979209976, "minerFee": 2.7973248197920997, "min": 0.19260661, - "max": 83.43297627 + "max": 83.43297627, }, { "from": "btg", @@ -30187,7 +30187,7 @@ const List> fixedRateMarketsJSON = [ "rate": 4.283407778445163, "minerFee": 1.0327226920087436, "min": 0.29731381, - "max": 8.53244811 + "max": 8.53244811, }, { "from": "btg", @@ -30195,7 +30195,7 @@ const List> fixedRateMarketsJSON = [ "rate": 17.982528521739127, "minerFee": 4.801996926956522, "min": 0.31988055, - "max": 83.55388651 + "max": 83.55388651, }, { "from": "btg", @@ -30203,7 +30203,7 @@ const List> fixedRateMarketsJSON = [ "rate": 138.6744152238806, "minerFee": 0.12268702089552239, "min": 0.08858457, - "max": 83.33415533 + "max": 83.33415533, }, { "from": "btg", @@ -30211,7 +30211,7 @@ const List> fixedRateMarketsJSON = [ "rate": 22.534172132400002, "minerFee": 3.728100272128, "min": 0.23156118, - "max": 83.46998311 + "max": 83.46998311, }, { "from": "btg", @@ -30219,7 +30219,7 @@ const List> fixedRateMarketsJSON = [ "rate": 1252.7191011235952, "minerFee": 161.49473712022473, "min": 0.1998075, - "max": 83.43981712 + "max": 83.43981712, }, { "from": "btg", @@ -30227,7 +30227,7 @@ const List> fixedRateMarketsJSON = [ "rate": 187.38151260504202, "minerFee": 25.017766722184874, "min": 0.20380376, - "max": 83.44361357 + "max": 83.44361357, }, { "from": "btg", @@ -30235,7 +30235,7 @@ const List> fixedRateMarketsJSON = [ "rate": 208.39626168224297, "minerFee": 19.992346607943926, "min": 0.1711361, - "max": 83.41257929 + "max": 83.41257929, }, { "from": "btg", @@ -30243,7 +30243,7 @@ const List> fixedRateMarketsJSON = [ "rate": 6277.161092590528, "minerFee": 1513.8717569700557, "min": 0.29737728, - "max": 83.53250841 + "max": 83.53250841, }, { "from": "btg", @@ -30251,7 +30251,7 @@ const List> fixedRateMarketsJSON = [ "rate": 193.610890625, "minerFee": 17.569255633333334, "min": 1.60631329, - "max": 83.40829371 + "max": 83.40829371, }, { "from": "btg", @@ -30259,7 +30259,7 @@ const List> fixedRateMarketsJSON = [ "rate": 2933.9999999999995, "minerFee": 368.58142651000003, "min": 0.1969461, - "max": 83.43709878 + "max": 83.43709878, }, { "from": "btg", @@ -30267,7 +30267,7 @@ const List> fixedRateMarketsJSON = [ "rate": 76.46913580246914, "minerFee": 0.013510288065843624, "min": 0.0878921, - "max": 83.33349749 + "max": 83.33349749, }, { "from": "btg", @@ -30275,7 +30275,7 @@ const List> fixedRateMarketsJSON = [ "rate": 229.24728710478126, "minerFee": 5.037504668646999, "min": 0.10920813, - "max": 41.68708105 + "max": 41.68708105, }, { "from": "btg", @@ -30283,7 +30283,7 @@ const List> fixedRateMarketsJSON = [ "rate": 22.535008322399996, "minerFee": 0.453686708928, "min": 0.10740723, - "max": 83.35203685 + "max": 83.35203685, }, { "from": "btg", @@ -30291,7 +30291,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.46281444582814446, "minerFee": 0.06113932606475716, "min": 0.20267429, - "max": 83.44254056 + "max": 83.44254056, }, { "from": "btg", @@ -30299,7 +30299,7 @@ const List> fixedRateMarketsJSON = [ "rate": 884.8571428571428, "minerFee": 93.37321282476191, "min": 0.17947232, - "max": 83.42049869 + "max": 83.42049869, }, { "from": "btg", @@ -30307,7 +30307,7 @@ const List> fixedRateMarketsJSON = [ "rate": 506.7818181818181, "minerFee": 52.515960910909094, "min": 0.17782294, - "max": 83.41893178 + "max": 83.41893178, }, { "from": "btg", @@ -30315,7 +30315,7 @@ const List> fixedRateMarketsJSON = [ "rate": 6.367332952598514, "minerFee": 1.9260347304625927, "min": 0.35174955, - "max": 83.58416206 + "max": 83.58416206, }, { "from": "btg", @@ -30323,7 +30323,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.8354698343649306, "minerFee": 0.0885791221814912, "min": 0.1799284, - "max": 83.42093197 + "max": 83.42093197, }, { "from": "btg", @@ -30331,7 +30331,7 @@ const List> fixedRateMarketsJSON = [ "rate": 22.8335616, "minerFee": 3.9672583620000004, "min": 0.23870754, - "max": 16.81010549 + "max": 16.81010549, }, { "from": "btg", @@ -30339,7 +30339,7 @@ const List> fixedRateMarketsJSON = [ "rate": 3.138851351351351, "minerFee": 0.016413513513513514, "min": 0.09283341, - "max": 83.33819173 + "max": 83.33819173, }, { "from": "btg", @@ -30347,7 +30347,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.4608931852561984, "minerFee": 0.010075401748099174, "min": 2.21766809, - "max": 83.35363967 + "max": 83.35363967, }, { "from": "btg", @@ -30355,7 +30355,7 @@ const List> fixedRateMarketsJSON = [ "rate": 247.21064301552101, "minerFee": 26.831382298980042, "min": 0.18209172, - "max": 83.42298712 + "max": 83.42298712, }, { "from": "btg", @@ -30363,7 +30363,7 @@ const List> fixedRateMarketsJSON = [ "rate": 19.18967297762478, "minerFee": 0.021139414802065402, "min": 0.08879667, - "max": 83.33435683 + "max": 83.33435683, }, { "from": "btg", @@ -30371,7 +30371,7 @@ const List> fixedRateMarketsJSON = [ "rate": 130.5526932084309, "minerFee": 12.39344039381733, "min": 0.17026368, - "max": 83.41175049 + "max": 83.41175049, }, { "from": "btg", @@ -30379,7 +30379,7 @@ const List> fixedRateMarketsJSON = [ "rate": 27.597029702970296, "minerFee": 0.018714851485148516, "min": 0.08838241, - "max": 83.33396328 + "max": 83.33396328, }, { "from": "btg", @@ -30387,7 +30387,7 @@ const List> fixedRateMarketsJSON = [ "rate": 17.982528521739127, "minerFee": 0.005441926956521739, "min": 0.08801527, - "max": 83.3336145 + "max": 83.3336145, }, { "from": "btg", @@ -30395,7 +30395,7 @@ const List> fixedRateMarketsJSON = [ "rate": 3.509348441926345, "minerFee": 0.3667472365344665, "min": 0.17861615, - "max": 83.41968533 + "max": 83.41968533, }, { "from": "btg", @@ -30403,7 +30403,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.0025110810810810807, "minerFee": 0.0002959808108108108, "min": 0.19011354, - "max": 83.43060785 + "max": 83.43060785, }, { "from": "btg", @@ -30411,7 +30411,7 @@ const List> fixedRateMarketsJSON = [ "rate": 496.19390719999996, "minerFee": 5.081176917333333, "min": 0.09773432, - "max": 83.3428476 + "max": 83.3428476, }, { "from": "btg", @@ -30419,7 +30419,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.02820440172021249, "minerFee": 0.0035042742170503416, "min": 0.19577529, - "max": 83.43598652 + "max": 83.43598652, }, { "from": "btg", @@ -30427,7 +30427,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.024726546906187623, "minerFee": 0.002459945242847638, "min": 0.17412814, - "max": 83.41542172 + "max": 83.41542172, }, { "from": "btg", @@ -30435,7 +30435,7 @@ const List> fixedRateMarketsJSON = [ "rate": 20.878651685393255, "minerFee": 2.4370465303370787, "min": 0.18920965, - "max": 83.42974916 + "max": 83.42974916, }, { "from": "btg", @@ -30443,7 +30443,7 @@ const List> fixedRateMarketsJSON = [ "rate": 59.39904102290889, "minerFee": 6.174845094523175, "min": 0.17830277, - "max": 83.41938762 + "max": 83.41938762, }, { "from": "btg", @@ -30451,7 +30451,7 @@ const List> fixedRateMarketsJSON = [ "rate": 28.36946564885496, "minerFee": 4.275159711374045, "min": 0.21907466, - "max": 83.45812092 + "max": 83.45812092, }, { "from": "btg", @@ -30459,7 +30459,7 @@ const List> fixedRateMarketsJSON = [ "rate": 13.750920382230898, "minerFee": 0.022249639326336345, "min": 0.08930041, - "max": 8.33483538 + "max": 8.33483538, }, { "from": "btg", @@ -30467,7 +30467,7 @@ const List> fixedRateMarketsJSON = [ "rate": 796.3714285714286, "minerFee": 50.13028571428571, "min": 0.14928281, - "max": 16.725152 + "max": 16.725152, }, { "from": "btg", @@ -30475,7 +30475,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.0011144742103158737, "minerFee": 0.00014126232706917233, "min": 0.1979233, - "max": 83.43802713 + "max": 83.43802713, }, { "from": "btg", @@ -30483,7 +30483,7 @@ const List> fixedRateMarketsJSON = [ "rate": 3.612832145171743, "minerFee": 0.4919526463836682, "min": 0.20603589, - "max": 83.44573408 + "max": 83.44573408, }, { "from": "btg", @@ -30491,7 +30491,7 @@ const List> fixedRateMarketsJSON = [ "rate": 19.266102637415788, "minerFee": 4.643757508631888, "min": 0.2972747, - "max": 83.53241095 + "max": 83.53241095, }, { "from": "btg", @@ -30499,7 +30499,7 @@ const List> fixedRateMarketsJSON = [ "rate": 3898.7903671972317, "minerFee": 3.637838914878893, "min": 0.08863178, - "max": 8.33420018 + "max": 8.33420018, }, { "from": "btg", @@ -30507,7 +30507,7 @@ const List> fixedRateMarketsJSON = [ "rate": 381.8219178082191, "minerFee": 0.36246575342465753, "min": 0.08864772, - "max": 83.33421533 + "max": 83.33421533, }, { "from": "btg", @@ -30515,7 +30515,7 @@ const List> fixedRateMarketsJSON = [ "rate": 3.818219178082191, "minerFee": 0.0031246575342465752, "min": 0.08851965, - "max": 83.33409366 + "max": 83.33409366, }, { "from": "btg", @@ -30523,7 +30523,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.26596374045801524, "minerFee": 0.11600931145038167, "min": 0.46683669, - "max": 83.69349485 + "max": 83.69349485, }, { "from": "btg", @@ -30531,7 +30531,7 @@ const List> fixedRateMarketsJSON = [ "rate": 1093.0588235294117, "minerFee": 0.2288235294117647, "min": 0.08792404, - "max": 83.33352783 + "max": 83.33352783, }, { "from": "btg", @@ -30539,7 +30539,7 @@ const List> fixedRateMarketsJSON = [ "rate": 404.870747019408, "minerFee": 43.251741243029755, "min": 0.18056873, - "max": 83.42154028 + "max": 83.42154028, }, { "from": "btg", @@ -30547,7 +30547,7 @@ const List> fixedRateMarketsJSON = [ "rate": 1.3869350444572706, "minerFee": 0.16101662143876602, "min": 0.18862802, - "max": 83.42919661 + "max": 83.42919661, }, { "from": "btg", @@ -30555,7 +30555,7 @@ const List> fixedRateMarketsJSON = [ "rate": 95.08442330126582, "minerFee": 0.02180573387341772, "min": 0.08794361, - "max": 83.33354642 + "max": 83.33354642, }, { "from": "btg", @@ -30563,7 +30563,7 @@ const List> fixedRateMarketsJSON = [ "rate": 3737.1489755223874, "minerFee": 413.0672189554229, "min": 0.18397532, - "max": 83.42477654 + "max": 83.42477654, }, { "from": "btg", @@ -30571,7 +30571,7 @@ const List> fixedRateMarketsJSON = [ "rate": 140.24150943396225, "minerFee": 13.650713756226414, "min": 0.17224921, - "max": 83.41363674 + "max": 83.41363674, }, { "from": "btg", @@ -30579,7 +30579,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.4314705882352941, "minerFee": 0.00012058823529411766, "min": 0.0879925, - "max": 83.33359287 + "max": 83.33359287, }, { "from": "btg", @@ -30587,7 +30587,7 @@ const List> fixedRateMarketsJSON = [ "rate": 1.6517333333333333, "minerFee": 0.18282373222222223, "min": 0.18381776, - "max": 83.42462686 + "max": 83.42462686, }, { "from": "btg", @@ -30595,7 +30595,7 @@ const List> fixedRateMarketsJSON = [ "rate": 38.20713660801095, "minerFee": 0.007250656295789113, "min": 0.08790493, - "max": 83.33350968 + "max": 83.33350968, }, { "from": "btg", @@ -30603,7 +30603,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.2544187300242379, "minerFee": 0.026577112696118482, "min": 0.17851233, - "max": 41.75292004 + "max": 41.75292004, }, { "from": "btg", @@ -30611,7 +30611,7 @@ const List> fixedRateMarketsJSON = [ "rate": 16.868962584, "minerFee": 2.03397511848, "min": 0.19251378, - "max": 41.76622141 + "max": 41.76622141, }, { "from": "btg", @@ -30619,7 +30619,7 @@ const List> fixedRateMarketsJSON = [ "rate": 6.083403995128442, "minerFee": 1.4697898999174033, "min": 0.29768826, - "max": 41.86613717 + "max": 41.86613717, }, { "from": "btg", @@ -30627,7 +30627,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.5307810974973711, "minerFee": 0.038952535353373806, "min": 0.15150883, - "max": 41.72726671 + "max": 41.72726671, }, { "from": "btg", @@ -30635,7 +30635,7 @@ const List> fixedRateMarketsJSON = [ "rate": 21.923439825429888, "minerFee": 5.241758686822156, "min": 0.2955048, - "max": 41.86406288 + "max": 41.86406288, }, { "from": "btg", @@ -30643,7 +30643,7 @@ const List> fixedRateMarketsJSON = [ "rate": 7.784757829577479, "minerFee": 0.7385709600130188, "min": 0.17018079, - "max": 41.74500507 + "max": 41.74500507, }, { "from": "btg", @@ -30651,7 +30651,7 @@ const List> fixedRateMarketsJSON = [ "rate": 532.2547783127123, "minerFee": 60.112871576349725, "min": 0.18592609, - "max": 41.75996311 + "max": 41.75996311, }, { "from": "btg", @@ -30659,7 +30659,7 @@ const List> fixedRateMarketsJSON = [ "rate": 41665.08538469991, "minerFee": 4084.9677050170676, "min": 0.17293476, - "max": 41.74762135 + "max": 41.74762135, }, { "from": "btg", @@ -30667,7 +30667,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.6118044872481201, "minerFee": 0.14782887071366022, "min": 0.29770663, - "max": 8.53282129 + "max": 8.53282129, }, { "from": "btg", @@ -30675,7 +30675,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.996192336510844, "minerFee": 0.07963596625137191, "min": 0.15720383, - "max": 41.73267696 + "max": 41.73267696, }, { "from": "btg", @@ -30683,7 +30683,7 @@ const List> fixedRateMarketsJSON = [ "rate": 45.30923006034302, "minerFee": 4.192996272975107, "min": 0.16815405, - "max": 41.74307967 + "max": 41.74307967, }, { "from": "btg", @@ -30691,7 +30691,7 @@ const List> fixedRateMarketsJSON = [ "rate": 7.805272065534172, "minerFee": 0.8042204261252407, "min": 0.1772731, - "max": 41.75174277 + "max": 41.75174277, }, { "from": "btg", @@ -30699,7 +30699,7 @@ const List> fixedRateMarketsJSON = [ "rate": 60316.30294623652, "minerFee": 5047.749709763515, "min": 0.16046023, - "max": 41.73577054 + "max": 41.73577054, }, { "from": "btg", @@ -30707,7 +30707,7 @@ const List> fixedRateMarketsJSON = [ "rate": 70.009326310896, "minerFee": 7.92499520851712, "min": 0.18610492, - "max": 12.59346633 + "max": 12.59346633, }, { "from": "btg", @@ -30715,7 +30715,7 @@ const List> fixedRateMarketsJSON = [ "rate": 131.2824883268124, "minerFee": 33.47929173770173, "min": 0.30934212, - "max": 41.87720834 + "max": 41.87720834, }, { "from": "btg", @@ -30723,7 +30723,7 @@ const List> fixedRateMarketsJSON = [ "rate": 123.11994828693237, "minerFee": 25.595069842828128, "min": 0.26838725, - "max": 41.83830121 + "max": 41.83830121, }, { "from": "btg", @@ -30731,7 +30731,7 @@ const List> fixedRateMarketsJSON = [ "rate": 7.742499999999999, "minerFee": 3.1125799666666665, "min": 0.43721974, - "max": 83.66535875 + "max": 83.66535875, }, { "from": "btg", @@ -30739,7 +30739,7 @@ const List> fixedRateMarketsJSON = [ "rate": 219.46838781925342, "minerFee": 17.069634938722984, "min": 0.15535109, - "max": 83.39758353 + "max": 83.39758353, }, { "from": "btg", @@ -30747,7 +30747,7 @@ const List> fixedRateMarketsJSON = [ "rate": 21.176068376068375, "minerFee": 2.0092964074643875, "min": 0.17025534, - "max": 8.41174256 + "max": 8.41174256, }, { "from": "btg", @@ -30755,7 +30755,7 @@ const List> fixedRateMarketsJSON = [ "rate": 612.5934065934065, "minerFee": 59.01474506021978, "min": 0.1714848, - "max": 12.57957722 + "max": 12.57957722, }, { "from": "btg", @@ -30763,7 +30763,7 @@ const List> fixedRateMarketsJSON = [ "rate": 80.90856313497822, "minerFee": 10.475728664746008, "min": 0.20037674, - "max": 83.44035789 + "max": 83.44035789, }, { "from": "btg", @@ -30771,7 +30771,7 @@ const List> fixedRateMarketsJSON = [ "rate": 45.39679365500407, "minerFee": 10.967275788307568, "min": 0.29768343, - "max": 8.53279925 + "max": 8.53279925, }, { "from": "btg", @@ -30779,7 +30779,7 @@ const List> fixedRateMarketsJSON = [ "rate": 1023.9303787475625, "minerFee": 108.08245980239225, "min": 0.17946355, - "max": 41.7538237 + "max": 41.7538237, }, { "from": "btg", @@ -30787,7 +30787,7 @@ const List> fixedRateMarketsJSON = [ "rate": 845.2681353944778, "minerFee": 204.27068066552465, "min": 0.29773803, - "max": 41.86618445 + "max": 41.86618445, }, { "from": "btg", @@ -30795,7 +30795,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.22476236914651365, "minerFee": 0.02690367093973767, "min": 0.19175252, - "max": 41.76549822 + "max": 41.76549822, }, { "from": "btg", @@ -30803,7 +30803,7 @@ const List> fixedRateMarketsJSON = [ "rate": 91.6875, "minerFee": 22.121816120000002, "min": 0.29748467, - "max": 8.53261043 + "max": 8.53261043, }, { "from": "btg", @@ -30811,7 +30811,7 @@ const List> fixedRateMarketsJSON = [ "rate": 10.699808061420343, "minerFee": 0.0019504798464491364, "min": 0.08789758, - "max": 83.3335027 + "max": 83.3335027, }, { "from": "btg", @@ -30819,7 +30819,7 @@ const List> fixedRateMarketsJSON = [ "rate": 107437.37372137688, "minerFee": 12888.999709777485, "min": 0.19198681, - "max": 41.76572079 + "max": 41.76572079, }, { "from": "btg", @@ -30827,7 +30827,7 @@ const List> fixedRateMarketsJSON = [ "rate": 175.85488958990535, "minerFee": 42.38823344608833, "min": 0.29728203, - "max": 83.53241792 + "max": 83.53241792, }, { "from": "btg", @@ -30835,7 +30835,7 @@ const List> fixedRateMarketsJSON = [ "rate": 12.645703012657657, "minerFee": 1.2157302866687374, "min": 0.17127865, - "max": 41.74604804 + "max": 41.74604804, }, { "from": "btg", @@ -30843,7 +30843,7 @@ const List> fixedRateMarketsJSON = [ "rate": 99.72450805008945, "minerFee": 24.051843297942757, "min": 0.29740539, - "max": 83.53253511 + "max": 83.53253511, }, { "from": "btg", @@ -30851,7 +30851,7 @@ const List> fixedRateMarketsJSON = [ "rate": 972.5725973521215, "minerFee": 231.4808729713664, "min": 0.29456204, - "max": 41.86316726 + "max": 41.86316726, }, { "from": "btg", @@ -30859,7 +30859,7 @@ const List> fixedRateMarketsJSON = [ "rate": 8205.7777524, "minerFee": 1376.043255978, "min": 0.23345847, - "max": 41.80511887 + "max": 41.80511887, }, { "from": "btg", @@ -30867,7 +30867,7 @@ const List> fixedRateMarketsJSON = [ "rate": 23.0498242712425, "minerFee": 2.6009864423961133, "min": 0.18581415, - "max": 83.42652343 + "max": 83.42652343, }, { "from": "btg", @@ -30875,7 +30875,7 @@ const List> fixedRateMarketsJSON = [ "rate": 106.08182683158896, "minerFee": 14.361561920095149, "min": 0.20554102, - "max": 83.44526396 + "max": 83.44526396, }, { "from": "btg", @@ -30883,7 +30883,7 @@ const List> fixedRateMarketsJSON = [ "rate": 63.351092304, "minerFee": 6.15465636688, "min": 0.17216011, - "max": 8.41355209 + "max": 8.41355209, }, { "from": "btg", @@ -30891,7 +30891,7 @@ const List> fixedRateMarketsJSON = [ "rate": 1.1763832478840275, "minerFee": 0.11332311533707713, "min": 0.17144766, - "max": 41.74620861 + "max": 41.74620861, }, { "from": "btg", @@ -30899,7 +30899,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.015022024360411204, "minerFee": 0.003612937590897409, "min": 0.29673517, - "max": 8.53189841 + "max": 8.53189841, }, { "from": "btg", @@ -30907,7 +30907,7 @@ const List> fixedRateMarketsJSON = [ "rate": 5.682568807339449, "minerFee": 0.04301919360856269, "min": 0.09512682, - "max": 83.34037047 + "max": 83.34037047, }, { "from": "btg", @@ -30915,7 +30915,7 @@ const List> fixedRateMarketsJSON = [ "rate": 136.46511627906975, "minerFee": 29.59897541139535, "min": 0.27629352, - "max": 83.51247883 + "max": 83.51247883, }, { "from": "btg", @@ -30923,7 +30923,7 @@ const List> fixedRateMarketsJSON = [ "rate": 464.2112197964683, "minerFee": 112.00700295583582, "min": 0.29740852, - "max": 8.53253808 + "max": 8.53253808, }, { "from": "btg", @@ -30931,7 +30931,7 @@ const List> fixedRateMarketsJSON = [ "rate": 3471.9626303199416, "minerFee": 0.5680102462691111, "min": 0.0878793, - "max": 83.33348533 + "max": 83.33348533, }, { "from": "btg", @@ -30939,7 +30939,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.017095917447858364, "minerFee": 0.002113616878110079, "min": 0.19517191, - "max": 8.4354133 + "max": 8.4354133, }, { "from": "btg", @@ -30947,7 +30947,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.7097640416503936, "minerFee": 0.0011161168166299214, "min": 0.08925667, - "max": 83.33479383 + "max": 83.33479383, }, { "from": "btg", @@ -30955,7 +30955,7 @@ const List> fixedRateMarketsJSON = [ "rate": 158.1446808510638, "minerFee": 12.124159390425532, "min": 0.15428995, - "max": 83.39657544 + "max": 83.39657544, }, { "from": "btg", @@ -30963,7 +30963,7 @@ const List> fixedRateMarketsJSON = [ "rate": 227.55283266759034, "minerFee": 25.64287931728713, "min": 0.18566249, - "max": 53.42637936 + "max": 53.42637936, }, { "from": "btg", @@ -30971,7 +30971,7 @@ const List> fixedRateMarketsJSON = [ "rate": 5633.752080599999, "minerFee": 1361.8708393220002, "min": 0.29786654, - "max": 8.53297321 + "max": 8.53297321, }, { "from": "btg", @@ -30979,7 +30979,7 @@ const List> fixedRateMarketsJSON = [ "rate": 1827193.2965347187, "minerFee": 218035.50092333645, "min": 0.19141369, - "max": 83.43184299 + "max": 83.43184299, }, { "from": "btg", @@ -30987,7 +30987,7 @@ const List> fixedRateMarketsJSON = [ "rate": 3.4336926393594087, "minerFee": 0.001561749307052664, "min": 0.08816395, - "max": 83.33375575 + "max": 83.33375575, }, { "from": "btg", @@ -30995,7 +30995,7 @@ const List> fixedRateMarketsJSON = [ "rate": 47704658766.10275, "minerFee": 101208911269.9956, "min": 0.37427568, - "max": 85.27056189 + "max": 85.27056189, }, { "from": "btg", @@ -31003,7 +31003,7 @@ const List> fixedRateMarketsJSON = [ "rate": 612.5934065934065, "minerFee": 0.2002197802197802, "min": 0.08803895, - "max": 83.333637 + "max": 83.333637, }, { "from": "btg", @@ -31011,7 +31011,7 @@ const List> fixedRateMarketsJSON = [ "rate": 184.89552238805967, "minerFee": 44.6104948562189, "min": 0.29748467, - "max": 83.53261043 + "max": 83.53261043, }, { "from": "btg", @@ -31019,7 +31019,7 @@ const List> fixedRateMarketsJSON = [ "rate": 160.65129682997116, "minerFee": 13.33209866074928, "min": 0.1597774, - "max": 83.40178852 + "max": 83.40178852, }, { "from": "btg", @@ -31027,7 +31027,7 @@ const List> fixedRateMarketsJSON = [ "rate": 80.90856313497822, "minerFee": 0.048236584746008705, "min": 0.08797608, - "max": 83.33357726 + "max": 83.33357726, }, { "from": "btg", @@ -31035,7 +31035,7 @@ const List> fixedRateMarketsJSON = [ "rate": 164.42060350642885, "minerFee": 39.73910784623827, "min": 0.29776237, - "max": 83.53287424 + "max": 83.53287424, }, { "from": "btg", @@ -31043,7 +31043,7 @@ const List> fixedRateMarketsJSON = [ "rate": 131.78723404255317, "minerFee": 12.519967913687944, "min": 0.17032487, - "max": 83.41180861 + "max": 83.41180861, }, { "from": "btg", @@ -31051,7 +31051,7 @@ const List> fixedRateMarketsJSON = [ "rate": 498.7152768934262, "minerFee": 2.0815894113527076, "min": 0.09180146, - "max": 83.33721138 + "max": 83.33721138, }, { "from": "btg", @@ -31059,7 +31059,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.08000287026406429, "minerFee": 0.0003481184041331802, "min": 0.09198019, - "max": 83.33738117 + "max": 83.33738117, }, { "from": "btg", @@ -31067,7 +31067,7 @@ const List> fixedRateMarketsJSON = [ "rate": 89.11854948873689, "minerFee": 0.6111676217977483, "min": 0.0944301, - "max": 3.33970858 + "max": 3.33970858, }, { "from": "btg", @@ -31075,7 +31075,7 @@ const List> fixedRateMarketsJSON = [ "rate": 92.44776119402984, "minerFee": 0.6723596881094528, "min": 0.09483216, - "max": 83.34009054 + "max": 83.34009054, }, { "from": "btg", @@ -31083,7 +31083,7 @@ const List> fixedRateMarketsJSON = [ "rate": 22.534406265599998, "minerFee": 0.244898820432, "min": 0.09834693, - "max": 83.34342957 + "max": 83.34342957, }, { "from": "btg", @@ -31091,7 +31091,7 @@ const List> fixedRateMarketsJSON = [ "rate": 153.07227367150617, "minerFee": 36.342140058760165, "min": 0.2957292, - "max": 16.86427607 + "max": 16.86427607, }, { "from": "btg", @@ -31099,7 +31099,7 @@ const List> fixedRateMarketsJSON = [ "rate": 1.1826880237615356, "minerFee": 0.010193486793253421, "min": 0.09615562, - "max": 83.34134783 + "max": 83.34134783, }, { "from": "btg", @@ -31107,7 +31107,7 @@ const List> fixedRateMarketsJSON = [ "rate": 215.23552123552122, "minerFee": 24.542612145212356, "min": 0.1868645, - "max": 83.42752127 + "max": 83.42752127, }, { "from": "btg", @@ -31115,7 +31115,7 @@ const List> fixedRateMarketsJSON = [ "rate": 0.014632648240018899, "minerFee": 0.0013523938892826208, "min": 0.16468619, - "max": 83.40962433 + "max": 83.40962433, }, { "from": "btg", @@ -31123,7 +31123,7 @@ const List> fixedRateMarketsJSON = [ "rate": 11.819357574472594, "minerFee": 0.011933637230997562, "min": 0.23192767, - "max": 83.33427225 + "max": 83.33427225, }, { "from": "btg", @@ -31131,7 +31131,7 @@ const List> fixedRateMarketsJSON = [ "rate": 23126390.835414965, "minerFee": 1655052.6969984109, "min": 0.15775047, - "max": 12.56652961 + "max": 12.56652961, }, { "from": "btg", @@ -31139,7 +31139,7 @@ const List> fixedRateMarketsJSON = [ "rate": 2839.115528846205, "minerFee": 287.82718733813437, "min": 0.17583358, - "max": 8.41704189 + "max": 8.41704189, }, { "from": "btg", @@ -31147,7 +31147,7 @@ const List> fixedRateMarketsJSON = [ "rate": 22.535543483999994, "minerFee": 0.24498456648, "min": 0.09835134, - "max": 83.34343376 + "max": 83.34343376, }, { "from": "btg", @@ -31155,7 +31155,7 @@ const List> fixedRateMarketsJSON = [ "rate": 26679.282835897662, "minerFee": 195.69592369840862, "min": 0.09489708, - "max": 8.34015221 + "max": 8.34015221, }, { "from": "btg", @@ -31163,7 +31163,7 @@ const List> fixedRateMarketsJSON = [ "rate": 27.86603349162709, "minerFee": 0.010798860284928768, "min": 0.08809814, - "max": 83.33369322 + "max": 83.33369322, }, { "from": "btg", @@ -31171,7 +31171,7 @@ const List> fixedRateMarketsJSON = [ "rate": 3.6662939822426828, "minerFee": 0.05059980269648142, "min": 0.83376746, - "max": 41.67948949 + "max": 41.67948949, }, { "from": "btg", @@ -31179,7 +31179,7 @@ const List> fixedRateMarketsJSON = [ "rate": 5.67331569305923, "minerFee": 0.04632814980663546, "min": 0.09570443, - "max": 8.3409192 + "max": 8.3409192, }, { "from": "btg", @@ -31187,7 +31187,7 @@ const List> fixedRateMarketsJSON = [ "rate": 51299.297206563795, "minerFee": 6560.880224620378, "min": 0.19887458, - "max": 8.43893084 + "max": 8.43893084, }, { "from": "btg", @@ -31195,7 +31195,7 @@ const List> fixedRateMarketsJSON = [ "rate": 2234.966119881007, "minerFee": 19.921217139019387, "min": 0.09644154, - "max": 8.34161945 + "max": 8.34161945, }, { "from": "btg", @@ -31203,7 +31203,7 @@ const List> fixedRateMarketsJSON = [ "rate": 5458.751231330335, "minerFee": 54.51949997620946, "min": 0.09749263, - "max": 8.34261799 + "max": 8.34261799, }, { "from": "btg", @@ -31211,7 +31211,7 @@ const List> fixedRateMarketsJSON = [ "rate": 59.383655060680816, "minerFee": 0.6014325873923404, "min": 0.09763, - "max": 8.34274849 + "max": 8.34274849, }, { "from": "btg", @@ -31219,7 +31219,7 @@ const List> fixedRateMarketsJSON = [ "rate": 20009.571409609012, "minerFee": 199.91039097486447, "min": 0.09749576, - "max": 8.34262096 + "max": 8.34262096, }, { "from": "btg", @@ -31227,7 +31227,7 @@ const List> fixedRateMarketsJSON = [ "rate": 18.444033614780405, "minerFee": 0.182008368812234, "min": 0.09737579, - "max": 8.34250699 + "max": 8.34250699, }, { "from": "btg", @@ -31235,7 +31235,7 @@ const List> fixedRateMarketsJSON = [ "rate": 1806.094478793519, "minerFee": 16.983146009352723, "min": 0.09692086, - "max": 8.34207481 + "max": 8.34207481, }, { "from": "btg", @@ -31243,7 +31243,7 @@ const List> fixedRateMarketsJSON = [ "rate": 54.38559116685567, "minerFee": 0.5737179582277065, "min": 0.09804213, - "max": 83.34314002 + "max": 83.34314002, }, { "from": "btg", @@ -31251,7 +31251,7 @@ const List> fixedRateMarketsJSON = [ "rate": 26453.452494334324, "minerFee": 272.19687938925716, "min": 0.09778827, - "max": 8.34289884 + "max": 8.34289884, }, { "from": "btg", @@ -31259,7 +31259,7 @@ const List> fixedRateMarketsJSON = [ "rate": 84.36554911793039, "minerFee": 0.9320547548250192, "min": 0.09853017, - "max": 8.34360365 + "max": 8.34360365, }, { "from": "btg", @@ -31267,7 +31267,7 @@ const List> fixedRateMarketsJSON = [ "rate": 23.430113978958218, "minerFee": 0.24127752748122017, "min": 0.09779619, - "max": 41.67623971 + "max": 41.67623971, }, { "from": "btg", @@ -31275,7 +31275,7 @@ const List> fixedRateMarketsJSON = [ "rate": 35.98522107076894, "minerFee": 0.26028537273141417, "min": 0.09479726, - "max": 66.67339072 + "max": 66.67339072, }, { "from": "btg", @@ -31283,7 +31283,7 @@ const List> fixedRateMarketsJSON = [ "rate": 57997.80585040937, "minerFee": 453.837920195159, "min": 0.09537655, - "max": 8.34060771 + "max": 8.34060771, }, { "from": "btg", @@ -31291,7 +31291,7 @@ const List> fixedRateMarketsJSON = [ "rate": 271.50612436626506, "minerFee": 2.6064851398554216, "min": 0.09711852, - "max": 8.34226258 + "max": 8.34226258, }, { "from": "btg", @@ -31299,7 +31299,7 @@ const List> fixedRateMarketsJSON = [ "rate": 14.240899220845574, "minerFee": 5.822829629463533, "min": 0.44314537, - "max": 83.67098809 + "max": 83.67098809, }, { "from": "btg", @@ -31307,11 +31307,15 @@ const List> fixedRateMarketsJSON = [ "rate": 1639.5882352941173, "minerFee": 163.23764397411765, "min": 0.17428818, - "max": 83.41557376 + "max": 83.41557376, }, ]; const Map createStandardTransactionResponse = { + "fromAmount": "0.3", + "toAmount": "0.0021936", + "flow": "standard", + "type": "direct", "payinAddress": "85uTiLU3DPHDw8JuinfrLAJPsPw64BnCB8UU95mHhqXsVQrG1XKz3umMwnh468nRn54WWxNzZ79d5RGcESjKPSBGPDtrTRd", "payoutAddress": "bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5", @@ -31321,6 +31325,8 @@ const Map createStandardTransactionResponse = { "refundAddress": "888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H", "refundExtraId": "", + "fromNetwork": "xmr", + "toNetwork": "", + "validUntil": "2019-09-09T14:01:04.921Z", "id": "6d2f9280dacab3", - "amount": 0.0021936 }; diff --git a/test/services/change_now/change_now_test.dart b/test/services/change_now/change_now_test.dart index f922bc3ed7..5cc526d6d3 100644 --- a/test/services/change_now/change_now_test.dart +++ b/test/services/change_now/change_now_test.dart @@ -5,8 +5,8 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; -import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; -import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; +import 'package:stackwallet/models/exchange/change_now/cn_exchange_transaction.dart'; +import 'package:stackwallet/models/exchange/change_now/cn_exchange_transaction_status.dart'; import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; import 'package:stackwallet/networking/http.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; @@ -16,68 +16,119 @@ import 'change_now_test.mocks.dart'; @GenerateMocks([HTTP]) void main() { - group("getAvailableCurrencies", () { - test("getAvailableCurrencies succeeds without options", () async { - final client = MockHTTP(); + const testApiKey = 'testAPIKEY'; + + Uri buildV2Uri(String path, [Map? params]) { + return Uri.https('api.changenow.io', '/v2$path', params); + } + + Map changeNowHeaders([String apiKey = '']) { + return {'Content-Type': 'application/json', 'x-changenow-api-key': apiKey}; + } + + String buildCreateExchangeBody({ + required String fromCurrency, + required String fromNetwork, + required String toCurrency, + required String toNetwork, + required String fromAmount, + required String toAmount, + required String flow, + required String type, + required String address, + String extraId = '', + String refundAddress = '', + String refundExtraId = '', + String userId = '', + String payload = '', + String contactEmail = '', + String rateId = '', + }) { + return jsonEncode({ + 'fromCurrency': fromCurrency, + 'fromNetwork': fromNetwork, + 'toCurrency': toCurrency, + 'toNetwork': toNetwork, + 'fromAmount': fromAmount, + 'toAmount': toAmount, + 'flow': flow, + 'type': type, + 'address': address, + 'extraId': extraId, + 'refundAddress': refundAddress, + 'refundExtraId': refundExtraId, + 'userId': userId, + 'payload': payload, + 'contactEmail': contactEmail, + 'rateId': rateId, + }); + } + group('getAvailableCurrencies', () { + test('getAvailableCurrencies succeeds without options', () async { + final client = MockHTTP(); final instance = ChangeNowAPI(http: client); when( client.get( - url: Uri.parse("https://api.ChangeNow.io/v1/currencies"), - headers: {'Content-Type': 'application/json'}, + url: buildV2Uri('/exchange/currencies', {'flow': 'standard'}), + headers: changeNowHeaders(testApiKey), proxyInfo: null, ), ).thenAnswer( - (realInvocation) async => + (_) async => Response(utf8.encode(jsonEncode(availableCurrenciesJSON)), 200), ); - final result = await instance.getAvailableCurrencies(); + final result = await instance.getAvailableCurrencies(apiKey: testApiKey); expect(result.exception, null); expect(result.value == null, false); expect(result.value!.length, 538); }); - test("getAvailableCurrencies succeeds with active option", () async { + test('getAvailableCurrencies succeeds with active option', () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); when( client.get( - url: Uri.parse("https://api.ChangeNow.io/v1/currencies?active=true"), - headers: {'Content-Type': 'application/json'}, + url: buildV2Uri('/exchange/currencies', { + 'flow': 'standard', + 'active': 'true', + }), + headers: changeNowHeaders(testApiKey), proxyInfo: null, ), ).thenAnswer( - (realInvocation) async => Response( + (_) async => Response( utf8.encode(jsonEncode(availableCurrenciesJSONActive)), 200, ), ); - final result = await instance.getAvailableCurrencies(active: true); + final result = await instance.getAvailableCurrencies( + active: true, + apiKey: testApiKey, + ); expect(result.exception, null); expect(result.value == null, false); expect(result.value!.length, 531); }); - test("getAvailableCurrencies succeeds with fixedRate option", () async { + test('getAvailableCurrencies succeeds with fixedRate option', () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); when( client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/currencies?fixedRate=true", - ), - headers: {'Content-Type': 'application/json'}, + url: buildV2Uri('/exchange/currencies', {'flow': 'fixed-rate'}), + headers: changeNowHeaders(testApiKey), proxyInfo: null, ), ).thenAnswer( - (realInvocation) async => Response( + (_) async => Response( utf8.encode(jsonEncode(availableCurrenciesJSONFixedRate)), 200, ), @@ -85,6 +136,7 @@ void main() { final result = await instance.getAvailableCurrencies( flow: CNFlow.fixedRate, + apiKey: testApiKey, ); expect(result.exception, null); @@ -93,21 +145,22 @@ void main() { }); test( - "getAvailableCurrencies succeeds with fixedRate and active options", + 'getAvailableCurrencies succeeds with fixedRate and active options', () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); when( client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/currencies?fixedRate=true&active=true", - ), - headers: {'Content-Type': 'application/json'}, + url: buildV2Uri('/exchange/currencies', { + 'flow': 'fixed-rate', + 'active': 'true', + }), + headers: changeNowHeaders(testApiKey), proxyInfo: null, ), ).thenAnswer( - (realInvocation) async => Response( + (_) async => Response( utf8.encode(jsonEncode(availableCurrenciesJSONActiveFixedRate)), 200, ), @@ -116,6 +169,7 @@ void main() { final result = await instance.getAvailableCurrencies( active: true, flow: CNFlow.fixedRate, + apiKey: testApiKey, ); expect(result.exception, null); @@ -125,25 +179,27 @@ void main() { ); test( - "getAvailableCurrencies fails with ChangeNowExceptionType.serializeResponseError", + 'getAvailableCurrencies fails with ChangeNowExceptionType.serializeResponseError', () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); when( client.get( - url: Uri.parse("https://api.ChangeNow.io/v1/currencies"), - headers: {'Content-Type': 'application/json'}, + url: buildV2Uri('/exchange/currencies', {'flow': 'standard'}), + headers: changeNowHeaders(testApiKey), proxyInfo: null, ), ).thenAnswer( - (realInvocation) async => Response( + (_) async => Response( utf8.encode('{"some unexpected": "but valid json data"}'), 200, ), ); - final result = await instance.getAvailableCurrencies(); + final result = await instance.getAvailableCurrencies( + apiKey: testApiKey, + ); expect( result.exception!.type, @@ -153,50 +209,48 @@ void main() { }, ); - test("getAvailableCurrencies fails for any other reason", () async { + test('getAvailableCurrencies fails for any other reason', () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); when( client.get( - url: Uri.parse("https://api.ChangeNow.io/v1/currencies"), - headers: {'Content-Type': 'application/json'}, + url: buildV2Uri('/exchange/currencies', {'flow': 'standard'}), + headers: changeNowHeaders(testApiKey), proxyInfo: null, ), - ).thenAnswer((realInvocation) async => Response(utf8.encode(""), 400)); + ).thenAnswer((_) async => Response(utf8.encode(''), 400)); - final result = await instance.getAvailableCurrencies(); + final result = await instance.getAvailableCurrencies(apiKey: testApiKey); - expect( - result.exception!.type, - ExchangeExceptionType.serializeResponseError, - ); + expect(result.exception!.type, ExchangeExceptionType.generic); expect(result.value == null, true); }); }); - group("getMinimalExchangeAmount", () { - test("getMinimalExchangeAmount succeeds", () async { + group('getMinimalExchangeAmount', () { + test('getMinimalExchangeAmount succeeds', () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); when( client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/min-amount/xmr_btc?api_key=testAPIKEY", - ), - headers: {'Content-Type': 'application/json'}, + url: buildV2Uri('/exchange/min-amount', { + 'fromCurrency': 'xmr', + 'toCurrency': 'btc', + 'flow': 'standard', + }), + headers: changeNowHeaders('testAPIKEY'), proxyInfo: null, ), ).thenAnswer( - (realInvocation) async => - Response(utf8.encode('{"minAmount": 42}'), 200), + (_) async => Response(utf8.encode('{"minAmount": 42}'), 200), ); final result = await instance.getMinimalExchangeAmount( - fromCurrency: "xmr", - toCurrency: "btc", - apiKey: "testAPIKEY", + fromCurrency: 'xmr', + toCurrency: 'btc', + apiKey: 'testAPIKEY', ); expect(result.exception, null); @@ -205,27 +259,27 @@ void main() { }); test( - "getMinimalExchangeAmount fails with ChangeNowExceptionType.serializeResponseError", + 'getMinimalExchangeAmount fails with ChangeNowExceptionType.serializeResponseError', () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); when( client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/min-amount/xmr_btc?api_key=testAPIKEY", - ), - headers: {'Content-Type': 'application/json'}, + url: buildV2Uri('/exchange/min-amount', { + 'fromCurrency': 'xmr', + 'toCurrency': 'btc', + 'flow': 'standard', + }), + headers: changeNowHeaders('testAPIKEY'), proxyInfo: null, ), - ).thenAnswer( - (realInvocation) async => Response(utf8.encode('{"error": 42}'), 200), - ); + ).thenAnswer((_) async => Response(utf8.encode('{"error": 42}'), 200)); final result = await instance.getMinimalExchangeAmount( - fromCurrency: "xmr", - toCurrency: "btc", - apiKey: "testAPIKEY", + fromCurrency: 'xmr', + toCurrency: 'btc', + apiKey: 'testAPIKEY', ); expect( @@ -236,61 +290,78 @@ void main() { }, ); - test("getMinimalExchangeAmount fails for any other reason", () async { + test('getMinimalExchangeAmount fails for any other reason', () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); when( client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/min-amount/xmr_btc?api_key=testAPIKEY", - ), - headers: {'Content-Type': 'application/json'}, + url: buildV2Uri('/exchange/min-amount', { + 'fromCurrency': 'xmr', + 'toCurrency': 'btc', + 'flow': 'standard', + }), + headers: changeNowHeaders('testAPIKEY'), proxyInfo: null, ), - ).thenAnswer((realInvocation) async => Response(utf8.encode(''), 400)); + ).thenAnswer((_) async => Response(utf8.encode(''), 400)); final result = await instance.getMinimalExchangeAmount( - fromCurrency: "xmr", - toCurrency: "btc", - apiKey: "testAPIKEY", + fromCurrency: 'xmr', + toCurrency: 'btc', + apiKey: 'testAPIKEY', ); - expect( - result.exception!.type, - ExchangeExceptionType.serializeResponseError, - ); + expect(result.exception!.type, ExchangeExceptionType.generic); expect(result.value == null, true); }); }); - group("getEstimatedExchangeAmount", () { - test("getEstimatedExchangeAmount succeeds", () async { + group('getEstimatedExchangeAmount', () { + test('getEstimatedExchangeAmount succeeds', () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); when( client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/exchange-amount/42/xmr_btc?api_key=testAPIKEY", - ), - headers: {'Content-Type': 'application/json'}, + url: buildV2Uri('/exchange/estimated-amount', { + 'fromCurrency': 'xmr', + 'toCurrency': 'btc', + 'fromAmount': '42', + 'flow': 'standard', + 'type': 'direct', + }), + headers: changeNowHeaders('testAPIKEY'), proxyInfo: null, ), ).thenAnswer( - (realInvocation) async => Response( + (_) async => Response( utf8.encode( - '{"estimatedAmount": 58.4142873, "transactionSpeedForecast": "10-60", "warningMessage": null}', + jsonEncode({ + 'fromCurrency': 'xmr', + 'fromNetwork': 'xmr', + 'toCurrency': 'btc', + 'toNetwork': 'btc', + 'flow': 'standard', + 'type': 'direct', + 'validUntil': '2019-09-09T14:01:04.921Z', + 'transactionSpeedForecast': '10-60', + 'warningMessage': 'Rates may shift while the order is pending.', + 'depositFee': '0', + 'withdrawalFee': '0.0001', + 'fromAmount': '42', + 'toAmount': '58.4142873', + }), ), 200, ), ); final result = await instance.getEstimatedExchangeAmount( - fromCurrency: "xmr", - toCurrency: "btc", + fromCurrency: 'xmr', + toCurrency: 'btc', fromAmount: Decimal.fromInt(42), - apiKey: "testAPIKEY", + apiKey: 'testAPIKEY', ); expect(result.exception, null); @@ -299,28 +370,30 @@ void main() { }); test( - "getEstimatedExchangeAmount fails with ChangeNowExceptionType.serializeResponseError", + 'getEstimatedExchangeAmount fails with ChangeNowExceptionType.serializeResponseError', () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); when( client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/exchange-amount/42/xmr_btc?api_key=testAPIKEY", - ), - headers: {'Content-Type': 'application/json'}, + url: buildV2Uri('/exchange/estimated-amount', { + 'fromCurrency': 'xmr', + 'toCurrency': 'btc', + 'fromAmount': '42', + 'flow': 'standard', + 'type': 'direct', + }), + headers: changeNowHeaders('testAPIKEY'), proxyInfo: null, ), - ).thenAnswer( - (realInvocation) async => Response(utf8.encode('{"error": 42}'), 200), - ); + ).thenAnswer((_) async => Response(utf8.encode('{"error": 42}'), 200)); final result = await instance.getEstimatedExchangeAmount( - fromCurrency: "xmr", - toCurrency: "btc", + fromCurrency: 'xmr', + toCurrency: 'btc', fromAmount: Decimal.fromInt(42), - apiKey: "testAPIKEY", + apiKey: 'testAPIKEY', ); expect( @@ -331,25 +404,29 @@ void main() { }, ); - test("getEstimatedExchangeAmount fails for any other reason", () async { + test('getEstimatedExchangeAmount fails for any other reason', () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); when( client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/exchange-amount/42/xmr_btc?api_key=testAPIKEY", - ), - headers: {'Content-Type': 'application/json'}, + url: buildV2Uri('/exchange/estimated-amount', { + 'fromCurrency': 'xmr', + 'toCurrency': 'btc', + 'fromAmount': '42', + 'flow': 'standard', + 'type': 'direct', + }), + headers: changeNowHeaders('testAPIKEY'), proxyInfo: null, ), - ).thenAnswer((realInvocation) async => Response(utf8.encode(''), 400)); + ).thenAnswer((_) async => Response(utf8.encode(''), 400)); final result = await instance.getEstimatedExchangeAmount( - fromCurrency: "xmr", - toCurrency: "btc", + fromCurrency: 'xmr', + toCurrency: 'btc', fromAmount: Decimal.fromInt(42), - apiKey: "testAPIKEY", + apiKey: 'testAPIKEY', ); expect(result.exception!.type, ExchangeExceptionType.generic); @@ -357,113 +434,46 @@ void main() { }); }); - // group("getEstimatedFixedRateExchangeAmount", () { - // test("getEstimatedFixedRateExchangeAmount succeeds", () async { - // final client = MockHTTP(); - // ChangeNow.instance.client = client; - // - // when(client.get(url: - // Uri.parse( - // "https://api.ChangeNow.io/v1/exchange-amount/fixed-rate/10/xmr_btc?api_key=testAPIKEY&useRateId=true"), - // headers: {'Content-Type': 'application/json'}, - // proxyInfo: null, - // )).thenAnswer((realInvocation) async => - // Response(utf8.encode(jsonEncode(estFixedRateExchangeAmountJSON )), 200)); - // - // final result = - // await ChangeNow.instance.getEstimatedFixedRateExchangeAmount( - // fromCurrency: "xmr", - // toCurrency: "btc", - // fromAmount: Decimal.fromInt(10), - // apiKey: "testAPIKEY", - // ); - // - // expect(result.exception, null); - // expect(result.value == null, false); - // expect(result.value.toString(), - // 'EstimatedExchangeAmount: {estimatedAmount: 0.07271053, transactionSpeedForecast: 10-60, warningMessage: null, rateId: 1t2W5KBPqhycSJVYpaNZzYWLfMr0kSFe, networkFee: 0.00002408}'); - // }); - // - // test( - // "getEstimatedFixedRateExchangeAmount fails with ChangeNowExceptionType.serializeResponseError", - // () async { - // final client = MockHTTP(); - // ChangeNow.instance.client = client; - // - // when(client.get(url: - // Uri.parse( - // "https://api.ChangeNow.io/v1/exchange-amount/fixed-rate/10/xmr_btc?api_key=testAPIKEY&useRateId=true"), - // headers: {'Content-Type': 'application/json'}, - // proxyInfo: null, - // )).thenAnswer((realInvocation) async => Response('{"error": 42}', 200)); - // - // final result = - // await ChangeNow.instance.getEstimatedFixedRateExchangeAmount( - // fromCurrency: "xmr", - // toCurrency: "btc", - // fromAmount: Decimal.fromInt(10), - // apiKey: "testAPIKEY", - // ); - // - // expect(result.exception!.type, - // ChangeNowExceptionType.serializeResponseError); - // expect(result.value == null, true); - // }); - // - // test("getEstimatedFixedRateExchangeAmount fails for any other reason", - // () async { - // final client = MockHTTP(); - // ChangeNow.instance.client = client; - // - // when(client.get(url: - // Uri.parse( - // "https://api.ChangeNow.io/v1/exchange-amount/fixed-rate/10/xmr_btc?api_key=testAPIKEY&useRateId=true"), - // headers: {'Content-Type': 'application/json'}, - // proxyInfo: null, - // )).thenAnswer((realInvocation) async => Response('', 400)); - // - // final result = - // await ChangeNow.instance.getEstimatedFixedRateExchangeAmount( - // fromCurrency: "xmr", - // toCurrency: "btc", - // fromAmount: Decimal.fromInt(10), - // apiKey: "testAPIKEY", - // ); - // - // expect(result.exception!.type, ChangeNowExceptionType.generic); - // expect(result.value == null, true); - // }); - // }); - - group("createExchangeTransaction", () { - test("createExchangeTransaction succeeds", () async { + group('createExchangeTransaction standard flow', () { + test('createExchangeTransaction succeeds', () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); when( client.post( - url: Uri.parse("https://api.ChangeNow.io/v1/transactions/testAPIKEY"), - headers: {'Content-Type': 'application/json'}, + url: buildV2Uri('/exchange'), + headers: changeNowHeaders('testAPIKEY'), proxyInfo: null, - body: - '{"from":"xmr","to":"btc","address":"bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5","amount":"0.3","flow":"standard","extraId":"","userId":"","contactEmail":"","refundAddress":"888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H","refundExtraId":""}', + body: buildCreateExchangeBody( + fromCurrency: 'xmr', + fromNetwork: 'xmr', + toCurrency: 'btc', + toNetwork: '', + fromAmount: '0.3', + toAmount: '', + flow: 'standard', + type: 'direct', + address: 'bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5', + refundAddress: + '888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H', + ), encoding: null, ), ).thenAnswer( - (realInvocation) async => Response( + (_) async => Response( utf8.encode(jsonEncode(createStandardTransactionResponse)), 200, ), ); final result = await instance.createExchangeTransaction( - fromCurrency: "xmr", - toCurrency: "btc", - address: "bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5", - fromAmount: Decimal.parse("0.3"), + fromCurrency: 'xmr', + toCurrency: 'btc', + address: 'bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5', + fromAmount: Decimal.parse('0.3'), refundAddress: - "888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H", - apiKey: "testAPIKEY", + '888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H', + apiKey: 'testAPIKEY', fromNetwork: 'xmr', toNetwork: '', rateId: '', @@ -471,38 +481,45 @@ void main() { expect(result.exception, null); expect(result.value == null, false); - expect(result.value, isA()); + expect(result.value, isA()); }); test( - "createExchangeTransaction fails with ChangeNowExceptionType.serializeResponseError", + 'createExchangeTransaction fails with ChangeNowExceptionType.serializeResponseError', () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); when( client.post( - url: Uri.parse( - "https://api.ChangeNow.io/v1/transactions/testAPIKEY", - ), - headers: {'Content-Type': 'application/json'}, + url: buildV2Uri('/exchange'), + headers: changeNowHeaders('testAPIKEY'), proxyInfo: null, - body: - '{"from":"xmr","to":"btc","address":"bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5","amount":"0.3","flow":"standard","extraId":"","userId":"","contactEmail":"","refundAddress":"888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H","refundExtraId":""}', + body: buildCreateExchangeBody( + fromCurrency: 'xmr', + fromNetwork: 'xmr', + toCurrency: 'btc', + toNetwork: '', + fromAmount: '0.3', + toAmount: '', + flow: 'standard', + type: 'direct', + address: 'bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5', + refundAddress: + '888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H', + ), encoding: null, ), - ).thenAnswer( - (realInvocation) async => Response(utf8.encode('{"error": 42}'), 200), - ); + ).thenAnswer((_) async => Response(utf8.encode('{"error": 42}'), 200)); final result = await instance.createExchangeTransaction( - fromCurrency: "xmr", - toCurrency: "btc", - address: "bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5", - fromAmount: Decimal.parse("0.3"), + fromCurrency: 'xmr', + toCurrency: 'btc', + address: 'bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5', + fromAmount: Decimal.parse('0.3'), refundAddress: - "888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H", - apiKey: "testAPIKEY", + '888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H', + apiKey: 'testAPIKEY', fromNetwork: 'xmr', toNetwork: '', rateId: '', @@ -516,29 +533,40 @@ void main() { }, ); - test("createExchangeTransaction fails for any other reason", () async { + test('createExchangeTransaction fails for any other reason', () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); when( client.post( - url: Uri.parse("https://api.ChangeNow.io/v1/transactions/testAPIKEY"), - headers: {'Content-Type': 'application/json'}, + url: buildV2Uri('/exchange'), + headers: changeNowHeaders('testAPIKEY'), proxyInfo: null, - body: - '{"from":"xmr","to":"btc","address":"bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5","amount":"0.3","flow":"standard","extraId":"","userId":"","contactEmail":"","refundAddress":"888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H","refundExtraId":""}', + body: buildCreateExchangeBody( + fromCurrency: 'xmr', + fromNetwork: 'xmr', + toCurrency: 'btc', + toNetwork: '', + fromAmount: '0.3', + toAmount: '', + flow: 'standard', + type: 'direct', + address: 'bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5', + refundAddress: + '888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H', + ), encoding: null, ), - ).thenAnswer((realInvocation) async => Response(utf8.encode(''), 400)); + ).thenAnswer((_) async => Response(utf8.encode(''), 400)); final result = await instance.createExchangeTransaction( - fromCurrency: "xmr", - toCurrency: "btc", - address: "bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5", - fromAmount: Decimal.parse("0.3"), + fromCurrency: 'xmr', + toCurrency: 'btc', + address: 'bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5', + fromAmount: Decimal.parse('0.3'), refundAddress: - "888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H", - apiKey: "testAPIKEY", + '888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H', + apiKey: 'testAPIKEY', fromNetwork: 'xmr', toNetwork: '', rateId: '', @@ -549,43 +577,63 @@ void main() { }); }); - group("createExchangeTransaction", () { - test("createExchangeTransaction succeeds", () async { + group('createExchangeTransaction fixed-rate flow', () { + test('createExchangeTransaction succeeds', () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); when( client.post( - url: Uri.parse( - "https://api.ChangeNow.io/v1/transactions/fixed-rate/testAPIKEY", - ), - headers: {'Content-Type': 'application/json'}, + url: buildV2Uri('/exchange'), + headers: changeNowHeaders('testAPIKEY'), proxyInfo: null, - body: - '{"from":"btc","to":"eth","address":"0x57f31ad4b64095347F87eDB1675566DAfF5EC886","flow":"fixed-rate","extraId":"","userId":"","contactEmail":"","refundAddress":"","refundExtraId":"","rateId":"","amount":"0.3"}', + body: buildCreateExchangeBody( + fromCurrency: 'btc', + fromNetwork: 'xmr', + toCurrency: 'eth', + toNetwork: '', + fromAmount: '0.3', + toAmount: '', + flow: 'fixed-rate', + type: 'direct', + address: '0x57f31ad4b64095347F87eDB1675566DAfF5EC886', + ), encoding: null, ), ).thenAnswer( - (realInvocation) async => Response( + (_) async => Response( utf8.encode( - '{"payinAddress": "33eFX2jfeWbXMSmRe9ewUUTrmSVSxZi5cj", "payoutAddress":' - ' "0x57f31ad4b64095347F87eDB1675566DAfF5EC886","payoutExtraId": "",' - ' "fromCurrency": "btc", "toCurrency": "eth", "refundAddress": "",' - '"refundExtraId": "","validUntil": "2019-09-09T14:01:04.921Z","id":' - ' "a5c73e2603f40d","amount": 62.9737711}', + jsonEncode({ + 'fromAmount': '0.3', + 'toAmount': '62.9737711', + 'flow': 'fixed-rate', + 'type': 'direct', + 'payinAddress': '33eFX2jfeWbXMSmRe9ewUUTrmSVSxZi5cj', + 'payoutAddress': '0x57f31ad4b64095347F87eDB1675566DAfF5EC886', + 'payoutExtraId': '', + 'fromCurrency': 'btc', + 'toCurrency': 'eth', + 'refundAddress': '', + 'refundExtraId': '', + 'fromNetwork': 'xmr', + 'toNetwork': '', + 'validUntil': '2019-09-09T14:01:04.921Z', + 'id': 'a5c73e2603f40d', + }), ), 200, ), ); final result = await instance.createExchangeTransaction( - fromCurrency: "btc", - toCurrency: "eth", - address: "0x57f31ad4b64095347F87eDB1675566DAfF5EC886", - fromAmount: Decimal.parse("0.3"), - refundAddress: "", - apiKey: "testAPIKEY", + fromCurrency: 'btc', + toCurrency: 'eth', + address: '0x57f31ad4b64095347F87eDB1675566DAfF5EC886', + fromAmount: Decimal.parse('0.3'), + refundAddress: '', + apiKey: 'testAPIKEY', rateId: '', + flow: CNFlow.fixedRate, type: CNExchangeType.direct, fromNetwork: 'xmr', toNetwork: '', @@ -593,77 +641,98 @@ void main() { expect(result.exception, null); expect(result.value == null, false); - expect(result.value, isA()); + expect(result.value, isA()); }); test( - "createExchangeTransaction fails with ChangeNowExceptionType.serializeResponseError", + 'createExchangeTransaction fails with ChangeNowExceptionType.serializeResponseError', () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); when( client.post( - url: Uri.parse( - "https://api.ChangeNow.io/v1/transactions/fixed-rate/testAPIKEY", - ), - headers: {'Content-Type': 'application/json'}, + url: buildV2Uri('/exchange'), + headers: changeNowHeaders('testAPIKEY'), proxyInfo: null, - body: - '{"from":"btc","to":"eth","address":"0x57f31ad4b64095347F87eDB1675566DAfF5EC886","amount":"0.3","flow":"fixed-rate","extraId":"","userId":"","contactEmail":"","refundAddress":"","refundExtraId":"","rateId":""}', + body: buildCreateExchangeBody( + fromCurrency: 'btc', + fromNetwork: 'xmr', + toCurrency: 'eth', + toNetwork: '', + fromAmount: '0.3', + toAmount: '', + flow: 'fixed-rate', + type: 'direct', + address: '0x57f31ad4b64095347F87eDB1675566DAfF5EC886', + ), encoding: null, ), ).thenAnswer( - (realInvocation) async => Response( - utf8.encode('{"id": "a5c73e2603f40d","amount": 62.9737711}'), + (_) async => Response( + utf8.encode('{"id": "a5c73e2603f40d", "amount": 62.9737711}'), 200, ), ); final result = await instance.createExchangeTransaction( - fromCurrency: "btc", - toCurrency: "eth", - address: "0x57f31ad4b64095347F87eDB1675566DAfF5EC886", - fromAmount: Decimal.parse("0.3"), - refundAddress: "", - apiKey: "testAPIKEY", + fromCurrency: 'btc', + toCurrency: 'eth', + address: '0x57f31ad4b64095347F87eDB1675566DAfF5EC886', + fromAmount: Decimal.parse('0.3'), + refundAddress: '', + apiKey: 'testAPIKEY', rateId: '', + flow: CNFlow.fixedRate, type: CNExchangeType.direct, fromNetwork: 'xmr', toNetwork: '', ); - expect(result.exception!.type, ExchangeExceptionType.generic); + expect( + result.exception!.type, + ExchangeExceptionType.serializeResponseError, + ); expect(result.value == null, true); }, ); - test("createExchangeTransaction fails for any other reason", () async { + test('createExchangeTransaction fails for any other reason', () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); when( client.post( - url: Uri.parse( - "https://api.ChangeNow.io/v1/transactions/fixed-rate/testAPIKEY", - ), - headers: {'Content-Type': 'application/json'}, + url: buildV2Uri('/exchange'), + headers: changeNowHeaders('testAPIKEY'), proxyInfo: null, - body: - '{"from": "btc","to": "eth","address": "0x57f31ad4b64095347F87eDB1675566DAfF5EC886", "amount": "1.12345","extraId": "", "userId": "","contactEmail": "","refundAddress": "", "refundExtraId": "", "rateId": "" }', + body: buildCreateExchangeBody( + fromCurrency: 'xmr', + fromNetwork: 'xmr', + toCurrency: 'btc', + toNetwork: '', + fromAmount: '0.3', + toAmount: '', + flow: 'fixed-rate', + type: 'direct', + address: 'bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5', + refundAddress: + '888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H', + ), encoding: null, ), - ).thenAnswer((realInvocation) async => Response(utf8.encode(''), 400)); + ).thenAnswer((_) async => Response(utf8.encode(''), 400)); final result = await instance.createExchangeTransaction( - fromCurrency: "xmr", - toCurrency: "btc", - address: "bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5", - fromAmount: Decimal.parse("0.3"), + fromCurrency: 'xmr', + toCurrency: 'btc', + address: 'bc1qu58svs9983e2vuyqh7gq7ratf8k5qehz5k0cn5', + fromAmount: Decimal.parse('0.3'), refundAddress: - "888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H", - apiKey: "testAPIKEY", + '888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H', + apiKey: 'testAPIKEY', rateId: '', + flow: CNFlow.fixedRate, type: CNExchangeType.direct, fromNetwork: 'xmr', toNetwork: '', @@ -674,64 +743,73 @@ void main() { }); }); - group("getTransactionStatus", () { - test("getTransactionStatus succeeds", () async { + group('getTransactionStatus', () { + test('getTransactionStatus succeeds', () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); when( client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/transactions/47F87eDB1675566DAfF5EC886/testAPIKEY", - ), - headers: {'Content-Type': 'application/json'}, + url: buildV2Uri('/exchange/by-id', { + 'id': '47F87eDB1675566DAfF5EC886', + }), + headers: changeNowHeaders('testAPIKEY'), proxyInfo: null, ), ).thenAnswer( - (realInvocation) async => Response( + (_) async => Response( utf8.encode( - '{"status": "waiting", "payinAddress": "32Ge2ci26rj1sRGw2NjiQa9L7Xvxtgzhrj", ' - '"payoutAddress": "0x57f31ad4b64095347F87eDB1675566DAfF5EC886", ' - '"fromCurrency": "btc", "toCurrency": "eth", "id": "50727663e5d9a4", ' - '"updatedAt": "2019-08-22T14:47:49.943Z", "expectedSendAmount": 1, ' - '"expectedReceiveAmount": 52.31667, "createdAt": "2019-08-22T14:47:49.943Z",' - ' "isPartner": false}', + jsonEncode({ + 'status': 'waiting', + 'id': '50727663e5d9a4', + 'actionsAvailable': false, + 'fromCurrency': 'btc', + 'fromNetwork': 'btc', + 'toCurrency': 'eth', + 'toNetwork': 'eth', + 'expectedAmountFrom': '1', + 'expectedAmountTo': '52.31667', + 'payinAddress': '32Ge2ci26rj1sRGw2NjiQa9L7Xvxtgzhrj', + 'payoutAddress': '0x57f31ad4b64095347F87eDB1675566DAfF5EC886', + 'createdAt': '2019-08-22T14:47:49.943Z', + 'updatedAt': '2019-08-22T14:47:49.943Z', + 'fromLegacyTicker': 'btc', + 'toLegacyTicker': 'eth', + }), ), 200, ), ); final result = await instance.getTransactionStatus( - id: "47F87eDB1675566DAfF5EC886", - apiKey: "testAPIKEY", + id: '47F87eDB1675566DAfF5EC886', + apiKey: 'testAPIKEY', ); expect(result.exception, null); expect(result.value == null, false); - expect(result.value, isA()); + expect(result.value, isA()); }); test( - "getTransactionStatus fails with ChangeNowExceptionType.serializeResponseError", + 'getTransactionStatus fails with ChangeNowExceptionType.serializeResponseError', () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); when( client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/transactions/47F87eDB1675566DAfF5EC886/testAPIKEY", - ), - headers: {'Content-Type': 'application/json'}, + url: buildV2Uri('/exchange/by-id', { + 'id': '47F87eDB1675566DAfF5EC886', + }), + headers: changeNowHeaders('testAPIKEY'), proxyInfo: null, ), - ).thenAnswer( - (realInvocation) async => Response(utf8.encode('{"error": 42}'), 200), - ); + ).thenAnswer((_) async => Response(utf8.encode('{"error": 42}'), 200)); final result = await instance.getTransactionStatus( - id: "47F87eDB1675566DAfF5EC886", - apiKey: "testAPIKEY", + id: '47F87eDB1675566DAfF5EC886', + apiKey: 'testAPIKEY', ); expect( @@ -742,29 +820,26 @@ void main() { }, ); - test("getTransactionStatus fails for any other reason", () async { + test('getTransactionStatus fails for any other reason', () async { final client = MockHTTP(); final instance = ChangeNowAPI(http: client); when( client.get( - url: Uri.parse( - "https://api.ChangeNow.io/v1/transactions/47F87eDB1675566DAfF5EC886/testAPIKEY", - ), - headers: {'Content-Type': 'application/json'}, + url: buildV2Uri('/exchange/by-id', { + 'id': '47F87eDB1675566DAfF5EC886', + }), + headers: changeNowHeaders('testAPIKEY'), proxyInfo: null, ), - ).thenAnswer((realInvocation) async => Response(utf8.encode(''), 400)); + ).thenAnswer((_) async => Response(utf8.encode(''), 400)); final result = await instance.getTransactionStatus( - id: "47F87eDB1675566DAfF5EC886", - apiKey: "testAPIKEY", + id: '47F87eDB1675566DAfF5EC886', + apiKey: 'testAPIKEY', ); - expect( - result.exception!.type, - ExchangeExceptionType.serializeResponseError, - ); + expect(result.exception!.type, ExchangeExceptionType.generic); expect(result.value == null, true); }); }); diff --git a/test/services/change_now/change_now_test.mocks.dart b/test/services/change_now/change_now_test.mocks.dart index 4b92e7841a..96aeaf72e4 100644 --- a/test/services/change_now/change_now_test.mocks.dart +++ b/test/services/change_now/change_now_test.mocks.dart @@ -43,12 +43,14 @@ class MockHTTP extends _i1.Mock implements _i2.HTTP { required Uri? url, Map? headers, required ({_i4.InternetAddress host, int port})? proxyInfo, + Duration? connectionTimeout, }) => (super.noSuchMethod( Invocation.method(#get, [], { #url: url, #headers: headers, #proxyInfo: proxyInfo, + #connectionTimeout: connectionTimeout, }), returnValue: _i3.Future<_i2.Response>.value( _FakeResponse_0( @@ -57,6 +59,7 @@ class MockHTTP extends _i1.Mock implements _i2.HTTP { #url: url, #headers: headers, #proxyInfo: proxyInfo, + #connectionTimeout: connectionTimeout, }), ), ), @@ -93,4 +96,57 @@ class MockHTTP extends _i1.Mock implements _i2.HTTP { ), ) as _i3.Future<_i2.Response>); + + @override + _i3.Future<_i2.Response> patch({ + required Uri? url, + Map? headers, + Object? body, + required ({_i4.InternetAddress host, int port})? proxyInfo, + }) => + (super.noSuchMethod( + Invocation.method(#patch, [], { + #url: url, + #headers: headers, + #body: body, + #proxyInfo: proxyInfo, + }), + returnValue: _i3.Future<_i2.Response>.value( + _FakeResponse_0( + this, + Invocation.method(#patch, [], { + #url: url, + #headers: headers, + #body: body, + #proxyInfo: proxyInfo, + }), + ), + ), + ) + as _i3.Future<_i2.Response>); + + @override + _i3.Future<_i2.Response> delete({ + required Uri? url, + Map? headers, + required ({_i4.InternetAddress host, int port})? proxyInfo, + }) => + (super.noSuchMethod( + Invocation.method(#delete, [], { + #url: url, + #headers: headers, + #proxyInfo: proxyInfo, + }), + returnValue: _i3.Future<_i2.Response>.value( + _FakeResponse_0( + this, + Invocation.method(#delete, [], { + #url: url, + #headers: headers, + #proxyInfo: proxyInfo, + }), + ), + ), + ) + as _i3.Future<_i2.Response>); } diff --git a/test/services/coins/bitcoin/bitcoin_wallet_test.mocks.dart b/test/services/coins/bitcoin/bitcoin_wallet_test.mocks.dart index 7d1f9507c8..b9d265e2f8 100644 --- a/test/services/coins/bitcoin/bitcoin_wallet_test.mocks.dart +++ b/test/services/coins/bitcoin/bitcoin_wallet_test.mocks.dart @@ -328,6 +328,22 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { ) as _i8.Future>); + @override + _i8.Future>> getBatchTransactions({ + required List? txHashes, + String? requestID, + }) => + (super.noSuchMethod( + Invocation.method(#getBatchTransactions, [], { + #txHashes: txHashes, + #requestID: requestID, + }), + returnValue: _i8.Future>>.value( + >[], + ), + ) + as _i8.Future>>); + @override _i8.Future> getLelantusAnonymitySet({ String? groupId = '1', @@ -661,6 +677,22 @@ class MockCachedElectrumXClient extends _i1.Mock ) as _i8.Future>); + @override + _i8.Future>> getBatchTransactions({ + required List? txHashes, + required _i2.CryptoCurrency? cryptoCurrency, + }) => + (super.noSuchMethod( + Invocation.method(#getBatchTransactions, [], { + #txHashes: txHashes, + #cryptoCurrency: cryptoCurrency, + }), + returnValue: _i8.Future>>.value( + >[], + ), + ) + as _i8.Future>>); + @override _i8.Future clearSharedTransactionCache({ required _i2.CryptoCurrency? cryptoCurrency, diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart index a4e641ec33..a91186a54f 100644 --- a/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart +++ b/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart @@ -328,6 +328,22 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { ) as _i8.Future>); + @override + _i8.Future>> getBatchTransactions({ + required List? txHashes, + String? requestID, + }) => + (super.noSuchMethod( + Invocation.method(#getBatchTransactions, [], { + #txHashes: txHashes, + #requestID: requestID, + }), + returnValue: _i8.Future>>.value( + >[], + ), + ) + as _i8.Future>>); + @override _i8.Future> getLelantusAnonymitySet({ String? groupId = '1', @@ -661,6 +677,22 @@ class MockCachedElectrumXClient extends _i1.Mock ) as _i8.Future>); + @override + _i8.Future>> getBatchTransactions({ + required List? txHashes, + required _i2.CryptoCurrency? cryptoCurrency, + }) => + (super.noSuchMethod( + Invocation.method(#getBatchTransactions, [], { + #txHashes: txHashes, + #cryptoCurrency: cryptoCurrency, + }), + returnValue: _i8.Future>>.value( + >[], + ), + ) + as _i8.Future>>); + @override _i8.Future clearSharedTransactionCache({ required _i2.CryptoCurrency? cryptoCurrency, diff --git a/test/services/coins/dogecoin/dogecoin_wallet_test.mocks.dart b/test/services/coins/dogecoin/dogecoin_wallet_test.mocks.dart index cf2da0eb7f..8fde902450 100644 --- a/test/services/coins/dogecoin/dogecoin_wallet_test.mocks.dart +++ b/test/services/coins/dogecoin/dogecoin_wallet_test.mocks.dart @@ -328,6 +328,22 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { ) as _i8.Future>); + @override + _i8.Future>> getBatchTransactions({ + required List? txHashes, + String? requestID, + }) => + (super.noSuchMethod( + Invocation.method(#getBatchTransactions, [], { + #txHashes: txHashes, + #requestID: requestID, + }), + returnValue: _i8.Future>>.value( + >[], + ), + ) + as _i8.Future>>); + @override _i8.Future> getLelantusAnonymitySet({ String? groupId = '1', @@ -661,6 +677,22 @@ class MockCachedElectrumXClient extends _i1.Mock ) as _i8.Future>); + @override + _i8.Future>> getBatchTransactions({ + required List? txHashes, + required _i2.CryptoCurrency? cryptoCurrency, + }) => + (super.noSuchMethod( + Invocation.method(#getBatchTransactions, [], { + #txHashes: txHashes, + #cryptoCurrency: cryptoCurrency, + }), + returnValue: _i8.Future>>.value( + >[], + ), + ) + as _i8.Future>>); + @override _i8.Future clearSharedTransactionCache({ required _i2.CryptoCurrency? cryptoCurrency, diff --git a/test/services/coins/firo/firo_wallet_test.dart b/test/services/coins/firo/firo_wallet_test.dart index 3edbd52f11..22ef37197f 100644 --- a/test/services/coins/firo/firo_wallet_test.dart +++ b/test/services/coins/firo/firo_wallet_test.dart @@ -1,8 +1,9 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hive_ce/hive.dart'; -import 'package:hive_test/hive_test.dart'; import 'package:mockito/annotations.dart'; +import '../../../hive/hive_ce_test_utils.dart'; + @GenerateMocks([ // ElectrumXClient, // CachedElectrumXClient, @@ -329,7 +330,7 @@ void main() { const testWalletName = "Test Wallet"; setUp(() async { - await setUpTestHive(); + await setUpHiveCeTest(); final wallets = await Hive.openBox('wallets'); await wallets.put('currentWalletName', testWalletName); @@ -3015,7 +3016,7 @@ void main() { // }); // tearDown(() async { - await tearDownTestHive(); + await tearDownHiveCeTest(); }); }); diff --git a/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart b/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart index 103ca6b1d4..9ecb591912 100644 --- a/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart +++ b/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart @@ -328,6 +328,22 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { ) as _i8.Future>); + @override + _i8.Future>> getBatchTransactions({ + required List? txHashes, + String? requestID, + }) => + (super.noSuchMethod( + Invocation.method(#getBatchTransactions, [], { + #txHashes: txHashes, + #requestID: requestID, + }), + returnValue: _i8.Future>>.value( + >[], + ), + ) + as _i8.Future>>); + @override _i8.Future> getLelantusAnonymitySet({ String? groupId = '1', @@ -661,6 +677,22 @@ class MockCachedElectrumXClient extends _i1.Mock ) as _i8.Future>); + @override + _i8.Future>> getBatchTransactions({ + required List? txHashes, + required _i2.CryptoCurrency? cryptoCurrency, + }) => + (super.noSuchMethod( + Invocation.method(#getBatchTransactions, [], { + #txHashes: txHashes, + #cryptoCurrency: cryptoCurrency, + }), + returnValue: _i8.Future>>.value( + >[], + ), + ) + as _i8.Future>>); + @override _i8.Future clearSharedTransactionCache({ required _i2.CryptoCurrency? cryptoCurrency, diff --git a/test/services/coins/particl/particl_wallet_test.mocks.dart b/test/services/coins/particl/particl_wallet_test.mocks.dart index 8c10019e4a..6929d60a42 100644 --- a/test/services/coins/particl/particl_wallet_test.mocks.dart +++ b/test/services/coins/particl/particl_wallet_test.mocks.dart @@ -328,6 +328,22 @@ class MockElectrumXClient extends _i1.Mock implements _i5.ElectrumXClient { ) as _i8.Future>); + @override + _i8.Future>> getBatchTransactions({ + required List? txHashes, + String? requestID, + }) => + (super.noSuchMethod( + Invocation.method(#getBatchTransactions, [], { + #txHashes: txHashes, + #requestID: requestID, + }), + returnValue: _i8.Future>>.value( + >[], + ), + ) + as _i8.Future>>); + @override _i8.Future> getLelantusAnonymitySet({ String? groupId = '1', @@ -661,6 +677,22 @@ class MockCachedElectrumXClient extends _i1.Mock ) as _i8.Future>); + @override + _i8.Future>> getBatchTransactions({ + required List? txHashes, + required _i2.CryptoCurrency? cryptoCurrency, + }) => + (super.noSuchMethod( + Invocation.method(#getBatchTransactions, [], { + #txHashes: txHashes, + #cryptoCurrency: cryptoCurrency, + }), + returnValue: _i8.Future>>.value( + >[], + ), + ) + as _i8.Future>>); + @override _i8.Future clearSharedTransactionCache({ required _i2.CryptoCurrency? cryptoCurrency, diff --git a/test/services/node_service_test.dart b/test/services/node_service_test.dart index efae567278..447cffb69f 100644 --- a/test/services/node_service_test.dart +++ b/test/services/node_service_test.dart @@ -1,8 +1,6 @@ // TODO MWC import 'package:flutter_test/flutter_test.dart'; -import 'package:hive_ce/hive.dart'; -import 'package:hive_test/hive_test.dart'; import 'package:stackwallet/app_config.dart'; import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/models/node_model.dart'; @@ -10,16 +8,26 @@ import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart'; +import '../hive/hive_ce_test_utils.dart'; + void main() { bool wasRegistered = false; + final expectedPrimaryDefaults = AppConfig.coins + .where((coin) => coin.identifier != 'firo') + .map((e) => e.defaultNode(isPrimary: true)) + .toList(growable: false); + final expectedDefaultNodeCount = + expectedPrimaryDefaults.length + + (AppConfig.coins.any((e) => e.identifier == 'firo') ? 4 : 0); + setUp(() async { - await setUpTestHive(); + await setUpHiveCeTest(); if (!wasRegistered) { wasRegistered = true; - Hive.registerAdapter(NodeModelAdapter()); + DB.instance.hive.registerAdapter(NodeModelAdapter()); } - await Hive.openBox(DB.boxNameNodeModels); - // await Hive.openBox(DB.boxNamePrimaryNodes); + await DB.instance.hive.openBox(DB.boxNameNodeModels); + // await DB.instance.hive.openBox(DB.boxNamePrimaryNodes); }); group("Empty nodes DB tests", () { @@ -115,10 +123,7 @@ void main() { final fakeStore = FakeSecureStorage(); final service = NodeService(secureStorageInterface: fakeStore); await service.updateDefaults(); - expect( - service.nodes.length, - AppConfig.coins.map((e) => e.defaultNode).length, - ); + expect(service.nodes.length, expectedDefaultNodeCount); expect(fakeStore.interactions, 0); }); }); @@ -177,10 +182,12 @@ void main() { final fakeStore = FakeSecureStorage(); final service = NodeService(secureStorageInterface: fakeStore); expect( - service.getPrimaryNodeFor( - currency: Bitcoin(CryptoCurrencyNetwork.main), - ), - null, + service + .getPrimaryNodeFor(currency: Bitcoin(CryptoCurrencyNetwork.main)) + ?.toString(), + Bitcoin( + CryptoCurrencyNetwork.main, + ).defaultNode(isPrimary: true).toString(), ); await service.setPrimaryNodeFor( coin: Bitcoin(CryptoCurrencyNetwork.main), @@ -190,7 +197,9 @@ void main() { service .getPrimaryNodeFor(currency: Bitcoin(CryptoCurrencyNetwork.main)) .toString(), - Bitcoin(CryptoCurrencyNetwork.main).defaultNode.toString(), + Bitcoin( + CryptoCurrencyNetwork.main, + ).defaultNode(isPrimary: true).toString(), ); expect(fakeStore.interactions, 0); }); @@ -206,13 +215,13 @@ void main() { coin: Monero(CryptoCurrencyNetwork.main), node: Monero(CryptoCurrencyNetwork.main).defaultNode(isPrimary: true), ); - expect( - service.primaryNodes.toString(), - [ - Bitcoin(CryptoCurrencyNetwork.main).defaultNode(isPrimary: true), - Monero(CryptoCurrencyNetwork.main).defaultNode(isPrimary: true), - ].toString(), - ); + final primaryNodes = service.primaryNodes; + final expectedPrimaryNodes = [...expectedPrimaryDefaults] + ..sort((a, b) => a.id.compareTo(b.id)); + primaryNodes.sort((a, b) => a.id.compareTo(b.id)); + + expect(primaryNodes.length, expectedPrimaryNodes.length); + expect(primaryNodes.toString(), expectedPrimaryNodes.toString()); expect(fakeStore.interactions, 0); }); @@ -220,15 +229,18 @@ void main() { final fakeStore = FakeSecureStorage(); final service = NodeService(secureStorageInterface: fakeStore); final nodes = service.nodes; - final defaults = AppConfig.coins - .map((e) => e.defaultNode(isPrimary: true)) - .toList(); - - nodes.sort((a, b) => a.id.compareTo(b.id)); - defaults.sort((a, b) => a.id.compareTo(b.id)); - - expect(nodes.length, defaults.length); - expect(nodes.toString(), defaults.toString()); + final defaultIds = expectedPrimaryDefaults.map((e) => e.id).toSet(); + final extraFiroIds = service.nodes + .where((node) => node.id.startsWith('not_a_real_default_but_temp_')) + .map((node) => node.id) + .toSet(); + + expect(nodes.length, expectedDefaultNodeCount); + expect(nodes.map((node) => node.id).toSet(), containsAll(defaultIds)); + expect( + extraFiroIds.length, + AppConfig.coins.any((e) => e.identifier == 'firo') ? 4 : 0, + ); expect(fakeStore.interactions, 0); }); @@ -236,21 +248,15 @@ void main() { final fakeStore = FakeSecureStorage(); final service = NodeService(secureStorageInterface: fakeStore); await service.save(nodeA, null, true); - expect( - service.nodes.length, - AppConfig.coins.map((e) => e.defaultNode).length + 1, - ); - expect(fakeStore.interactions, 0); + expect(service.nodes.length, expectedDefaultNodeCount + 1); + expect(fakeStore.interactions, 1); }); test("add a node with a password", () async { final fakeStore = FakeSecureStorage(); final service = NodeService(secureStorageInterface: fakeStore); await service.save(nodeA, "some password", true); - expect( - service.nodes.length, - AppConfig.coins.map((e) => e.defaultNode).length + 1, - ); + expect(service.nodes.length, expectedDefaultNodeCount + 1); expect(fakeStore.interactions, 1); expect(fakeStore.writes, 1); }); @@ -309,10 +315,7 @@ void main() { await service.delete(nodeB.id, true); - expect( - service.nodes.length, - AppConfig.coins.map((e) => e.defaultNode).length + 2, - ); + expect(service.nodes.length, expectedDefaultNodeCount + 2); expect( service.nodes.where((element) => element.id == nodeB.id).length, 0, @@ -341,6 +344,6 @@ void main() { }); tearDown(() async { - await tearDownTestHive(); + await tearDownHiveCeTest(); }); } diff --git a/test/shopinbit/car_research_persistence_test.dart b/test/shopinbit/car_research_persistence_test.dart new file mode 100644 index 0000000000..10df9aee52 --- /dev/null +++ b/test/shopinbit/car_research_persistence_test.dart @@ -0,0 +1,114 @@ +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:stackwallet/models/shopinbit/shopinbit_order_model.dart'; + +// Parses "Key: Value\n" car research description; strips " EUR" from Budget. +Map _parseCarRequestDescription(String desc) { + final result = {}; + for (final line in desc.split('\n')) { + final separatorIndex = line.indexOf(': '); + if (separatorIndex == -1) continue; + final key = line.substring(0, separatorIndex); + var value = line.substring(separatorIndex + 2); + if (key == 'Budget') { + value = value.replaceAll(' EUR', ''); + } + result[key] = value; + } + return result; +} + +void main() { + group('car research persistence', () { + group('requestDescription parsing', () { + test('parses all six fields from canonical format', () { + const desc = + 'Brand: Toyota\n' + 'Model: Corolla\n' + 'Condition: used\n' + 'Description: sedan\n' + 'Budget: 10000 EUR\n' + 'Delivery country: DE'; + final parsed = _parseCarRequestDescription(desc); + expect(parsed['Brand'], 'Toyota'); + expect(parsed['Model'], 'Corolla'); + expect(parsed['Condition'], 'used'); + expect(parsed['Description'], 'sedan'); + expect(parsed['Budget'], '10000'); + expect(parsed['Delivery country'], 'DE'); + }); + }); + + group('carResearchPaymentLinks JSON round-trip', () { + test('encode then decode preserves all keys and values', () { + final original = { + 'BTC': 'bitcoin:abc?amount=0.1', + 'ETH': 'ethereum:def', + }; + final encoded = jsonEncode(original); + final decoded = (jsonDecode(encoded) as Map).map( + (k, v) => MapEntry(k, v as String), + ); + expect(decoded, equals(original)); + }); + }); + + group('isPendingPayment defaults false', () { + test('new ShopInBitOrderModel has isPendingPayment == false', () { + final model = ShopInBitOrderModel(); + expect(model.isPendingPayment, isFalse); + }); + }); + + group( + 'toIsarTicket/fromIsarTicket round-trip for pending payment fields', + () { + test('isPendingPayment round-trips', () { + final model = ShopInBitOrderModel() + ..isPendingPayment = true + ..carResearchExpiresAt = DateTime(2026, 6, 1) + ..carResearchPaymentLinks = '{"BTC":"link"}'; + final ticket = model.toIsarTicket(); + final restored = ShopInBitOrderModel.fromIsarTicket(ticket); + expect(restored.isPendingPayment, isTrue); + expect(restored.carResearchExpiresAt, DateTime(2026, 6, 1)); + expect(restored.carResearchPaymentLinks, '{"BTC":"link"}'); + }); + }, + ); + + group('live invoice routes to payment view', () { + test('expiresAt in the future means invoice is live', () { + final expiresAt = DateTime.now().add(const Duration(hours: 1)); + expect(expiresAt.isAfter(DateTime.now()), isTrue); + }); + }); + + group('expired invoice routes to fee view', () { + test('expiresAt in the past means invoice is expired', () { + final expiresAt = DateTime.now().subtract(const Duration(hours: 1)); + expect(expiresAt.isAfter(DateTime.now()), isFalse); + }); + }); + + group('clearing isPendingPayment preserves other fields', () { + test( + 'all other model fields unchanged after clearing isPendingPayment', + () { + final model = ShopInBitOrderModel() + ..displayName = 'Test User' + ..requestDescription = + 'Brand: BMW\nModel: X5\nCondition: new\nDescription: suv\nBudget: 50000 EUR\nDelivery country: AT' + ..carResearchInvoiceId = 'inv-123' + ..isPendingPayment = true; + model.isPendingPayment = false; + expect(model.isPendingPayment, isFalse); + expect(model.displayName, 'Test User'); + expect(model.carResearchInvoiceId, 'inv-123'); + expect(model.requestDescription, startsWith('Brand: BMW')); + }, + ); + }); + }); +} diff --git a/test/utilities/dynamic_object_test.dart b/test/utilities/dynamic_object_test.dart index 9999bf0167..023e71e8e7 100644 --- a/test/utilities/dynamic_object_test.dart +++ b/test/utilities/dynamic_object_test.dart @@ -9,7 +9,10 @@ void main() { test("DynamicObject get failure", () { final object = DynamicObject(1); - expect(object.get(), throwsA(isA())); + expect( + () => object.get(), + throwsA(isA()), + ); }); test("DynamicObject get if match success", () { final object = DynamicObject(1); diff --git a/test/utilities/electrum_seed_utils_test.dart b/test/utilities/electrum_seed_utils_test.dart index a726cbb7c0..e9d06a4e6b 100644 --- a/test/utilities/electrum_seed_utils_test.dart +++ b/test/utilities/electrum_seed_utils_test.dart @@ -241,62 +241,67 @@ void main() { ); }); - group("test group requires coinlib", () { - setUpAll(() => loadCoinlib()); + group( + "test group requires coinlib", + () { + setUpAll(() => loadCoinlib()); - test("test master electrum fingerprint", () async { - final bytes = ElectrumSeedUtils.electrumMnemonicToSeedBytes( - kElectrumMnemonic, - ); - final hd = HDPrivateKey.fromSeed(bytes); - expect(BigInt.from(hd.fingerprint).toHex, "ec8d82aa"); - }); + test("test master electrum fingerprint", () async { + final bytes = ElectrumSeedUtils.electrumMnemonicToSeedBytes( + kElectrumMnemonic, + ); + final hd = HDPrivateKey.fromSeed(bytes); + expect(BigInt.from(hd.fingerprint).toHex, "ec8d82aa"); + }); - test("test root zpub", () async { - final bytes = ElectrumSeedUtils.electrumMnemonicToSeedBytes( - kElectrumMnemonic, - ); - final hd = HDPrivateKey.fromSeed(bytes); - final master = hd.derivePath("m/0'"); + test("test root zpub", () async { + final bytes = ElectrumSeedUtils.electrumMnemonicToSeedBytes( + kElectrumMnemonic, + ); + final hd = HDPrivateKey.fromSeed(bytes); + final master = hd.derivePath("m/0'"); - const zpubHDVersion = - 0x04b24746; // https://github.com/satoshilabs/slips/blob/master/slip-0132.md - expect( - master.hdPublicKey.encode(zpubHDVersion), - "zpub6oHsSqJH7vSzDJTFB8NR4YpzFU13XRmkJaVW9jQTePrnf5BPHHAQXxBMiBot12Z7DqfuTykmyPxGowrQfNa7M8xiAdEvQG47V5jhx5Tk158", - ); - }); + const zpubHDVersion = + 0x04b24746; // https://github.com/satoshilabs/slips/blob/master/slip-0132.md + expect( + master.hdPublicKey.encode(zpubHDVersion), + "zpub6oHsSqJH7vSzDJTFB8NR4YpzFU13XRmkJaVW9jQTePrnf5BPHHAQXxBMiBot12Z7DqfuTykmyPxGowrQfNa7M8xiAdEvQG47V5jhx5Tk158", + ); + }); - test("test first receiving address", () async { - final bytes = ElectrumSeedUtils.electrumMnemonicToSeedBytes( - kElectrumMnemonic, - ); - final hd = HDPrivateKey.fromSeed(bytes); - final master = hd.derivePath("m/0'"); + test("test first receiving address", () async { + final bytes = ElectrumSeedUtils.electrumMnemonicToSeedBytes( + kElectrumMnemonic, + ); + final hd = HDPrivateKey.fromSeed(bytes); + final master = hd.derivePath("m/0'"); - expect( - P2WPKHAddress.fromHash( - hash160(master.derivePath("0/0").publicKey.data), - hrp: "bc", - ).toString(), - "bc1qgfjuzurxzhl9vdalmjgw68s680lj5q933k37h5", - ); - }); + expect( + P2WPKHAddress.fromHash( + hash160(master.derivePath("0/0").publicKey.data), + hrp: "bc", + ).toString(), + "bc1qgfjuzurxzhl9vdalmjgw68s680lj5q933k37h5", + ); + }); - test("test 9th change address", () async { - final bytes = ElectrumSeedUtils.electrumMnemonicToSeedBytes( - kElectrumMnemonic, - ); - final hd = HDPrivateKey.fromSeed(bytes); - final master = hd.derivePath("m/0'"); + test("test 9th change address", () async { + final bytes = ElectrumSeedUtils.electrumMnemonicToSeedBytes( + kElectrumMnemonic, + ); + final hd = HDPrivateKey.fromSeed(bytes); + final master = hd.derivePath("m/0'"); - expect( - P2WPKHAddress.fromHash( - hash160(master.derivePath("1/8").publicKey.data), - hrp: "bc", - ).toString(), - "bc1qzz0mvhza5sdd2fy77klh3w8h5z238avztvqjdx", - ); - }); - }); + expect( + P2WPKHAddress.fromHash( + hash160(master.derivePath("1/8").publicKey.data), + hrp: "bc", + ).toString(), + "bc1qzz0mvhza5sdd2fy77klh3w8h5z238avztvqjdx", + ); + }); + }, + skip: + "Requires build/libsecp256k1.so for coinlib-backed derivation checks on Ubuntu; pure-Dart Electrum seed coverage remains active.", + ); } diff --git a/test/utilities/mock_electrum_server.dart b/test/utilities/mock_electrum_server.dart new file mode 100644 index 0000000000..6802c0cec6 --- /dev/null +++ b/test/utilities/mock_electrum_server.dart @@ -0,0 +1,197 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:electrum_adapter/electrum_adapter.dart'; +import 'package:event_bus/event_bus.dart'; +import 'package:json_rpc_2/json_rpc_2.dart' as rpc; +import 'package:stackwallet/app_config.dart'; +import 'package:stackwallet/electrumx_rpc/client_manager.dart'; +import 'package:stackwallet/electrumx_rpc/electrumx_client.dart'; +import 'package:stackwallet/services/event_bus/events/global/tor_connection_status_changed_event.dart'; +import 'package:stackwallet/services/tor_service.dart'; +import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stackwallet/utilities/tor_plain_net_option_enum.dart'; +import 'package:stackwallet/wallets/crypto_currency/coins/firo.dart'; +import 'package:stream_channel/stream_channel.dart'; + +typedef MockElectrumHandler = FutureOr Function(List params); +typedef MockElectrumRequest = ({String method, List params}); + +class MockElectrumServer { + MockElectrumServer({ + Map handlers = const {}, + BlockHeader? initialHeader, + this.host = 'mock.electrum', + this.port = 50002, + this.useSSL = true, + }) : _handlers = Map.from(handlers), + _latestHeader = initialHeader ?? BlockHeader('00', 1) { + _handlers.putIfAbsent( + 'blockchain.headers.subscribe', + () => + (_) => {'hex': _latestHeader.hex, 'height': _latestHeader.height}, + ); + } + + final String host; + final int port; + final bool useSSL; + final Map _handlers; + final List requests = []; + final List _peers = []; + BlockHeader _latestHeader; + + Future createElectrumClient({ + ({InternetAddress host, int port})? proxyInfo, + }) async { + final channel = StreamChannelController(); + final peer = rpc.Peer.withoutJson( + channel.foreign, + onUnhandledError: (_, __) {}, + ); + _registerHandlers(peer); + unawaited(peer.listen()); + _peers.add(peer); + + return ElectrumClient(channel.local, host, port, useSSL, proxyInfo); + } + + Future createFiroElectrumClient({ + ({InternetAddress host, int port})? proxyInfo, + }) async { + final channel = StreamChannelController(); + final peer = rpc.Peer.withoutJson( + channel.foreign, + onUnhandledError: (_, __) {}, + ); + _registerHandlers(peer); + unawaited(peer.listen()); + _peers.add(peer); + + return FiroElectrumClient(channel.local, host, port, useSSL, proxyInfo); + } + + void _registerHandlers(rpc.Peer peer) { + for (final entry in _handlers.entries) { + peer.registerMethod(entry.key, (rpc.Parameters params) async { + final args = _paramsAsList(params); + requests.add((method: entry.key, params: args)); + return await entry.value(args); + }); + } + } + + List _paramsAsList(rpc.Parameters params) { + try { + return List.from(params.asList); + } catch (_) { + return const []; + } + } + + int requestCount(String method) => + requests.where((request) => request.method == method).length; + + Future emitHeader(BlockHeader header) async { + _latestHeader = header; + for (final peer in _peers) { + peer.sendNotification('blockchain.headers.subscribe', [ + {'hex': header.hex, 'height': header.height}, + ]); + } + } + + Future close() async { + for (final peer in _peers) { + await peer.close(); + } + _peers.clear(); + } +} + +class ManagedElectrumXClient extends ElectrumXClient { + ManagedElectrumXClient({ + required super.host, + required super.port, + required super.useSSL, + required Prefs prefs, + required TorService torService, + required super.failovers, + required super.cryptoCurrency, + required super.netType, + required this.clearServer, + this.torServer, + EventBus? globalEventBusForTesting, + }) : _prefsForTest = prefs, + _torServiceForTest = torService, + super( + prefs: prefs, + torService: torService, + globalEventBusForTesting: globalEventBusForTesting, + ); + + final Prefs _prefsForTest; + final TorService _torServiceForTest; + final MockElectrumServer clearServer; + final MockElectrumServer? torServer; + + @override + Future checkElectrumAdapter() async { + ({InternetAddress host, int port})? proxyInfo; + + if (AppConfig.hasFeature(AppFeature.tor)) { + if (_prefsForTest.useTor) { + if (_torServiceForTest.status != TorConnectionStatus.connected) { + if (_prefsForTest.torKillSwitch) { + throw Exception( + 'Tor preference and killswitch set but Tor is not enabled, ' + 'not connecting to Electrum adapter', + ); + } + } else { + proxyInfo = _torServiceForTest.getProxyInfo(); + } + + if (netType == TorPlainNetworkOption.clear) { + await (await ClientManager.sharedInstance.remove( + cryptoCurrency: cryptoCurrency, + )).$1?.close(); + } + } else if (netType == TorPlainNetworkOption.tor) { + await (await ClientManager.sharedInstance.remove( + cryptoCurrency: cryptoCurrency, + )).$1?.close(); + } + } + + final existing = getElectrumAdapter(); + if (existing != null && !existing.peer.isClosed) { + return; + } + if (existing != null) { + await (await ClientManager.sharedInstance.remove( + cryptoCurrency: cryptoCurrency, + )).$1?.close(); + } + + final server = proxyInfo != null ? (torServer ?? clearServer) : clearServer; + final adapter = cryptoCurrency is Firo + ? await server.createFiroElectrumClient(proxyInfo: proxyInfo) + : await server.createElectrumClient(proxyInfo: proxyInfo); + + await ClientManager.sharedInstance.addClient( + adapter, + cryptoCurrency: cryptoCurrency, + netType: netType, + ); + } +} + +Future tearDownManagedElectrum({ + Iterable servers = const [], +}) async { + await ClientManager.sharedInstance.closeAll(); + for (final server in servers) { + await server.close(); + } +} diff --git a/test/widget_tests/managed_favorite_test.mocks.dart b/test/widget_tests/managed_favorite_test.mocks.dart index 28d64ceb68..517d7e9fb0 100644 --- a/test/widget_tests/managed_favorite_test.mocks.dart +++ b/test/widget_tests/managed_favorite_test.mocks.dart @@ -11,6 +11,7 @@ import 'package:logger/logger.dart' as _i19; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i17; import 'package:stackwallet/db/isar/main_db.dart' as _i3; +import 'package:stackwallet/models/epicbox_server_model.dart' as _i24; import 'package:stackwallet/models/isar/stack_theme.dart' as _i14; import 'package:stackwallet/models/node_model.dart' as _i23; import 'package:stackwallet/networking/http.dart' as _i6; @@ -1132,6 +1133,64 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { ) as _i10.Future); + @override + _i10.Future updateDefaultEpicBoxes() => + (super.noSuchMethod( + Invocation.method(#updateDefaultEpicBoxes, []), + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) + as _i10.Future); + + @override + _i10.Future setPrimaryEpicBox({ + required _i24.EpicBoxServerModel? epicBox, + bool? shouldNotifyListeners = false, + }) => + (super.noSuchMethod( + Invocation.method(#setPrimaryEpicBox, [], { + #epicBox: epicBox, + #shouldNotifyListeners: shouldNotifyListeners, + }), + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) + as _i10.Future); + + @override + List<_i24.EpicBoxServerModel> getEpicBoxes() => + (super.noSuchMethod( + Invocation.method(#getEpicBoxes, []), + returnValue: <_i24.EpicBoxServerModel>[], + ) + as List<_i24.EpicBoxServerModel>); + + @override + _i24.EpicBoxServerModel? getEpicBoxById({required String? id}) => + (super.noSuchMethod(Invocation.method(#getEpicBoxById, [], {#id: id})) + as _i24.EpicBoxServerModel?); + + @override + _i10.Future addEpicBox( + _i24.EpicBoxServerModel? epicBox, + bool? shouldNotifyListeners, + ) => + (super.noSuchMethod( + Invocation.method(#addEpicBox, [epicBox, shouldNotifyListeners]), + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) + as _i10.Future); + + @override + _i10.Future deleteEpicBox(String? id, bool? shouldNotifyListeners) => + (super.noSuchMethod( + Invocation.method(#deleteEpicBox, [id, shouldNotifyListeners]), + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) + as _i10.Future); + @override _i10.Future updateCommunityNodes() => (super.noSuchMethod( diff --git a/test/widget_tests/node_card_test.dart b/test/widget_tests/node_card_test.dart index 71a08f8514..f159dab708 100644 --- a/test/widget_tests/node_card_test.dart +++ b/test/widget_tests/node_card_test.dart @@ -11,246 +11,135 @@ import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/node_card.dart'; import 'package:stackwallet/widgets/node_options_sheet.dart'; import '../sample_data/theme_json.dart'; import 'node_card_test.mocks.dart'; +import 'support/platform_test_overrides.dart'; @GenerateMocks([NodeService]) void main() { - testWidgets("NodeCard builds inactive node correctly", (tester) async { - final nodeService = MockNodeService(); - - when( - nodeService.getPrimaryNodeFor( - currency: Bitcoin(CryptoCurrencyNetwork.main), - ), - ).thenAnswer( - (realInvocation) => NodeModel( - host: "127.0.0.1", - port: 2000, - name: "Stack Default", - id: "node id", - useSSL: true, - enabled: true, - coinName: "Bitcoin", - isFailover: false, - isDown: false, - torEnabled: true, - clearnetEnabled: true, - isPrimary: true, - ), + final bitcoin = Bitcoin(CryptoCurrencyNetwork.main); + + NodeModel buildNode({required String id, required String name}) { + return NodeModel( + host: '127.0.0.1', + port: 2000, + name: name, + id: id, + useSSL: true, + enabled: true, + coinName: 'Bitcoin', + isFailover: false, + isDown: false, + torEnabled: true, + clearnetEnabled: true, + isPrimary: true, ); + } - when(nodeService.getNodeById(id: "node id")).thenAnswer( - (realInvocation) => NodeModel( - host: "127.0.0.1", - port: 2000, - name: "some other name", - id: "node id", - useSSL: true, - enabled: true, - coinName: "Bitcoin", - isFailover: false, - isDown: false, - torEnabled: true, - clearnetEnabled: true, - isPrimary: true, - ), + ThemeData buildTheme() { + return ThemeData( + extensions: [ + StackColors.fromStackColorTheme( + StackTheme.fromJson(json: lightThemeJsonMap), + ), + ], ); + } + Future pumpSubject( + WidgetTester tester, { + required MockNodeService nodeService, + required List extraOverrides, + }) async { await tester.pumpWidget( ProviderScope( overrides: [ nodeServiceChangeNotifierProvider.overrideWithValue(nodeService), + ...extraOverrides, ], child: MaterialApp( - theme: ThemeData( - extensions: [ - StackColors.fromStackColorTheme( - StackTheme.fromJson( - json: lightThemeJsonMap, - ), - ), - ], - ), - home: NodeCard( - nodeId: "node id", - coin: Bitcoin(CryptoCurrencyNetwork.main), - popBackToRoute: "", - ), + theme: buildTheme(), + home: NodeCard(nodeId: 'node id', coin: bitcoin, popBackToRoute: ''), ), ), ); await tester.pumpAndSettle(); + } + + testWidgets('NodeCard builds inactive node correctly', (tester) async { + final nodeService = MockNodeService(); + + when( + nodeService.getPrimaryNodeFor(currency: bitcoin), + ).thenAnswer((_) => buildNode(id: 'other node id', name: 'Stack Default')); + when( + nodeService.getNodeById(id: 'node id'), + ).thenAnswer((_) => buildNode(id: 'node id', name: 'some other name')); + + await pumpSubject( + tester, + nodeService: nodeService, + extraOverrides: const [], + ); - expect(find.text("some other name"), findsOneWidget); - expect(find.text("Disconnected"), findsOneWidget); + expect(find.text('some other name'), findsOneWidget); + expect(find.text('Disconnected'), findsOneWidget); expect(find.byType(SvgPicture), findsWidgets); - verify( - nodeService.getPrimaryNodeFor( - currency: Bitcoin(CryptoCurrencyNetwork.main), - ), - ).called(1); - verify(nodeService.getNodeById(id: "node id")).called(1); + verify(nodeService.getPrimaryNodeFor(currency: bitcoin)).called(1); + verify(nodeService.getNodeById(id: 'node id')).called(1); verify(nodeService.addListener(any)).called(1); verifyNoMoreInteractions(nodeService); }); - testWidgets("NodeCard builds active node correctly", (tester) async { + testWidgets('NodeCard builds active node correctly', (tester) async { final nodeService = MockNodeService(); + final activeNode = buildNode(id: 'node id', name: 'Some other node name'); when( - nodeService.getPrimaryNodeFor( - currency: Bitcoin(CryptoCurrencyNetwork.main), - ), - ).thenAnswer( - (realInvocation) => NodeModel( - host: "127.0.0.1", - port: 2000, - name: "Some other node name", - id: "node id", - useSSL: true, - enabled: true, - coinName: "Bitcoin", - isFailover: false, - isDown: false, - torEnabled: true, - clearnetEnabled: true, - isPrimary: true, - ), + nodeService.getPrimaryNodeFor(currency: bitcoin), + ).thenAnswer((_) => activeNode); + when(nodeService.getNodeById(id: 'node id')).thenAnswer((_) => activeNode); + + await pumpSubject( + tester, + nodeService: nodeService, + extraOverrides: const [], ); - when(nodeService.getNodeById(id: "node id")).thenAnswer( - (realInvocation) => NodeModel( - host: "127.0.0.1", - port: 2000, - name: "Some other node name", - id: "node id", - useSSL: true, - enabled: true, - coinName: "Bitcoin", - isFailover: false, - isDown: false, - torEnabled: true, - clearnetEnabled: true, - isPrimary: true, - ), - ); - - await tester.pumpWidget( - ProviderScope( - overrides: [ - nodeServiceChangeNotifierProvider.overrideWithValue(nodeService), - ], - child: MaterialApp( - theme: ThemeData( - extensions: [ - StackColors.fromStackColorTheme( - StackTheme.fromJson( - json: lightThemeJsonMap, - ), - ), - ], - ), - home: NodeCard( - nodeId: "node id", - coin: Bitcoin(CryptoCurrencyNetwork.main), - popBackToRoute: "", - ), - ), - ), - ); - await tester.pumpAndSettle(); - - expect(find.text("Some other node name"), findsOneWidget); - expect(find.text("Connected"), findsOneWidget); + expect(find.text('Some other node name'), findsOneWidget); + expect(find.text('Connected'), findsOneWidget); expect(find.byType(Text), findsNWidgets(2)); expect(find.byType(SvgPicture), findsWidgets); - verify( - nodeService.getPrimaryNodeFor( - currency: Bitcoin(CryptoCurrencyNetwork.main), - ), - ).called(1); - verify(nodeService.getNodeById(id: "node id")).called(1); + verify(nodeService.getPrimaryNodeFor(currency: bitcoin)).called(1); + verify(nodeService.getNodeById(id: 'node id')).called(1); verify(nodeService.addListener(any)).called(1); - verifyNoMoreInteractions(nodeService); }); - testWidgets("tap to open context menu on default node", (tester) async { + testWidgets('tap to open context menu on default node', (tester) async { final nodeService = MockNodeService(); + final activeNode = buildNode(id: 'node id', name: 'Stack Default'); when( - nodeService.getPrimaryNodeFor( - currency: Bitcoin(CryptoCurrencyNetwork.main), - ), - ).thenAnswer( - (realInvocation) => NodeModel( - host: "127.0.0.1", - port: 2000, - name: "Stack Default", - id: "node id", - useSSL: true, - enabled: true, - coinName: "Bitcoin", - isFailover: false, - isDown: false, - torEnabled: true, - clearnetEnabled: true, - isPrimary: true, - ), + nodeService.getPrimaryNodeFor(currency: bitcoin), + ).thenAnswer((_) => activeNode); + when(nodeService.getNodeById(id: 'node id')).thenAnswer((_) => activeNode); + + await pumpSubject( + tester, + nodeService: nodeService, + extraOverrides: const [], ); - when(nodeService.getNodeById(id: "node id")).thenAnswer( - (realInvocation) => NodeModel( - host: "127.0.0.1", - port: 2000, - name: "Stack Default", - id: "node id", - useSSL: true, - enabled: true, - coinName: "Bitcoin", - isFailover: false, - isDown: false, - torEnabled: true, - clearnetEnabled: true, - isPrimary: true, - ), - ); - - await tester.pumpWidget( - ProviderScope( - overrides: [ - nodeServiceChangeNotifierProvider.overrideWithValue(nodeService), - ], - child: MaterialApp( - theme: ThemeData( - extensions: [ - StackColors.fromStackColorTheme( - StackTheme.fromJson( - json: lightThemeJsonMap, - ), - ), - ], - ), - home: NodeCard( - nodeId: "node id", - coin: Bitcoin(CryptoCurrencyNetwork.main), - popBackToRoute: "", - ), - ), - ), - ); - - await tester.pumpAndSettle(); - - expect(find.text("Stack Default"), findsOneWidget); - expect(find.text("Connected"), findsOneWidget); + expect(find.text('Stack Default'), findsOneWidget); + expect(find.text('Connected'), findsOneWidget); expect(find.byType(Text), findsNWidgets(2)); expect(find.byType(SvgPicture), findsNWidgets(2)); @@ -258,31 +147,76 @@ void main() { await tester.pumpAndSettle(); if (Util.isDesktop) { - expect(find.text("Connect"), findsNothing); - expect(find.text("Details"), findsNothing); + expect(find.text('Connect'), findsNothing); + expect(find.text('Details'), findsNothing); - verify( - nodeService.getPrimaryNodeFor( - currency: Bitcoin(CryptoCurrencyNetwork.main), - ), - ).called(1); - verify(nodeService.getNodeById(id: "node id")).called(1); + verify(nodeService.getPrimaryNodeFor(currency: bitcoin)).called(1); + verify(nodeService.getNodeById(id: 'node id')).called(1); } else { - expect(find.text("Connect"), findsOneWidget); - expect(find.text("Details"), findsOneWidget); + expect(find.text('Connect'), findsOneWidget); + expect(find.text('Details'), findsOneWidget); expect(find.byType(NodeOptionsSheet), findsOneWidget); expect(find.byType(Text), findsNWidgets(7)); - verify( - nodeService.getPrimaryNodeFor( - currency: Bitcoin(CryptoCurrencyNetwork.main), - ), - ).called(2); - verify(nodeService.getNodeById(id: "node id")).called(2); + verify(nodeService.getPrimaryNodeFor(currency: bitcoin)).called(2); + verify(nodeService.getNodeById(id: 'node id')).called(2); } verify(nodeService.addListener(any)).called(1); - verifyNoMoreInteractions(nodeService); }); + + testWidgets( + 'desktop connect failure uses seam once and does not promote node', + (tester) async { + final nodeService = MockNodeService(); + final platformOverrides = await createPlatformTestOverrides( + connectionResult: false, + ); + final disconnectedNode = buildNode(id: 'node id', name: 'Stack Default'); + + when(nodeService.getPrimaryNodeFor(currency: bitcoin)).thenAnswer( + (_) => buildNode(id: 'other node id', name: 'Some other node name'), + ); + when( + nodeService.getNodeById(id: 'node id'), + ).thenAnswer((_) => disconnectedNode); + + await pumpSubject( + tester, + nodeService: nodeService, + extraOverrides: platformOverrides.overrides, + ); + + if (!Util.isDesktop) { + return; + } + + await tester.tap(find.byType(NodeCard)); + await tester.pumpAndSettle(); + + final connectFinder = find.byWidgetPredicate( + (widget) => widget is CustomTextButton && widget.text == 'Connect', + ); + expect(connectFinder, findsOneWidget); + expect(tester.widget(connectFinder).enabled, isTrue); + + tester.widget(connectFinder).onTap?.call(); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 100)); + + expect(platformOverrides.secureStorage.reads, 1); + expect(platformOverrides.connectionInvocations, hasLength(1)); + expect(platformOverrides.connectionInvocations.single.password, isNull); + expect(platformOverrides.connectionInvocations.single.host, '127.0.0.1'); + + verifyNever( + nodeService.setPrimaryNodeFor( + coin: bitcoin, + node: anyNamed('node'), + shouldNotifyListeners: anyNamed('shouldNotifyListeners'), + ), + ); + }, + ); } diff --git a/test/widget_tests/node_card_test.mocks.dart b/test/widget_tests/node_card_test.mocks.dart index a279b9bd3d..73db103f60 100644 --- a/test/widget_tests/node_card_test.mocks.dart +++ b/test/widget_tests/node_card_test.mocks.dart @@ -4,9 +4,10 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i5; -import 'dart:ui' as _i7; +import 'dart:ui' as _i8; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/epicbox_server_model.dart' as _i7; import 'package:stackwallet/models/node_model.dart' as _i4; import 'package:stackwallet/services/node_service.dart' as _i3; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' @@ -170,6 +171,64 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { ) as _i5.Future); + @override + _i5.Future updateDefaultEpicBoxes() => + (super.noSuchMethod( + Invocation.method(#updateDefaultEpicBoxes, []), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future setPrimaryEpicBox({ + required _i7.EpicBoxServerModel? epicBox, + bool? shouldNotifyListeners = false, + }) => + (super.noSuchMethod( + Invocation.method(#setPrimaryEpicBox, [], { + #epicBox: epicBox, + #shouldNotifyListeners: shouldNotifyListeners, + }), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + List<_i7.EpicBoxServerModel> getEpicBoxes() => + (super.noSuchMethod( + Invocation.method(#getEpicBoxes, []), + returnValue: <_i7.EpicBoxServerModel>[], + ) + as List<_i7.EpicBoxServerModel>); + + @override + _i7.EpicBoxServerModel? getEpicBoxById({required String? id}) => + (super.noSuchMethod(Invocation.method(#getEpicBoxById, [], {#id: id})) + as _i7.EpicBoxServerModel?); + + @override + _i5.Future addEpicBox( + _i7.EpicBoxServerModel? epicBox, + bool? shouldNotifyListeners, + ) => + (super.noSuchMethod( + Invocation.method(#addEpicBox, [epicBox, shouldNotifyListeners]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + + @override + _i5.Future deleteEpicBox(String? id, bool? shouldNotifyListeners) => + (super.noSuchMethod( + Invocation.method(#deleteEpicBox, [id, shouldNotifyListeners]), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) + as _i5.Future); + @override _i5.Future updateCommunityNodes() => (super.noSuchMethod( @@ -180,13 +239,13 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { as _i5.Future); @override - void addListener(_i7.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#addListener, [listener]), returnValueForMissingStub: null, ); @override - void removeListener(_i7.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#removeListener, [listener]), returnValueForMissingStub: null, ); diff --git a/test/widget_tests/node_options_sheet_test.dart b/test/widget_tests/node_options_sheet_test.dart index cedc9158b6..26cfffb339 100644 --- a/test/widget_tests/node_options_sheet_test.dart +++ b/test/widget_tests/node_options_sheet_test.dart @@ -6,256 +6,291 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:stackwallet/models/isar/stack_theme.dart'; import 'package:stackwallet/models/node_model.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/services/tor_service.dart'; import 'package:stackwallet/services/wallets.dart'; import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/enums/sync_type_enum.dart'; import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart'; import 'package:stackwallet/widgets/node_options_sheet.dart'; import '../sample_data/theme_json.dart'; import 'node_options_sheet_test.mocks.dart'; +import 'support/platform_test_overrides.dart'; @GenerateMocks([Wallets, Prefs, NodeService, TorService]) void main() { - testWidgets("Load Node Options widget", (tester) async { - final mockWallets = MockWallets(); - final mockPrefs = MockPrefs(); - final mockNodeService = MockNodeService(); + final bitcoin = Bitcoin(CryptoCurrencyNetwork.main); + + NodeModel buildNode({required String id, required String name}) { + return NodeModel( + host: '127.0.0.1', + port: 2000, + name: name, + id: id, + useSSL: true, + enabled: true, + coinName: 'Bitcoin', + isFailover: false, + isDown: false, + torEnabled: true, + clearnetEnabled: true, + isPrimary: true, + ); + } - when(mockNodeService.getNodeById(id: "node id")) - .thenAnswer((realInvocation) => NodeModel( - host: "127.0.0.1", - port: 2000, - name: "Some other name", - id: "node id", - useSSL: true, - enabled: true, - coinName: "Bitcoin", - isFailover: false, - isDown: false, - torEnabled: true, - clearnetEnabled: true, - isPrimary: true, - )); + ThemeData buildTheme() { + return ThemeData( + extensions: [ + StackColors.fromStackColorTheme( + StackTheme.fromJson(json: lightThemeJsonMap), + ), + ], + ); + } - when(mockNodeService.getPrimaryNodeFor( - currency: Bitcoin(CryptoCurrencyNetwork.main))) - .thenAnswer((realInvocation) => NodeModel( - host: "127.0.0.1", - port: 2000, - name: "Some other name", - id: "node id", - useSSL: true, - enabled: true, - coinName: "Bitcoin", - isFailover: false, - torEnabled: true, - clearnetEnabled: true, - isDown: false, - isPrimary: true)); + void stubCommonProviders({ + required MockWallets wallets, + required MockPrefs prefs, + required MockNodeService nodeService, + required NodeModel node, + required NodeModel primaryNode, + }) { + when(wallets.wallets).thenReturn([]); + when(prefs.syncType).thenReturn(SyncingType.currentWalletOnly); + when(nodeService.getNodeById(id: node.id)).thenAnswer((_) => node); + when( + nodeService.getPrimaryNodeFor(currency: bitcoin), + ).thenAnswer((_) => primaryNode); + } + Future pumpSubject( + WidgetTester tester, { + required MockWallets wallets, + required MockPrefs prefs, + required MockNodeService nodeService, + required List extraOverrides, + GlobalKey? navigatorKey, + RouteFactory? onGenerateRoute, + String popBackToRoute = '', + }) async { await tester.pumpWidget( ProviderScope( overrides: [ - pWallets.overrideWithValue(mockWallets), - prefsChangeNotifierProvider.overrideWithValue(mockPrefs), - nodeServiceChangeNotifierProvider.overrideWithValue(mockNodeService) + pWallets.overrideWithValue(wallets), + prefsChangeNotifierProvider.overrideWithValue(prefs), + nodeServiceChangeNotifierProvider.overrideWithValue(nodeService), + ...extraOverrides, ], child: MaterialApp( - theme: ThemeData( - extensions: [ - StackColors.fromStackColorTheme( - StackTheme.fromJson( - json: lightThemeJsonMap, - ), - ), - ], - ), + navigatorKey: navigatorKey, + theme: buildTheme(), + onGenerateRoute: onGenerateRoute, home: NodeOptionsSheet( - nodeId: "node id", - coin: Bitcoin(CryptoCurrencyNetwork.main), - popBackToRoute: ""), + nodeId: 'node id', + coin: bitcoin, + popBackToRoute: popBackToRoute, + ), ), ), ); await tester.pumpAndSettle(); - expect(find.text("Node options"), findsOneWidget); - expect(find.text("Some other name"), findsOneWidget); - expect(find.text("Connected"), findsOneWidget); + } + + testWidgets('Load Node Options widget with disabled connect state', ( + tester, + ) async { + final mockWallets = MockWallets(); + final mockPrefs = MockPrefs(); + final mockNodeService = MockNodeService(); + final connectedNode = buildNode(id: 'node id', name: 'Some other name'); + + stubCommonProviders( + wallets: mockWallets, + prefs: mockPrefs, + nodeService: mockNodeService, + node: connectedNode, + primaryNode: connectedNode, + ); + + await pumpSubject( + tester, + wallets: mockWallets, + prefs: mockPrefs, + nodeService: mockNodeService, + extraOverrides: const [], + ); + + expect(find.text('Node options'), findsOneWidget); + expect(find.text('Some other name'), findsOneWidget); + expect(find.text('Connected'), findsOneWidget); expect(find.byType(SvgPicture), findsNWidgets(2)); - expect(find.text("Details"), findsOneWidget); - expect(find.text("Connect"), findsOneWidget); + expect(find.text('Details'), findsOneWidget); + expect(find.text('Connect'), findsOneWidget); + expect( + tester + .widget(find.widgetWithText(TextButton, 'Connect')) + .onPressed, + isNull, + ); - verify(mockNodeService.getPrimaryNodeFor( - currency: Bitcoin(CryptoCurrencyNetwork.main))) - .called(1); - verify(mockNodeService.getNodeById(id: "node id")).called(1); + verify(mockNodeService.getPrimaryNodeFor(currency: bitcoin)).called(1); + verify(mockNodeService.getNodeById(id: 'node id')).called(1); verify(mockNodeService.addListener(any)).called(1); verifyNoMoreInteractions(mockNodeService); }); - testWidgets("Details tap", (tester) async { + testWidgets('Details tap pushes node details route', (tester) async { final navigatorKey = GlobalKey(); final mockWallets = MockWallets(); final mockPrefs = MockPrefs(); final mockNodeService = MockNodeService(); - final mockTorService = MockTorService(); + final node = buildNode(id: 'node id', name: 'Stack Default'); + final otherPrimary = buildNode(id: 'some node id', name: 'Stack Default'); - when(mockNodeService.getNodeById(id: "node id")).thenAnswer( - (_) => NodeModel( - host: "127.0.0.1", - port: 2000, - name: "Stack Default", - id: "node id", - useSSL: true, - enabled: true, - coinName: "Bitcoin", - isFailover: false, - isDown: false, - torEnabled: true, - clearnetEnabled: true, - isPrimary: true, - ), - ); - - when(mockNodeService.getPrimaryNodeFor( - currency: Bitcoin(CryptoCurrencyNetwork.main))) - .thenAnswer( - (_) => NodeModel( - host: "127.0.0.1", - port: 2000, - name: "Stack Default", - id: "some node id", - useSSL: true, - enabled: true, - coinName: "Bitcoin", - isFailover: false, - isDown: false, - torEnabled: true, - clearnetEnabled: true, - isPrimary: true, - ), + stubCommonProviders( + wallets: mockWallets, + prefs: mockPrefs, + nodeService: mockNodeService, + node: node, + primaryNode: otherPrimary, ); - await tester.pumpWidget( - ProviderScope( - overrides: [ - pWallets.overrideWithValue(mockWallets), - prefsChangeNotifierProvider.overrideWithValue(mockPrefs), - nodeServiceChangeNotifierProvider.overrideWithValue(mockNodeService), - pTorService.overrideWithValue(mockTorService), - ], - child: MaterialApp( - navigatorKey: navigatorKey, - theme: ThemeData( - extensions: [ - StackColors.fromStackColorTheme( - StackTheme.fromJson( - json: lightThemeJsonMap, - ), - ), - ], - ), - onGenerateRoute: (settings) { - if (settings.name == '/nodeDetails') { - return MaterialPageRoute(builder: (_) => Scaffold()); - } - return null; - }, - home: NodeOptionsSheet( - nodeId: "node id", - coin: Bitcoin(CryptoCurrencyNetwork.main), - popBackToRoute: "coinNodes", - ), - ), - ), + await pumpSubject( + tester, + wallets: mockWallets, + prefs: mockPrefs, + nodeService: mockNodeService, + extraOverrides: const [], + navigatorKey: navigatorKey, + popBackToRoute: 'coinNodes', + onGenerateRoute: (settings) { + if (settings.name == NodeDetailsView.routeName) { + return MaterialPageRoute( + builder: (_) => const Scaffold(body: Text('details route')), + ); + } + return null; + }, ); - await tester.tap(find.text("Details")); + await tester.tap(find.text('Details')); await tester.pumpAndSettle(); - final currentRoute = navigatorKey.currentState?.overlay?.context; - expect(currentRoute, isNotNull); + expect(find.text('details route'), findsOneWidget); + expect(navigatorKey.currentState?.canPop(), isFalse); }); - testWidgets("Connect tap", (tester) async { + testWidgets('Connect tap uses fake storage and promotes node on success', ( + tester, + ) async { final mockWallets = MockWallets(); final mockPrefs = MockPrefs(); final mockNodeService = MockNodeService(); - final mockTorService = MockTorService(); - - when(mockNodeService.getNodeById(id: "node id")).thenAnswer( - (_) => NodeModel( - host: "127.0.0.1", - port: 2000, - name: "Stack Default", - id: "node id", - useSSL: true, - enabled: true, - coinName: "Bitcoin", - isFailover: false, - isDown: false, - torEnabled: true, - clearnetEnabled: true, - isPrimary: true, - ), + final node = buildNode(id: 'node id', name: 'Stack Default'); + final otherPrimary = buildNode( + id: 'some node id', + name: 'Some other node name', ); - - when(mockNodeService.getPrimaryNodeFor( - currency: Bitcoin(CryptoCurrencyNetwork.main))) - .thenAnswer( - (_) => NodeModel( - host: "127.0.0.1", - port: 2000, - name: "Some other node name", - id: "some node id", - useSSL: true, - enabled: true, - coinName: "Bitcoin", - isFailover: false, - isDown: false, - torEnabled: true, - clearnetEnabled: true, - isPrimary: true, - ), + final platformOverrides = await createPlatformTestOverrides( + secureStorageEntries: {'node id_nodePW': 'fake-node-password'}, + connectionResult: true, ); - await tester.pumpWidget( - ProviderScope( - overrides: [ - pWallets.overrideWithValue(mockWallets), - prefsChangeNotifierProvider.overrideWithValue(mockPrefs), - nodeServiceChangeNotifierProvider.overrideWithValue(mockNodeService), - pTorService.overrideWithValue(mockTorService), - ], - child: MaterialApp( - theme: ThemeData( - extensions: [ - StackColors.fromStackColorTheme( - StackTheme.fromJson( - json: lightThemeJsonMap, - ), - ), - ], - ), - home: NodeOptionsSheet( - nodeId: "node id", - coin: Bitcoin(CryptoCurrencyNetwork.main), - popBackToRoute: "", - ), - ), + stubCommonProviders( + wallets: mockWallets, + prefs: mockPrefs, + nodeService: mockNodeService, + node: node, + primaryNode: otherPrimary, + ); + when( + mockNodeService.setPrimaryNodeFor( + coin: bitcoin, + node: node, + shouldNotifyListeners: true, ), + ).thenAnswer((_) async {}); + + await pumpSubject( + tester, + wallets: mockWallets, + prefs: mockPrefs, + nodeService: mockNodeService, + extraOverrides: platformOverrides.overrides, ); - await tester.pumpAndSettle(); - expect(find.text("Node options"), findsOneWidget); - expect(find.text("Disconnected"), findsOneWidget); + expect(find.text('Disconnected'), findsOneWidget); - await tester.tap(find.text("Connect")); + await tester.tap(find.widgetWithText(TextButton, 'Connect')); await tester.pumpAndSettle(); + + expect(platformOverrides.secureStorage.reads, 1); + expect(platformOverrides.connectionInvocations, hasLength(1)); + expect( + platformOverrides.connectionInvocations.single.password, + 'fake-node-password', + ); + expect(platformOverrides.connectionInvocations.single.host, '127.0.0.1'); + + verify( + mockNodeService.setPrimaryNodeFor( + coin: bitcoin, + node: node, + shouldNotifyListeners: true, + ), + ).called(1); }); + + testWidgets( + 'Connect failure stays inside fake seam with missing stored password', + (tester) async { + final mockWallets = MockWallets(); + final mockPrefs = MockPrefs(); + final mockNodeService = MockNodeService(); + final node = buildNode(id: 'node id', name: 'Stack Default'); + final otherPrimary = buildNode( + id: 'some node id', + name: 'Some other node name', + ); + final platformOverrides = await createPlatformTestOverrides( + connectionResult: false, + ); + + stubCommonProviders( + wallets: mockWallets, + prefs: mockPrefs, + nodeService: mockNodeService, + node: node, + primaryNode: otherPrimary, + ); + + await pumpSubject( + tester, + wallets: mockWallets, + prefs: mockPrefs, + nodeService: mockNodeService, + extraOverrides: platformOverrides.overrides, + ); + + await tester.tap(find.widgetWithText(TextButton, 'Connect')); + await tester.pumpAndSettle(); + + expect(platformOverrides.secureStorage.reads, 1); + expect(platformOverrides.connectionInvocations, hasLength(1)); + expect(platformOverrides.connectionInvocations.single.password, isNull); + + verifyNever( + mockNodeService.setPrimaryNodeFor( + coin: bitcoin, + node: anyNamed('node'), + shouldNotifyListeners: anyNamed('shouldNotifyListeners'), + ), + ); + }, + ); } diff --git a/test/widget_tests/node_options_sheet_test.mocks.dart b/test/widget_tests/node_options_sheet_test.mocks.dart index e30eade5d2..277a6b82c6 100644 --- a/test/widget_tests/node_options_sheet_test.mocks.dart +++ b/test/widget_tests/node_options_sheet_test.mocks.dart @@ -11,11 +11,12 @@ import 'package:logger/logger.dart' as _i16; import 'package:mockito/mockito.dart' as _i1; import 'package:mockito/src/dummies.dart' as _i14; import 'package:stackwallet/db/isar/main_db.dart' as _i3; +import 'package:stackwallet/models/epicbox_server_model.dart' as _i20; import 'package:stackwallet/models/node_model.dart' as _i19; import 'package:stackwallet/services/event_bus/events/global/tor_connection_status_changed_event.dart' - as _i21; + as _i22; import 'package:stackwallet/services/node_service.dart' as _i2; -import 'package:stackwallet/services/tor_service.dart' as _i20; +import 'package:stackwallet/services/tor_service.dart' as _i21; import 'package:stackwallet/services/wallets.dart' as _i9; import 'package:stackwallet/utilities/amount/amount_unit.dart' as _i17; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i15; @@ -967,6 +968,64 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { ) as _i10.Future); + @override + _i10.Future updateDefaultEpicBoxes() => + (super.noSuchMethod( + Invocation.method(#updateDefaultEpicBoxes, []), + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) + as _i10.Future); + + @override + _i10.Future setPrimaryEpicBox({ + required _i20.EpicBoxServerModel? epicBox, + bool? shouldNotifyListeners = false, + }) => + (super.noSuchMethod( + Invocation.method(#setPrimaryEpicBox, [], { + #epicBox: epicBox, + #shouldNotifyListeners: shouldNotifyListeners, + }), + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) + as _i10.Future); + + @override + List<_i20.EpicBoxServerModel> getEpicBoxes() => + (super.noSuchMethod( + Invocation.method(#getEpicBoxes, []), + returnValue: <_i20.EpicBoxServerModel>[], + ) + as List<_i20.EpicBoxServerModel>); + + @override + _i20.EpicBoxServerModel? getEpicBoxById({required String? id}) => + (super.noSuchMethod(Invocation.method(#getEpicBoxById, [], {#id: id})) + as _i20.EpicBoxServerModel?); + + @override + _i10.Future addEpicBox( + _i20.EpicBoxServerModel? epicBox, + bool? shouldNotifyListeners, + ) => + (super.noSuchMethod( + Invocation.method(#addEpicBox, [epicBox, shouldNotifyListeners]), + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) + as _i10.Future); + + @override + _i10.Future deleteEpicBox(String? id, bool? shouldNotifyListeners) => + (super.noSuchMethod( + Invocation.method(#deleteEpicBox, [id, shouldNotifyListeners]), + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) + as _i10.Future); + @override _i10.Future updateCommunityNodes() => (super.noSuchMethod( @@ -1004,18 +1063,18 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { /// A class which mocks [TorService]. /// /// See the documentation for Mockito's code generation for more information. -class MockTorService extends _i1.Mock implements _i20.TorService { +class MockTorService extends _i1.Mock implements _i21.TorService { MockTorService() { _i1.throwOnMissingStub(this); } @override - _i21.TorConnectionStatus get status => + _i22.TorConnectionStatus get status => (super.noSuchMethod( Invocation.getter(#status), - returnValue: _i21.TorConnectionStatus.disconnected, + returnValue: _i22.TorConnectionStatus.disconnected, ) - as _i21.TorConnectionStatus); + as _i22.TorConnectionStatus); @override ({_i8.InternetAddress host, int port}) getProxyInfo() => diff --git a/test/widget_tests/support/platform_test_overrides.dart b/test/widget_tests/support/platform_test_overrides.dart new file mode 100644 index 0000000000..73c0e5a0e6 --- /dev/null +++ b/test/widget_tests/support/platform_test_overrides.dart @@ -0,0 +1,187 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:stackwallet/utilities/test_node_connection.dart'; +import 'package:stackwallet/utilities/tor_plain_net_option_enum.dart'; +import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart'; + +class NodeConnectionTestInvocation { + const NodeConnectionTestInvocation({ + required this.cryptoCurrency, + required this.name, + required this.host, + required this.login, + required this.password, + required this.port, + required this.useSSL, + required this.isFailover, + required this.trusted, + required this.netOption, + }); + + factory NodeConnectionTestInvocation.fromFormData({ + required CryptoCurrency cryptoCurrency, + required NodeFormData nodeFormData, + }) { + return NodeConnectionTestInvocation( + cryptoCurrency: cryptoCurrency, + name: nodeFormData.name, + host: nodeFormData.host, + login: nodeFormData.login, + password: nodeFormData.password, + port: nodeFormData.port, + useSSL: nodeFormData.useSSL, + isFailover: nodeFormData.isFailover, + trusted: nodeFormData.trusted, + netOption: nodeFormData.netOption, + ); + } + + final CryptoCurrency cryptoCurrency; + final String? name; + final String? host; + final String? login; + final String? password; + final int? port; + final bool? useSSL; + final bool? isFailover; + final bool? trusted; + final TorPlainNetworkOption? netOption; +} + +typedef PlatformNodeConnectionHandler = + FutureOr Function(NodeConnectionTestInvocation invocation); + +class RecordingFakeSecureStorage extends FakeSecureStorage { + final List readKeys = []; + final List writtenKeys = []; + final List deletedKeys = []; + + @override + Future read({ + required String key, + IOSOptions? iOptions, + AndroidOptions? aOptions, + LinuxOptions? lOptions, + WebOptions? webOptions, + MacOsOptions? mOptions, + WindowsOptions? wOptions, + }) { + readKeys.add(key); + return super.read( + key: key, + iOptions: iOptions, + aOptions: aOptions, + lOptions: lOptions, + webOptions: webOptions, + mOptions: mOptions, + wOptions: wOptions, + ); + } + + @override + Future write({ + required String key, + required String? value, + IOSOptions? iOptions, + AndroidOptions? aOptions, + LinuxOptions? lOptions, + WebOptions? webOptions, + MacOsOptions? mOptions, + WindowsOptions? wOptions, + }) { + writtenKeys.add(key); + return super.write( + key: key, + value: value, + iOptions: iOptions, + aOptions: aOptions, + lOptions: lOptions, + webOptions: webOptions, + mOptions: mOptions, + wOptions: wOptions, + ); + } + + @override + Future delete({ + required String key, + IOSOptions? iOptions, + AndroidOptions? aOptions, + LinuxOptions? lOptions, + WebOptions? webOptions, + MacOsOptions? mOptions, + WindowsOptions? wOptions, + }) { + deletedKeys.add(key); + return super.delete( + key: key, + iOptions: iOptions, + aOptions: aOptions, + lOptions: lOptions, + webOptions: webOptions, + mOptions: mOptions, + wOptions: wOptions, + ); + } +} + +class PlatformTestOverrides { + const PlatformTestOverrides._({ + required this.secureStorage, + required this.connectionInvocations, + required this.overrides, + }); + + final RecordingFakeSecureStorage secureStorage; + final List connectionInvocations; + final List overrides; +} + +Future createPlatformTestOverrides({ + Map secureStorageEntries = const {}, + bool connectionResult = true, + PlatformNodeConnectionHandler? onTestNodeConnection, +}) async { + final secureStorage = RecordingFakeSecureStorage(); + for (final entry in secureStorageEntries.entries) { + await secureStorage.write(key: entry.key, value: entry.value); + } + + final connectionInvocations = []; + + return PlatformTestOverrides._( + secureStorage: secureStorage, + connectionInvocations: connectionInvocations, + overrides: [ + secureStoreProvider.overrideWithValue(secureStorage), + testNodeConnectionProvider.overrideWithValue(({ + required BuildContext context, + required NodeFormData nodeFormData, + required CryptoCurrency cryptoCurrency, + void Function(NodeFormData)? onSuccess, + }) async { + final invocation = NodeConnectionTestInvocation.fromFormData( + cryptoCurrency: cryptoCurrency, + nodeFormData: nodeFormData, + ); + connectionInvocations.add(invocation); + + final result = onTestNodeConnection != null + ? await onTestNodeConnection(invocation) + : connectionResult; + + if (result) { + onSuccess?.call(nodeFormData); + } + + return result; + }), + ], + ); +} diff --git a/test/widget_tests/transaction_card_test.mocks.dart b/test/widget_tests/transaction_card_test.mocks.dart index 3db3e7075e..20f8278524 100644 --- a/test/widget_tests/transaction_card_test.mocks.dart +++ b/test/widget_tests/transaction_card_test.mocks.dart @@ -1724,6 +1724,30 @@ class MockMainDB extends _i1.Mock implements _i3.MainDB { returnValueForMissingStub: _i10.Future.value(), ) as _i10.Future); + + @override + List<_i28.ShopInBitTicket> getShopInBitTickets() => + (super.noSuchMethod( + Invocation.method(#getShopInBitTickets, []), + returnValue: <_i28.ShopInBitTicket>[], + ) + as List<_i28.ShopInBitTicket>); + + @override + _i10.Future putShopInBitTicket(_i28.ShopInBitTicket? ticket) => + (super.noSuchMethod( + Invocation.method(#putShopInBitTicket, [ticket]), + returnValue: _i10.Future.value(0), + ) + as _i10.Future); + + @override + _i10.Future deleteShopInBitTicket(String? ticketId) => + (super.noSuchMethod( + Invocation.method(#deleteShopInBitTicket, [ticketId]), + returnValue: _i10.Future.value(false), + ) + as _i10.Future); } /// A class which mocks [IThemeAssets]. diff --git a/test/widget_tests/wallet_info_row/sub_widgets/wallet_info_row_balance_future_test.mocks.dart b/test/widget_tests/wallet_info_row/sub_widgets/wallet_info_row_balance_future_test.mocks.dart index 94f1c27403..ecc2874ec6 100644 --- a/test/widget_tests/wallet_info_row/sub_widgets/wallet_info_row_balance_future_test.mocks.dart +++ b/test/widget_tests/wallet_info_row/sub_widgets/wallet_info_row_balance_future_test.mocks.dart @@ -4,10 +4,11 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i8; -import 'dart:ui' as _i12; +import 'dart:ui' as _i13; import 'package:mockito/mockito.dart' as _i1; import 'package:stackwallet/db/isar/main_db.dart' as _i3; +import 'package:stackwallet/models/epicbox_server_model.dart' as _i12; import 'package:stackwallet/models/node_model.dart' as _i11; import 'package:stackwallet/services/node_service.dart' as _i2; import 'package:stackwallet/services/wallets.dart' as _i7; @@ -298,6 +299,64 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { ) as _i8.Future); + @override + _i8.Future updateDefaultEpicBoxes() => + (super.noSuchMethod( + Invocation.method(#updateDefaultEpicBoxes, []), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future setPrimaryEpicBox({ + required _i12.EpicBoxServerModel? epicBox, + bool? shouldNotifyListeners = false, + }) => + (super.noSuchMethod( + Invocation.method(#setPrimaryEpicBox, [], { + #epicBox: epicBox, + #shouldNotifyListeners: shouldNotifyListeners, + }), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + List<_i12.EpicBoxServerModel> getEpicBoxes() => + (super.noSuchMethod( + Invocation.method(#getEpicBoxes, []), + returnValue: <_i12.EpicBoxServerModel>[], + ) + as List<_i12.EpicBoxServerModel>); + + @override + _i12.EpicBoxServerModel? getEpicBoxById({required String? id}) => + (super.noSuchMethod(Invocation.method(#getEpicBoxById, [], {#id: id})) + as _i12.EpicBoxServerModel?); + + @override + _i8.Future addEpicBox( + _i12.EpicBoxServerModel? epicBox, + bool? shouldNotifyListeners, + ) => + (super.noSuchMethod( + Invocation.method(#addEpicBox, [epicBox, shouldNotifyListeners]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + + @override + _i8.Future deleteEpicBox(String? id, bool? shouldNotifyListeners) => + (super.noSuchMethod( + Invocation.method(#deleteEpicBox, [id, shouldNotifyListeners]), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) + as _i8.Future); + @override _i8.Future updateCommunityNodes() => (super.noSuchMethod( @@ -308,13 +367,13 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { as _i8.Future); @override - void addListener(_i12.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i13.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#addListener, [listener]), returnValueForMissingStub: null, ); @override - void removeListener(_i12.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i13.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#removeListener, [listener]), returnValueForMissingStub: null, ); diff --git a/test/widget_tests/wallet_info_row/wallet_info_row_test.mocks.dart b/test/widget_tests/wallet_info_row/wallet_info_row_test.mocks.dart index 4aad121c69..c2139bdfd6 100644 --- a/test/widget_tests/wallet_info_row/wallet_info_row_test.mocks.dart +++ b/test/widget_tests/wallet_info_row/wallet_info_row_test.mocks.dart @@ -5,10 +5,11 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i9; import 'dart:typed_data' as _i14; -import 'dart:ui' as _i16; +import 'dart:ui' as _i17; import 'package:mockito/mockito.dart' as _i1; import 'package:stackwallet/db/isar/main_db.dart' as _i3; +import 'package:stackwallet/models/epicbox_server_model.dart' as _i16; import 'package:stackwallet/models/isar/stack_theme.dart' as _i13; import 'package:stackwallet/models/node_model.dart' as _i15; import 'package:stackwallet/networking/http.dart' as _i6; @@ -414,6 +415,64 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { ) as _i9.Future); + @override + _i9.Future updateDefaultEpicBoxes() => + (super.noSuchMethod( + Invocation.method(#updateDefaultEpicBoxes, []), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) + as _i9.Future); + + @override + _i9.Future setPrimaryEpicBox({ + required _i16.EpicBoxServerModel? epicBox, + bool? shouldNotifyListeners = false, + }) => + (super.noSuchMethod( + Invocation.method(#setPrimaryEpicBox, [], { + #epicBox: epicBox, + #shouldNotifyListeners: shouldNotifyListeners, + }), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) + as _i9.Future); + + @override + List<_i16.EpicBoxServerModel> getEpicBoxes() => + (super.noSuchMethod( + Invocation.method(#getEpicBoxes, []), + returnValue: <_i16.EpicBoxServerModel>[], + ) + as List<_i16.EpicBoxServerModel>); + + @override + _i16.EpicBoxServerModel? getEpicBoxById({required String? id}) => + (super.noSuchMethod(Invocation.method(#getEpicBoxById, [], {#id: id})) + as _i16.EpicBoxServerModel?); + + @override + _i9.Future addEpicBox( + _i16.EpicBoxServerModel? epicBox, + bool? shouldNotifyListeners, + ) => + (super.noSuchMethod( + Invocation.method(#addEpicBox, [epicBox, shouldNotifyListeners]), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) + as _i9.Future); + + @override + _i9.Future deleteEpicBox(String? id, bool? shouldNotifyListeners) => + (super.noSuchMethod( + Invocation.method(#deleteEpicBox, [id, shouldNotifyListeners]), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) + as _i9.Future); + @override _i9.Future updateCommunityNodes() => (super.noSuchMethod( @@ -424,13 +483,13 @@ class MockNodeService extends _i1.Mock implements _i2.NodeService { as _i9.Future); @override - void addListener(_i16.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i17.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#addListener, [listener]), returnValueForMissingStub: null, ); @override - void removeListener(_i16.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i17.VoidCallback? listener) => super.noSuchMethod( Invocation.method(#removeListener, [listener]), returnValueForMissingStub: null, );