Skip to content
Merged

0.9.3 #292

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
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/foundation.dart' show kIsWeb, kIsWasm;
import 'package:komodo_defi_types/komodo_defi_type_utils.dart';
import 'package:logging/logging.dart';

/// Defines a transform that can be applied to a single coin configuration.
///
Expand Down Expand Up @@ -29,6 +30,7 @@ class CoinConfigTransformer {
transforms ??
const [
WssWebsocketTransform(),
SslElectrumTransform(),
ZhtlcLightWalletTransform(),
ParentCoinTransform(),
];
Expand Down Expand Up @@ -205,6 +207,121 @@ class WssWebsocketTransform implements CoinConfigTransform {
/// Specifies which type of Electrum servers to retain
enum ElectrumServerType { wssOnly, nonWssOnly }

/// Filters out insecure connections on non-web platforms, emulating the
/// filtering applied to the coins_config_ssl.json file in KomodoPlatform/coins
/// On non-web platforms, only SSL electrum servers and HTTPS URLs are
/// supported for security.
// TODO: move this to a build-time step via the coins config and/or the
// komodo_wallet_build_transformer package
class SslElectrumTransform implements CoinConfigTransform {
const SslElectrumTransform();

static final _log = Logger('SslElectrumTransform');

@override
/// Determines if the transform should run by checking if this is a non-web
/// platform with any of the filterable fields in the configuration.
bool needsTransform(JsonMap config) {
// Only run on non-web platforms
if (kIsWeb || kIsWasm) return false;

final electrum = config.valueOrNull<List>('electrum');
final rpcNodes = config.valueOrNull<List>('nodes');
final lightWalletServers = config.valueOrNull<List>(
'light_wallet_d_servers',
);

return electrum != null || rpcNodes != null || lightWalletServers != null;
}

@override
/// Filters entries to keep only secure connections on non-web platforms.
JsonMap transform(JsonMap config) {
final result = JsonMap.of(config);
final coin = config.valueOrNull<String>('coin') ?? 'unknown';
// Filter electrum servers - keep only SSL protocol
final electrum = config.valueOrNull<JsonList>('electrum');
if (electrum != null) {
final originalCount = electrum.length;
final filteredElectrums = electrum.where((JsonMap e) {
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

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

The where clause explicitly casts list elements to JsonMap without validation. If the electrum list contains non-map elements, this will cause a runtime type error. Consider using safer type checking:

final filteredElectrums = electrum.where((e) {
  if (e is! Map<String, dynamic>) return false;
  final protocol = (e as JsonMap).valueOrNull<String>('protocol');
  return protocol == 'SSL';
}).cast<JsonMap>().toList();

The same issue exists on line 265 for rpcNodes.

Copilot uses AI. Check for mistakes.
final protocol = e.valueOrNull<String>('protocol');
return protocol == 'SSL';
}).toList();

if (filteredElectrums.isEmpty && originalCount > 0) {
_log.warning(
'SslElectrumTransform: All $originalCount electrum servers filtered '
'out for $coin (no SSL servers available)',
);
}

result['electrum'] = filteredElectrums;
}

// Filter RPC nodes - keep only HTTPS URLs
final rpcNodes = config.valueOrNull<JsonList>('nodes');
if (rpcNodes != null) {
final originalCount = rpcNodes.length;
final filteredRpcNodes = rpcNodes.where((JsonMap node) {
final url = node.valueOrNull<String>('url');
return url != null && url.startsWith('https://');
}).toList();
Comment on lines +265 to +268
Copy link

Copilot AI Nov 13, 2025

Choose a reason for hiding this comment

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

Similar to the electrum filtering issue above, the where clause explicitly casts list elements to JsonMap without validation. If the nodes list contains non-map elements, this will cause a runtime type error. Consider using safer type checking:

final filteredRpcNodes = rpcNodes.where((node) {
  if (node is! Map<String, dynamic>) return false;
  final url = (node as JsonMap).valueOrNull<String>('url');
  return url != null && url.startsWith('https://');
}).cast<JsonMap>().toList();
Suggested change
final filteredRpcNodes = rpcNodes.where((JsonMap node) {
final url = node.valueOrNull<String>('url');
return url != null && url.startsWith('https://');
}).toList();
final filteredRpcNodes = rpcNodes.where((node) {
if (node is! Map<String, dynamic>) return false;
final url = (node as JsonMap).valueOrNull<String>('url');
return url != null && url.startsWith('https://');
}).cast<JsonMap>().toList();

Copilot uses AI. Check for mistakes.

if (filteredRpcNodes.isEmpty && originalCount > 0) {
_log.warning(
'SslElectrumTransform: All $originalCount RPC nodes filtered '
'out for $coin (no HTTPS nodes available)',
);
}

result['nodes'] = filteredRpcNodes;
}

// Filter light wallet servers - keep only HTTPS URLs
final lightWalletServers = config.valueOrNull<List>(
'light_wallet_d_servers',
);
if (lightWalletServers != null) {
final originalCount = lightWalletServers.length;
final filteredLightWalletServers = lightWalletServers.where((
dynamic server,
) {
return server is String && server.startsWith('https://');
}).toList();

if (filteredLightWalletServers.isEmpty && originalCount > 0) {
_log.warning(
'SslElectrumTransform: All $originalCount light wallet servers '
'filtered out for $coin (no HTTPS servers available)',
);
}

result['light_wallet_d_servers'] = filteredLightWalletServers;
}

// Mark coin as having insufficient secure servers if all critical servers were filtered
final hasElectrum = (result['electrum'] as List?)?.isNotEmpty ?? false;
final hasNodes = (result['nodes'] as List?)?.isNotEmpty ?? false;
final hasLightWallet =
(result['light_wallet_d_servers'] as List?)?.isNotEmpty ?? false;

// If the coin had servers but now has none, mark it
if (!hasElectrum &&
!hasNodes &&
!hasLightWallet &&
(electrum != null || rpcNodes != null || lightWalletServers != null)) {
_log.severe(
'SslElectrumTransform: Coin $coin has no secure servers available '
'after filtering - coin may not be activatable',
);
// Optionally mark the coin for exclusion or special handling
// result['_ssl_filtered_empty'] = true;
}

return result;
}
}

class ParentCoinTransform implements CoinConfigTransform {
const ParentCoinTransform();

Expand Down
23 changes: 11 additions & 12 deletions packages/komodo_defi_framework/app_build/build_config.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
{
"api": {
"api_commit_hash": "9aa41b4c741907d59e4887db08cf84fb78e967e0",
"api_commit_hash": "1874d4399fc8c084108e62eb781e3300d2606213",
"branch": "dev",
"fetch_at_build_enabled": false,
"fetch_at_build_enabled": true,
"concurrent_downloads_enabled": true,
"source_urls": [
"https://api.github.com/repos/KomodoPlatform/komodo-defi-framework",
"https://sdk.devbuilds.komodo.earth/",
"https://nebula.decker.im/"
"https://sdk.devbuilds.komodo.earth/"
],
"platforms": {
"web": {
"matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-wasm|mm2_[a-f0-9]{7,40}-wasm|mm2-[a-f0-9]{7,40}-wasm)\\.zip$",
"valid_zip_sha256_checksums": [
"f92d61595317c16b8f8294c038c7c9215aca94187e22aedc7e0adeba93c94d82"
"267de38f10e2f0a545813547cc4dd0a7160ba42da2e02cdc4391d4d4ea660526"
],
"path": "web/kdf/bin"
},
"ios": {
"matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-ios-aarch64|mm2_[a-f0-9]{7,40}-ios-aarch64|mm2-[a-f0-9]{7,40}-ios-aarch64-CI)\\.zip$",
"valid_zip_sha256_checksums": [
"4f18e9e82ca16e7b1133cabd898d7503f925be1b7d06693f687c34866617da2e"
"32b49d10acf514884d4d599bc24f12abad0f720735fe22ab19602305fde284e4"
],
"path": "ios"
},
Expand All @@ -31,35 +30,35 @@
"mac-arm64"
],
"valid_zip_sha256_checksums": [
"169db9b1410888d7da9f6a0ead2e1a05834528982cebbb70a2e2e3d01e220b69"
"99c3e3820b5a81c9e3153c4538364ea12835806b24a66b90bd6944848c46602d"
],
"path": "macos/bin"
},
"windows": {
"matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-win-x86-64|mm2_[a-f0-9]{7,40}-win-x86-64|mm2-[a-f0-9]{7,40}-Win64)\\.zip$",
"valid_zip_sha256_checksums": [
"d4f5954071df5c2c23016a69074bf19d3daeba10ab413fda2fce8205ee32184c"
"e16eb75012ffec284c865cf9eef6e4c58247227a26af0fd2e290af35342cfdd7"
],
"path": "windows/bin"
},
"android-armv7": {
"matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-android-armv7|mm2_[a-f0-9]{7,40}-android-armv7|mm2-[a-f0-9]{7,40}-android-armv7-CI)\\.zip$",
"valid_zip_sha256_checksums": [
"d964f608787683de67ddde7d827dee88185509cc5ffeb6e85d5f7e2aeba84f08"
"730d1247a1f85351cfe923b8b8c58f50b54f88062f713564d63ddc9298f8cb39"
],
"path": "android/app/src/main/cpp/libs/armeabi-v7a"
},
"android-aarch64": {
"matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-android-aarch64|mm2_[a-f0-9]{7,40}-android-aarch64|mm2-[a-f0-9]{7,40}-android-aarch64-CI)\\.zip$",
"valid_zip_sha256_checksums": [
"32927dfa36a283d344c34cd1514ca8ea3233f20752ec15b7929309cf4df969c2"
"4c5307ccdf982780498192c28e110715f2aa05f95666e864065577e2656397f6"
],
"path": "android/app/src/main/cpp/libs/arm64-v8a"
},
"linux": {
"matching_pattern": "^(?:kdf_[a-f0-9]{7,40}-linux-x86-64|mm2_[a-f0-9]{7,40}-linux-x86-64|mm2-[a-f0-9]{7,40}-Linux-Release)\\.zip$",
"valid_zip_sha256_checksums": [
"e3315a46cb9e1957206b36036954bce51d61d8c4784ed878cc93360b9d01c1cb"
"983f652d6a6d0f77924bdda4f0539aea51b46c2f4c07a1aecc5eec1569e5a160"
],
"path": "linux/bin"
}
Expand All @@ -68,7 +67,7 @@
"coins": {
"fetch_at_build_enabled": true,
"update_commit_on_build": true,
"bundled_coins_repo_commit": "5344c5ab44a153020ea2bb3893a752f19df927d0",
"bundled_coins_repo_commit": "dbc5950f1df9c35e327c8b77d54957c277a6fb26",
"coins_repo_api_url": "https://api.github.com/repos/KomodoPlatform/coins",
"coins_repo_content_url": "https://raw.githubusercontent.com/KomodoPlatform/coins",
"coins_repo_branch": "master",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,11 @@ class KdfOperationsRemote implements IKdfOperations {
// If the scheme is not provided, default to http
url = url.scheme.isEmpty ? url.replace(scheme: 'http') : url;

// If it's localhost, return the url as is
if (url.host.contains('localhost') || url.host.contains('127.0.0.1')) {
// Normalize localhost to 127.0.0.1 to avoid DNS resolution issues on Android
if (url.host == 'localhost') {
return url.replace(host: '127.0.0.1');
}
if (url.host == '127.0.0.1') {
return url;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ class ActivationManager {
_client,
privKeyPolicy,
_configService,
_activatedAssetsCache,
);

await for (final progress in activator.activate(
Expand Down Expand Up @@ -139,13 +140,8 @@ class ActivationManager {
/// Check if asset and its children are already activated
Future<ActivationProgress> _checkActivationStatus(_AssetGroup group) async {
try {
final enabledCoins = await _client.rpc.generalActivation
.getEnabledCoins();
final enabledAssetIds = enabledCoins.result
.map((coin) => _assetLookup.findAssetsByConfigId(coin.ticker))
.expand((assets) => assets)
.map((asset) => asset.id)
.toSet();
// Use cache instead of direct RPC call to avoid excessive requests
final enabledAssetIds = await _activatedAssetsCache.getActivatedAssetIds();

final isActive = enabledAssetIds.contains(group.primary.id);
final childrenActive =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:komodo_defi_sdk/src/assets/activated_assets_cache.dart';
import 'package:komodo_defi_types/komodo_defi_types.dart';

abstract class AssetActivator {
Expand All @@ -23,9 +24,14 @@ abstract class BatchCapableActivator extends AssetActivator {

/// Smart activator that chooses between batch/single methods
class SmartAssetActivator extends BatchCapableActivator {
SmartAssetActivator(super.client, this._activator);
SmartAssetActivator(
super.client,
this._activator,
this._activatedAssetsCache,
);

final CompositeAssetActivator _activator;
final ActivatedAssetsCache _activatedAssetsCache;

@override
bool get supportsBatchActivation => true;
Expand Down Expand Up @@ -75,8 +81,9 @@ class SmartAssetActivator extends BatchCapableActivator {
}

Future<bool> _isAssetActive(Asset asset) async {
final enabledCoins = await client.rpc.generalActivation.getEnabledCoins();
return enabledCoins.result.any((coin) => coin.ticker == asset.id.id);
// Use cache instead of direct RPC call to avoid excessive requests
final enabledAssetIds = await _activatedAssetsCache.getActivatedAssetIds();
return enabledAssetIds.contains(asset.id);
}

bool _supportsBatchActivation(Asset asset) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:komodo_defi_rpc_methods/komodo_defi_rpc_methods.dart';
import 'package:komodo_defi_sdk/src/activation/_activation.dart';
import 'package:komodo_defi_sdk/src/activation_config/activation_config_service.dart';
import 'package:komodo_defi_sdk/src/assets/activated_assets_cache.dart';
import 'package:komodo_defi_types/komodo_defi_types.dart';

/// Factory for creating the complete activation strategy stack
Expand All @@ -11,10 +12,12 @@ class ActivationStrategyFactory {
/// [privKeyPolicy] The [PrivateKeyPolicy] to use for private key management.
/// This is used for external wallet support. E.g. trezor, wallet connect, etc
/// [configService] The [ActivationConfigService] for resolving activation configuration.
/// [activatedAssetsCache] The [ActivatedAssetsCache] to use for checking activation status.
static SmartAssetActivator createStrategy(
ApiClient client,
PrivateKeyPolicy privKeyPolicy,
ActivationConfigService configService,
ActivatedAssetsCache activatedAssetsCache,
) {
return SmartAssetActivator(
client,
Expand All @@ -34,6 +37,7 @@ class ActivationStrategyFactory {
ZhtlcActivationStrategy(client, privKeyPolicy, configService),
CustomErc20ActivationStrategy(client),
]),
activatedAssetsCache,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class KomodoDefiSdkConfig {
this.preActivateCustomTokenAssets = true,
this.maxPreActivationAttempts = 3,
this.activationRetryDelay = const Duration(seconds: 2),
this.activatedAssetsCacheTtl = const Duration(seconds: 2),
this.activatedAssetsCacheTtl = const Duration(seconds: 10),
this.marketDataConfig = const MarketDataConfig(),
});

Expand Down
Loading