Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .github/workflows/firebase-hosting-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ jobs:
# run: dart pub global activate melos
# - name: Bootstrap workspace
# run: melos bootstrap
# - name: Run dry web build to generate assets (expected to fail)
# run: cd packages/komodo_defi_sdk/example && flutter build web --release || echo "Dry build completed (failure expected)"
- name: Run dry web build to generate assets (expected to fail)
run: cd packages/komodo_defi_sdk/example && flutter build web --release || echo "Dry build completed (failure expected)"
- name: Build SDK example web
run: cd packages/komodo_defi_sdk/example && flutter build web --release
- uses: FirebaseExtended/action-hosting-deploy@v0
Expand Down
1 change: 1 addition & 0 deletions packages/komodo_coins/lib/komodo_coins.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/// TODO! Library description
library komodo_coins;

export 'src/asset_filter.dart';
export 'src/komodo_coins_base.dart';

/// A Calculator.
Expand Down
69 changes: 69 additions & 0 deletions packages/komodo_coins/lib/src/asset_filter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import 'package:equatable/equatable.dart';
import 'package:komodo_defi_types/komodo_defi_type_utils.dart';
import 'package:komodo_defi_types/komodo_defi_types.dart';

/// Strategy interface for filtering assets based on coin configuration.
abstract class AssetFilterStrategy extends Equatable {
const AssetFilterStrategy(this.strategyId);

/// A unique id for the strategy used for comparison and caching.
final String strategyId;

/// Returns `true` if the asset should be included.
bool shouldInclude(Asset asset, JsonMap coinConfig);

@override
List<Object?> get props => [strategyId];
}

/// Default strategy that includes all assets.
class NoAssetFilterStrategy extends AssetFilterStrategy {
const NoAssetFilterStrategy() : super('none');

@override
bool shouldInclude(Asset asset, JsonMap coinConfig) => true;
}

/// Filters assets that are not currently supported on Trezor.
/// This includes assets that are not UTXO-based or EVM-based tokens.
/// ETH, AVAX, BNB, FTM, etc. are excluded as they currently fail to
/// activate on Trezor.
/// ERC20, Arbitrum, and MATIC explicitly do not support Trezor via KDF
/// at this time, so they are also excluded.
class TrezorAssetFilterStrategy extends AssetFilterStrategy {
const TrezorAssetFilterStrategy() : super('trezor');

@override
bool shouldInclude(Asset asset, JsonMap coinConfig) {
final subClass = asset.protocol.subClass;

// AVAX, BNB, ETH, FTM, etc. currently fail to activate on Trezor,
// so we exclude them from the Trezor asset list.
return subClass == CoinSubClass.utxo ||
subClass == CoinSubClass.smartChain ||
subClass == CoinSubClass.qrc20;
}
}

/// Filters out assets that are not UTXO-based chains.
class UtxoAssetFilterStrategy extends AssetFilterStrategy {
const UtxoAssetFilterStrategy() : super('utxo');

@override
bool shouldInclude(Asset asset, JsonMap coinConfig) {
final subClass = asset.protocol.subClass;
return subClass == CoinSubClass.utxo || subClass == CoinSubClass.smartChain;
}
}

/// Filters assets that are EVM-based tokens.
/// This includes various EVM-compatible chains like Ethereum, Binance, etc.
/// This strategy is necessary for external wallets like Metamask or
/// WalletConnect.
class EvmAssetFilterStrategy extends AssetFilterStrategy {
const EvmAssetFilterStrategy() : super('evm');

@override
bool shouldInclude(Asset asset, JsonMap coinConfig) =>
evmCoinSubClasses.contains(asset.protocol.subClass);
}
33 changes: 31 additions & 2 deletions packages/komodo_coins/lib/src/komodo_coins_base.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:komodo_coins/src/config_transform.dart';
import 'package:komodo_coins/src/asset_filter.dart';
import 'package:komodo_defi_types/komodo_defi_type_utils.dart';
import 'package:komodo_defi_types/komodo_defi_types.dart';

Expand All @@ -17,6 +18,7 @@ class KomodoCoins {
}

Map<AssetId, Asset>? _assets;
final Map<String, Map<AssetId, Asset>> _filterCache = {};

