Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4250479
adds a disable geoblock variable
gcharang Aug 20, 2025
7d657b5
Merge branch 'dev' into allow-disable-geoblock
CharlVS Aug 22, 2025
e76d052
Refactor trading status to include disallowed assets and features
cursoragent Sep 16, 2025
94342f6
Refactor: Use enum for disallowed features
cursoragent Sep 16, 2025
e2ec258
Refactor: Use AssetId for disallowed assets in TradingStatus
cursoragent Sep 16, 2025
4a3c0c6
Merge branch 'dev' into cursor/update-bouncer-for-privacy-coin-blocki…
CharlVS Sep 16, 2025
0231242
chore: generate locale keys
CharlVS Sep 16, 2025
af2558e
Refactor: Update GEO_BLOCK logic and Bouncer API URL
cursoragent Sep 17, 2025
a694f99
refactor(trading-status): derive tradingEnabled from disallowedFeatur…
CharlVS Sep 17, 2025
4230993
fix(trading-status): block trading when disallowed_features is missin…
CharlVS Sep 17, 2025
45e5cc2
style: format project with dart format per repo rules
CharlVS Sep 17, 2025
0397ba7
docs: update AGENTS.md
CharlVS Sep 17, 2025
90c677a
Merge branch 'dev' into cursor/update-bouncer-for-privacy-coin-blocki…
CharlVS Sep 19, 2025
2ad6e58
fix(privacy-coin-blocking): update UI and state handling for new boun…
CharlVS Sep 19, 2025
52e47f8
chore: merge origin/cursor/update-bouncer-for-privacy-coin-blocking-b…
CharlVS Sep 19, 2025
dc2e78d
revert: formatting-only commit 45e5cc2f (restore pre-format state)\n\…
CharlVS Sep 19, 2025
2667ff9
fix(trading-status): hide blocked coins in coins repo and coins bloc …
takenagain Sep 23, 2025
3660b90
merge: dev into cursor/update-bouncer-for-privacy-coin-blocking-b618 …
CharlVS Sep 23, 2025
04636b4
Merge branch 'dev' into cursor/update-bouncer-for-privacy-coin-blocki…
CharlVS Sep 24, 2025
d787650
Merge branch 'dev' into cursor/update-bouncer-for-privacy-coin-blocki…
CharlVS Sep 25, 2025
286c082
Merge branch 'dev' of https://github.com/KomodoPlatform/komodo-wallet…
CharlVS Sep 30, 2025
b781bf3
Merge branch 'dev' into cursor/update-bouncer-for-privacy-coin-blocki…
CharlVS Sep 30, 2025
30c781a
fix: guard geo restricted assets
CharlVS Sep 30, 2025
fdd790f
Merge branch 'allow-disable-geoblock' of https://github.com/KomodoPla…
CharlVS Sep 30, 2025
e49b09e
fix(geo): ensure geo-blocked assets fully filtered across all flows
CharlVS Sep 30, 2025
fdba9b4
docs(geo): add TODO comments for future UX optimization
CharlVS Sep 30, 2025
5b84662
fix: remove geo-block filter from private key fetch to enable UI toggle
CharlVS Sep 30, 2025
a5efe93
fix(bridge): ensure bridge lists rebuild when trading status changes
CharlVS Sep 30, 2025
23efaa9
fix(dex): ensure coin/order tables rebuild when trading status changes
CharlVS Sep 30, 2025
59afc91
fix: remove Trello dependency from FEEDBACK_API_KEY configuration
CharlVS Sep 30, 2025
0025166
Revert "fix: remove Trello dependency from FEEDBACK_API_KEY configura…
CharlVS Sep 30, 2025
bf9ef01
fix(trading-status): filter null assets in canTradeAssets check
CharlVS Sep 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,7 @@ If the above fails due to the offline environment, add the `--offline` flag.

## Static Analysis and Formatting

Run analysis and formatting before committing code:

```bash
flutter analyze

dart format .
```
Run analysis and formatting (only on changed files) before committing code:

## Running Tests

Expand Down Expand Up @@ -48,6 +42,6 @@ The KDF API documentation can be found in the root folder at `/KDF_API_DOCUMENTA

## PR Guidance

Commit messages should be clear and descriptive. When opening a pull request, summarize the purpose of the change and reference related issues when appropriate. Ensure commit messages follow the Conventional Commits standard as described in the standards section below.
Commit messages should be clear and descriptive. When opening a pull request, summarize the purpose of the change and reference related issues when appropriate. Ensure commit messages and PR title follow the Conventional Commits standard as described in the standards section below.

<!-- The following sections are automatically generated during environment setup -->
1 change: 1 addition & 0 deletions assets/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,7 @@
"trend7d": "7d trend",
"tradingDisabledTooltip": "Trading features are currently disabled",
"tradingDisabled": "Trading unavailable in your location",
"includeBlockedAssets": "Include blocked assets",
"unbanPubkeysResults": "Unban Pubkeys Results",
"unbannedPubkeys": {
"zero": "{} Unbanned Pubkeys",
Expand Down
14 changes: 8 additions & 6 deletions lib/bloc/app_bloc_root.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import 'package:web_dex/bloc/system_health/system_clock_repository.dart';
import 'package:web_dex/bloc/system_health/system_health_bloc.dart';
import 'package:web_dex/bloc/taker_form/taker_bloc.dart';
import 'package:web_dex/bloc/trading_status/trading_status_bloc.dart';
import 'package:web_dex/bloc/trading_status/trading_status_repository.dart';
import 'package:web_dex/bloc/trading_status/trading_status_service.dart';
import 'package:web_dex/bloc/transaction_history/transaction_history_bloc.dart';
import 'package:web_dex/bloc/transaction_history/transaction_history_repo.dart';
import 'package:web_dex/bloc/version_info/version_info_bloc.dart';
Expand Down Expand Up @@ -176,13 +176,15 @@ class AppBlocRoot extends StatelessWidget {
RepositoryProvider(
create: (_) => KmdRewardsBloc(coinsRepository, mm2Api),
),
RepositoryProvider(create: (_) => TradingStatusRepository()),
],
child: MultiBlocProvider(
providers: [
BlocProvider(
create: (context) =>
CoinsBloc(komodoDefiSdk, coinsRepository)..add(CoinsStarted()),
create: (context) => CoinsBloc(
komodoDefiSdk,
coinsRepository,
context.read<TradingStatusService>(),
)..add(CoinsStarted()),
),
BlocProvider<PriceChartBloc>(
create: (context) => PriceChartBloc(komodoDefiSdk)
Expand Down Expand Up @@ -268,8 +270,8 @@ class AppBlocRoot extends StatelessWidget {
BlocProvider<TradingStatusBloc>(
lazy: false,
create: (context) =>
TradingStatusBloc(context.read<TradingStatusRepository>())
..add(TradingStatusCheckRequested()),
TradingStatusBloc(context.read<TradingStatusService>())
..add(TradingStatusWatchStarted()),
),
BlocProvider<SystemHealthBloc>(
create: (_) =>
Expand Down
42 changes: 37 additions & 5 deletions lib/bloc/auth_bloc/auth_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:komodo_defi_types/komodo_defi_types.dart';
import 'package:logging/logging.dart';
import 'package:web_dex/app_config/app_config.dart';
import 'package:web_dex/bloc/settings/settings_repository.dart';
import 'package:web_dex/bloc/trading_status/trading_status_service.dart';
import 'package:web_dex/blocs/wallets_repository.dart';
import 'package:web_dex/model/authorize_mode.dart';
import 'package:web_dex/model/kdf_auth_metadata_extension.dart';
Expand All @@ -23,8 +24,12 @@ part 'trezor_auth_mixin.dart';
class AuthBloc extends Bloc<AuthBlocEvent, AuthBlocState> with TrezorAuthMixin {
/// Handles [AuthBlocEvent]s and emits [AuthBlocState]s.
/// [_kdfSdk] is an instance of [KomodoDefiSdk] used for authentication.
AuthBloc(this._kdfSdk, this._walletsRepository, this._settingsRepository)
: super(AuthBlocState.initial()) {
AuthBloc(
this._kdfSdk,
this._walletsRepository,
this._settingsRepository,
this._tradingStatusService,
) : super(AuthBlocState.initial()) {
on<AuthModeChanged>(_onAuthChanged);
on<AuthStateClearRequested>(_onClearState);
on<AuthSignOutRequested>(_onLogout);
Expand All @@ -41,13 +46,34 @@ class AuthBloc extends Bloc<AuthBlocEvent, AuthBlocState> with TrezorAuthMixin {
final KomodoDefiSdk _kdfSdk;
final WalletsRepository _walletsRepository;
final SettingsRepository _settingsRepository;
final TradingStatusService _tradingStatusService;
StreamSubscription<KdfUser?>? _authChangesSubscription;
@override
final _log = Logger('AuthBloc');

@override
KomodoDefiSdk get _sdk => _kdfSdk;

/// Filters out geo-blocked assets from a list of coin IDs.
/// This ensures that blocked assets are not added to wallet metadata during
/// registration or restoration.
///
/// TODO: UX Improvement - For faster wallet creation/restoration, consider
/// adding all default coins to metadata initially, then removing blocked ones
/// when bouncer status is confirmed. This would require:
/// 1. Reactive metadata updates when trading status changes
/// 2. Coordinated cleanup across wallet metadata and activated coins
/// 3. Handling edge cases where user manually re-adds a blocked coin
/// See TradingStatusService._currentStatus for related startup optimizations.
@override
List<String> _filterBlockedAssets(List<String> coinIds) {
return coinIds.where((coinId) {
final assets = _kdfSdk.assets.findAssetsByConfigId(coinId);
if (assets.isEmpty) return true; // Keep unknown assets for now
return !_tradingStatusService.isAssetBlocked(assets.single.id);
}).toList();
}

@override
Future<void> close() async {
await _authChangesSubscription?.cancel();
Expand Down Expand Up @@ -186,7 +212,9 @@ class AuthBloc extends Bloc<AuthBlocEvent, AuthBlocState> with TrezorAuthMixin {
);
await _kdfSdk.setWalletType(event.wallet.config.type);
await _kdfSdk.confirmSeedBackup(hasBackup: false);
await _kdfSdk.addActivatedCoins(enabledByDefaultCoins);
// Filter out geo-blocked assets from default coins before adding to wallet
final allowedDefaultCoins = _filterBlockedAssets(enabledByDefaultCoins);
await _kdfSdk.addActivatedCoins(allowedDefaultCoins);

final currentUser = await _kdfSdk.auth.currentUser;
if (currentUser == null) {
Expand Down Expand Up @@ -242,14 +270,18 @@ class AuthBloc extends Bloc<AuthBlocEvent, AuthBlocState> with TrezorAuthMixin {
);
await _kdfSdk.setWalletType(event.wallet.config.type);
await _kdfSdk.confirmSeedBackup(hasBackup: event.wallet.config.hasBackup);
await _kdfSdk.addActivatedCoins(enabledByDefaultCoins);
// Filter out geo-blocked assets from default coins before adding to wallet
final allowedDefaultCoins = _filterBlockedAssets(enabledByDefaultCoins);
await _kdfSdk.addActivatedCoins(allowedDefaultCoins);
if (event.wallet.config.activatedCoins.isNotEmpty) {
// Seed import files and legacy wallets may contain removed or unsupported
// coins, so we filter them out before adding them to the wallet metadata.
final availableWalletCoins = _filterOutUnsupportedCoins(
event.wallet.config.activatedCoins,
);
await _kdfSdk.addActivatedCoins(availableWalletCoins);
// Also filter out geo-blocked assets from restored wallet coins
final allowedWalletCoins = _filterBlockedAssets(availableWalletCoins);
await _kdfSdk.addActivatedCoins(allowedWalletCoins);
}

// Delete legacy wallet on successful restoration & login to avoid
Expand Down
10 changes: 8 additions & 2 deletions lib/bloc/auth_bloc/trezor_auth_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ part of 'auth_bloc.dart';
/// Mixin that exposes Trezor authentication helpers for [AuthBloc].
mixin TrezorAuthMixin on Bloc<AuthBlocEvent, AuthBlocState> {
KomodoDefiSdk get _sdk;
final _log = Logger('TrezorAuthMixin');
Logger get _log;

/// Registers handlers for Trezor specific events.
void setupTrezorEventHandlers() {
Expand All @@ -17,6 +17,10 @@ mixin TrezorAuthMixin on Bloc<AuthBlocEvent, AuthBlocState> {
/// to authentication state changes.
void _listenToAuthStateChanges();

/// Filters out geo-blocked assets from a list of coin IDs.
/// Implemented in [AuthBloc].
List<String> _filterBlockedAssets(List<String> coinIds);

Future<void> _onTrezorInitAndAuth(
AuthTrezorInitAndAuthStarted event,
Emitter<AuthBlocState> emit,
Expand Down Expand Up @@ -130,7 +134,9 @@ mixin TrezorAuthMixin on Bloc<AuthBlocEvent, AuthBlocState> {
if (authState.user!.wallet.config.activatedCoins.isEmpty) {
// If no coins are activated, we assume this is the first time
// the user is setting up their Trezor wallet.
await _sdk.addActivatedCoins(enabledByDefaultCoins);
// Filter out geo-blocked assets from default coins before adding to wallet
final allowedDefaultCoins = _filterBlockedAssets(enabledByDefaultCoins);
await _sdk.addActivatedCoins(allowedDefaultCoins);
}

// Refresh the current user to pull in the updated wallet metadata
Expand Down
45 changes: 37 additions & 8 deletions lib/bloc/coins_bloc/coins_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:komodo_defi_types/komodo_defi_types.dart';
import 'package:logging/logging.dart';
import 'package:web_dex/app_config/app_config.dart';
import 'package:web_dex/bloc/coins_bloc/coins_repo.dart';
import 'package:web_dex/bloc/trading_status/trading_status_service.dart';
import 'package:web_dex/model/cex_price.dart';
import 'package:web_dex/model/coin.dart';
import 'package:web_dex/model/kdf_auth_metadata_extension.dart';
Expand All @@ -20,7 +21,8 @@ part 'coins_state.dart';

/// Responsible for coin activation, deactivation, syncing, and fiat price
class CoinsBloc extends Bloc<CoinsEvent, CoinsState> {
CoinsBloc(this._kdfSdk, this._coinsRepo) : super(CoinsState.initial()) {
CoinsBloc(this._kdfSdk, this._coinsRepo, this._tradingStatusService)
: super(CoinsState.initial()) {
on<CoinsStarted>(_onCoinsStarted, transformer: droppable());
// TODO: move auth listener to ui layer: bloclistener should fire auth events
on<CoinsBalanceMonitoringStarted>(_onCoinsBalanceMonitoringStarted);
Expand All @@ -40,6 +42,7 @@ class CoinsBloc extends Bloc<CoinsEvent, CoinsState> {

final KomodoDefiSdk _kdfSdk;
final CoinsRepo _coinsRepo;
final TradingStatusService _tradingStatusService;

final _log = Logger('CoinsBloc');

Expand Down Expand Up @@ -82,6 +85,18 @@ class CoinsBloc extends Bloc<CoinsEvent, CoinsState> {
CoinsStarted event,
Emitter<CoinsState> emit,
) async {
// Wait for trading status service to receive initial status before
// populating coins list. This ensures geo-blocked assets are properly
// filtered from the start, preventing them from appearing in the UI
// before filtering is applied.
//
// TODO: UX Improvement - For faster startup, populate coins immediately
// and reactively filter when trading status updates arrive. This would
// eliminate startup delay (~100-500ms) but requires UI to handle dynamic
// removal of blocked assets. See TradingStatusService._currentStatus for
// related trade-offs.
await _tradingStatusService.initialStatusReady;

emit(state.copyWith(coins: _coinsRepo.getKnownCoinsMap()));

final existingUser = await _kdfSdk.auth.currentUser;
Expand Down Expand Up @@ -320,9 +335,17 @@ class CoinsBloc extends Bloc<CoinsEvent, CoinsState> {

// Start off by emitting the newly activated coins so that they all appear
// in the list at once, rather than one at a time as they are activated
final coinsToActivate = currentWallet.config.activatedCoins;
emit(_prePopulateListWithActivatingCoins(coinsToActivate));
await _activateCoins(coinsToActivate, emit);
final allCoinsToActivate = currentWallet.config.activatedCoins;

// Filter out blocked coins before activation
final allowedCoins = allCoinsToActivate.where((coinId) {
final assets = _kdfSdk.assets.findAssetsByConfigId(coinId);
if (assets.isEmpty) return false;
return !_tradingStatusService.isAssetBlocked(assets.single.id);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: Single Asset Assumption Fails on Multiple Results

Using .single on the result of _kdfSdk.assets.findAssetsByConfigId assumes a single asset, but this method can return multiple. If multiple assets are found for a coin ID, it will throw an exception and halt coin activation.

Additional Locations (1)

Fix in Cursor Fix in Web


emit(_prePopulateListWithActivatingCoins(allowedCoins));
await _activateCoins(allowedCoins, emit);

add(CoinsBalancesRefreshed());
add(CoinsBalanceMonitoringStarted());
Expand Down Expand Up @@ -361,11 +384,17 @@ class CoinsBloc extends Bloc<CoinsEvent, CoinsState> {
// activation loops for assets not supported by the SDK.this may happen if the wallet
// has assets that were removed from the SDK or the config has unsupported default
// assets.
// This is also important for coin delistings.
final enableFutures = coins
final availableAssets = coins
.map((coin) => _kdfSdk.assets.findAssetsByConfigId(coin))
.where((assets) => assets.isNotEmpty)
.map((assets) => assets.single)
.where((assetsSet) => assetsSet.isNotEmpty)
.map((assetsSet) => assetsSet.single);

// Filter out blocked assets
final coinsToActivate = _tradingStatusService.filterAllowedAssets(
availableAssets.toList(),
);

final enableFutures = coinsToActivate
.map((asset) => _coinsRepo.activateAssetsSync([asset]))
.toList();

Expand Down
46 changes: 38 additions & 8 deletions lib/bloc/coins_bloc/coins_repo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import 'package:komodo_ui/komodo_ui.dart';
import 'package:logging/logging.dart';
import 'package:web_dex/app_config/app_config.dart' show excludedAssetList;
import 'package:web_dex/bloc/coins_bloc/asset_coin_extension.dart';
import 'package:web_dex/bloc/trading_status/trading_status_service.dart'
show TradingStatusService;
import 'package:web_dex/generated/codegen_loader.g.dart';
import 'package:web_dex/mm2/mm2.dart';
import 'package:web_dex/mm2/mm2_api/rpc/base.dart';
Expand All @@ -29,9 +31,13 @@ import 'package:web_dex/model/wallet.dart';
import 'package:web_dex/model/withdraw_details/withdraw_details.dart';

class CoinsRepo {
CoinsRepo({required KomodoDefiSdk kdfSdk, required MM2 mm2})
: _kdfSdk = kdfSdk,
_mm2 = mm2 {
CoinsRepo({
required KomodoDefiSdk kdfSdk,
required MM2 mm2,
required TradingStatusService tradingStatusService,
}) : _kdfSdk = kdfSdk,
_mm2 = mm2,
_tradingStatusService = tradingStatusService {
enabledAssetsChanges = StreamController<Coin>.broadcast(
onListen: () => _enabledAssetListenerCount += 1,
onCancel: () => _enabledAssetListenerCount -= 1,
Expand All @@ -40,6 +46,7 @@ class CoinsRepo {

final KomodoDefiSdk _kdfSdk;
final MM2 _mm2;
final TradingStatusService _tradingStatusService;

final _log = Logger('CoinsRepo');

Expand Down Expand Up @@ -86,6 +93,11 @@ class CoinsRepo {
// Cancel any existing subscription for this asset
_balanceWatchers[asset.id]?.cancel();

if (_tradingStatusService.isAssetBlocked(asset.id)) {
_log.info('Asset ${asset.id.id} is blocked. Skipping balance updates.');
return;
}

// Start a new subscription
_balanceWatchers[asset.id] = _kdfSdk.balances.watchBalance(asset.id).listen(
(balanceInfo) {
Expand Down Expand Up @@ -128,7 +140,11 @@ class CoinsRepo {
if (excludeExcludedAssets) {
assets.removeWhere((key, _) => excludedAssetList.contains(key.id));
}
return assets.values.map(_assetToCoinWithoutAddress).toList();
// Filter out blocked assets
final allowedAssets = _tradingStatusService.filterAllowedAssets(
assets.values.toList(),
);
return allowedAssets.map(_assetToCoinWithoutAddress).toList();
}

/// Returns a map of all known coins, optionally filtering out excluded assets.
Expand All @@ -139,8 +155,11 @@ class CoinsRepo {
if (excludeExcludedAssets) {
assets.removeWhere((key, _) => excludedAssetList.contains(key.id));
}
final allowedAssets = _tradingStatusService.filterAllowedAssets(
assets.values.toList(),
);
return Map.fromEntries(
assets.values.map(
allowedAssets.map(
(asset) => MapEntry(asset.id.id, _assetToCoinWithoutAddress(asset)),
),
);
Expand Down Expand Up @@ -180,7 +199,7 @@ class CoinsRepo {
return [];
}

return currentUser.wallet.config.activatedCoins
final walletAssets = currentUser.wallet.config.activatedCoins
.map((coinId) {
final assets = _kdfSdk.assets.findAssetsByConfigId(coinId);
if (assets.isEmpty) {
Expand All @@ -196,6 +215,10 @@ class CoinsRepo {
return assets.single;
})
.whereType<Asset>()
.toList();

return _tradingStatusService
.filterAllowedAssets(walletAssets)
.map(_assetToCoinWithoutAddress)
.toList();
}
Expand Down Expand Up @@ -564,11 +587,16 @@ class CoinsRepo {

// Filter out excluded and testnet assets, as they are not expected
// to have valid prices available at any of the providers
final validAssets = targetAssets
final filteredAssets = targetAssets
.where((asset) => !excludedAssetList.contains(asset.id.id))
.where((asset) => !asset.protocol.isTestnet)
.toList();

// Filter out blocked assets
final validAssets = _tradingStatusService.filterAllowedAssets(
filteredAssets,
);

// Process assets with bounded parallelism to avoid overwhelming providers
await _fetchAssetPricesInChunks(validAssets);

Expand Down Expand Up @@ -625,7 +653,9 @@ class CoinsRepo {
// the SDK's balance watchers to get live updates. We still
// implement it for backward compatibility.
final walletCoinsCopy = Map<String, Coin>.from(walletCoins);
final coins = walletCoinsCopy.values
final coins = _tradingStatusService
.filterAllowedAssetsMap(walletCoinsCopy, (coin) => coin.id)
.values
.where((coin) => coin.isActive)
.toList();

Expand Down
Loading
Loading