@mustCallSuper
Future<void> init() async {
Expand Down Expand Up @@ -76,8 +78,10 @@ class KomodoCoins {

try {
// Parse all possible AssetIds for this coin
final assetIds =
AssetId.parseAllTypes(coinData, knownIds: platformIds).map(
final assetIds = AssetId.parseAllTypes(
coinData,
knownIds: platformIds,
).map(
(id) => id.isChildAsset
? AssetId.parse(coinData, knownIds: platformIds)
: id,
Expand Down Expand Up @@ -111,6 +115,31 @@ class KomodoCoins {
coinData.valueOrNull<String>('parent_coin') == null;
}

/// Returns the assets filtered using the provided [strategy].
///
/// This allows higher-level components, such as [AssetManager], to tailor
/// the visible asset list to the active authentication context. For example,
/// a hardware wallet may only support a subset of coins, which can be
/// enforced by supplying an appropriate [AssetFilterStrategy].
Map<AssetId, Asset> filteredAssets(AssetFilterStrategy strategy) {
if (!isInitialized) {
throw StateError('Assets have not been initialized. Call init() first.');
}
final cacheKey = strategy.strategyId;
final cached = _filterCache[cacheKey];
if (cached != null) return cached;

final result = <AssetId, Asset>{};
for (final entry in _assets!.entries) {
final config = entry.value.protocol.config;
if (strategy.shouldInclude(entry.value, config)) {
result[entry.key] = entry.value;
}
}
_filterCache[cacheKey] = result;
return result;
}

// Helper methods
Asset? findByTicker(String ticker, CoinSubClass subClass) {
return all.entries
Expand Down
76 changes: 76 additions & 0 deletions packages/komodo_coins/test/asset_filter_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:komodo_coins/src/asset_filter.dart';
import 'package:komodo_defi_types/komodo_defi_types.dart';

void main() {
group('Asset filtering', () {
final btcConfig = {
'coin': 'BTC',
'fname': 'Bitcoin',
'chain_id': 0,
'type': 'UTXO',
'protocol': {'type': 'UTXO'},
'is_testnet': false,
'trezor_coin': 'Bitcoin',
};

final ethConfig = {
'coin': 'ETH',
'fname': 'Ethereum',
'chain_id': 1,
'type': 'ERC-20',
'protocol': {
'type': 'ETH',
'protocol_data': {'chain_id': 1},
},
'nodes': [
{'url': 'https://rpc'},
],
'swap_contract_address': '0xabc',
'fallback_swap_contract': '0xdef',
};

final btc = Asset.fromJson(btcConfig);
final eth = Asset.fromJson(ethConfig);

test('Trezor filter excludes assets missing trezor_coin', () {
const filter = TrezorAssetFilterStrategy();
expect(filter.shouldInclude(btc, btc.protocol.config), isTrue);
expect(filter.shouldInclude(eth, eth.protocol.config), isFalse);

final assets = {btc.id: btc, eth.id: eth};
final filtered = <AssetId, Asset>{};
for (final entry in assets.entries) {
if (filter.shouldInclude(entry.value, entry.value.protocol.config)) {
filtered[entry.key] = entry.value;
}
}

expect(filtered.containsKey(btc.id), isTrue);
expect(filtered.containsKey(eth.id), isFalse);
});

test('Trezor filter ignores empty trezor_coin field', () {
final cfg = Map<String, dynamic>.from(btcConfig)..['trezor_coin'] = '';
final asset = Asset.fromJson(cfg);
const filter = TrezorAssetFilterStrategy();
expect(filter.shouldInclude(asset, asset.protocol.config), isFalse);
});

test('UTXO filter only includes utxo assets', () {
const filter = UtxoAssetFilterStrategy();
expect(filter.shouldInclude(btc, btc.protocol.config), isTrue);
expect(filter.shouldInclude(eth, eth.protocol.config), isFalse);
});

test('UTXO filter accepts smartChain subclass', () {
final cfg = Map<String, dynamic>.from(btcConfig)
..['type'] = 'SMART_CHAIN'
..['protocol'] = {'type': 'UTXO'};
final asset = Asset.fromJson(cfg);
const filter = UtxoAssetFilterStrategy();
expect(asset.protocol.subClass, CoinSubClass.smartChain);
expect(filter.shouldInclude(asset, asset.protocol.config), isTrue);
});
});
}
2 changes: 1 addition & 1 deletion packages/komodo_defi_framework/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ dependencies:
path: ../komodo_defi_types
komodo_wallet_build_transformer:
path: ../komodo_wallet_build_transformer
logging: ^1.2.0
logging: ^1.3.0
mutex: ^3.1.0
path: any
path_provider: ^2.1.4
Expand Down
Loading
Loading