Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6ff31f3
fix(screenshot-sensitivity): notify listeners in postFrameCallback
takenagain Sep 24, 2025
212ad6d
chore(sdk): switch to zhtlc branch
takenagain Sep 25, 2025
23ded71
feat(zhtlc): add configuration and param download dialogs
takenagain Sep 26, 2025
349baca
refactor: generate localisations, and fix theming on popup dialog
takenagain Sep 26, 2025
d33742e
refactor: use repositoryprovider and adjust status bar width
takenagain Sep 26, 2025
79ee69d
Merge remote-tracking branch 'origin/dev' into feat/zhtlc-asset-support
takenagain Sep 26, 2025
521ba35
perf(coins-manager): add input debouncer and cache heavy operations
takenagain Sep 26, 2025
9c68937
fix(activation): swap page activation, improve list and disposal mana…
takenagain Sep 27, 2025
16c5e5a
fix(zhtlc): update activation warning, add padding, and stealth text
takenagain Sep 27, 2025
b351e11
feat(orderbook): migrate to v2 orderbook RPC via SDK
takenagain Sep 28, 2025
3a6d3db
perf(dex): remove enabled assets rpc call from taker validator
takenagain Sep 28, 2025
2f0a129
style(zhtlc): improve dialog layout, add transaction note, fix hidden…
takenagain Sep 28, 2025
a264209
fix(market-metrics): filter out inactive coins before starting calcs
takenagain Sep 29, 2025
ccfae68
fix(zhtlc): workaround "Task is finished" error with retry on activation
takenagain Sep 29, 2025
6968034
feat(zhtlc): add activation status to banner
takenagain Sep 30, 2025
0c34a9b
fix(withdraw): add recipient address and memo (if not empty)
takenagain Sep 30, 2025
0e0fd64
fix(zhtlc): filter out active assets before attempting zhtlc activation
takenagain Sep 30, 2025
b85c889
fix(wallet): add resilience to delisted coins for wallet coin metadata
takenagain Sep 30, 2025
a130697
fix(version-info): ensure that apiversion and commit has persist
takenagain Sep 30, 2025
3537231
perf(market-data): add update backoff strategy and guard against fetc…
takenagain Oct 1, 2025
3b4319b
Merge branch 'dev' into feat/zhtlc-asset-support
takenagain Oct 1, 2025
a82e022
perf(dex): reduce overhead in coin selection and filtering
takenagain Oct 1, 2025
ccfae6a
fix(dex): only update sell amount on order selection if no value present
takenagain Oct 1, 2025
fd73814
Merge branch 'dev' into feat/zhtlc-asset-support
takenagain Oct 1, 2025
1e58d91
fix(zhtlc): track ongoing activations to prevent duplicate requests
takenagain Oct 1, 2025
066dd1d
fix(taker-validator): revert to getting active assets from API
takenagain Oct 2, 2025
99602ee
Merge remote-tracking branch 'origin/dev' into feat/zhtlc-asset-support
takenagain Oct 2, 2025
4416b1c
chore(sdk): switch back to dev branch (sdk branch merged)
takenagain Oct 2, 2025
c6e7621
Merge remote-tracking branch 'origin/dev' into feat/zhtlc-asset-support
takenagain Oct 2, 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
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[submodule "sdk"]
path = sdk
url = https://github.com/KomodoPlatform/komodo-defi-sdk-flutter.git
branch = dev
branch = bugfix/zhltc-activation-fixes
update = checkout
fetchRecurseSubmodules = on-demand
ignore = dirty
26 changes: 24 additions & 2 deletions assets/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,6 @@
"backupSeedPhrase": "Backup seed phrase",
"seedOr": "OR",
"seedDownload": "Download seed phrase",
"seedSaveAndRemember": "Save and remember",
"seedIntroWarning": "This phrase is the main access to your\nassets, save and never share this phrase",
"seedSettings": "Seed phrase",
"errorDescription": "Error description",
Expand Down Expand Up @@ -460,6 +459,7 @@
"withdrawAmountTooLowError": "{} {} too low, you need > {} {} to send",
"withdrawNoSuchCoinError": "Invalid selection, {} does not exist",
"withdrawPreview": "Preview Withdrawal",
"withdrawPreviewZhtlcNote": "ZHTLC transactions can take a while to generate.\nPlease stay on this page until the preview is ready, otherwise you will need to start over.",
"withdrawPreviewError": "Error occurred while fetching withdrawal preview",
"txHistoryFetchError": "Error fetching tx history from the endpoint. Unsupported type: {}",
"txHistoryNoTransactions": "Transactions are not available",
Expand Down Expand Up @@ -764,5 +764,27 @@
"fetchingPrivateKeysTitle": "Fetching Private Keys...",
"fetchingPrivateKeysMessage": "Please wait while we securely fetch your private keys...",
"pubkeyType": "Type",
"securitySettings": "Security Settings"
"securitySettings": "Security Settings",
"zhtlcConfigureTitle": "Configure {}",
"zhtlcZcashParamsPathLabel": "Zcash parameters path",
"zhtlcPathAutomaticallyDetected": "Path automatically detected",
"zhtlcSaplingParamsFolder": "Folder containing sapling params",
"zhtlcBlocksPerIterationLabel": "Blocks per iteration",
"zhtlcScanIntervalLabel": "Scan interval (ms)",
"zhtlcStartSyncFromLabel": "Start sync from:",
"zhtlcEarliestSaplingOption": "Earliest (sapling)",
"zhtlcBlockHeightOption": "Block height",
"zhtlcDateTimeOption": "Date & Time",
"zhtlcSelectDateTimeLabel": "Select date & time",
"zhtlcZcashParamsRequired": "Zcash params path is required",
"zhtlcInvalidBlockHeight": "Enter a valid block height",
"zhtlcSelectDateTimeRequired": "Please select a date and time",
"zhtlcDownloadingZcashParams": "Downloading Zcash Parameters",
"zhtlcPreparingDownload": "Preparing download...",
"zhtlcErrorSettingUpZcash": "Error setting up Zcash parameters: {}",
"zhtlcDateSyncHint": "Selecting a date further in the past can significantly increase the activation time. \nActivation can take a little while the first time to download block cache data.\n\nTransactions and balance prior to the sync date may be missing.\nOften this can be restored by sending in and out new transactions",
"activatingZhtlcCoins": {
"one": "Activating ZHTLC coin: {}. Please do not close the app or tab until complete.",
"other": "Activating ZHTLC coins: {}. Please do not close the app or tab until complete."
}
}
7 changes: 0 additions & 7 deletions lib/app_config/app_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,8 @@ const Set<String> excludedAssetList = {
'FENIX',
'AWR',
'BOT',
// Pirate activation params are not yet implemented, so we need to
// exclude it from the list of coins for now.
'ARRR',
'ZOMBIE',
'SMTF-v2',
'SFUSD',
'VOTE2023',
'RICK',
'MORTY',

// NFT v2 coins: https://github.com/KomodoPlatform/coins/pull/1061 will be
// used in the background, so users do not need to see them.
Expand Down
2 changes: 1 addition & 1 deletion lib/bloc/app_bloc_root.dart
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ class AppBlocRoot extends StatelessWidget {
dexRepository: dexRepository,
),
),
RepositoryProvider(create: (_) => OrderbookBloc(api: mm2Api)),
RepositoryProvider(create: (_) => OrderbookBloc(sdk: komodoDefiSdk)),
RepositoryProvider(create: (_) => myOrdersService),
RepositoryProvider(
create: (_) => KmdRewardsBloc(coinsRepository, mm2Api),
Expand Down
10 changes: 8 additions & 2 deletions lib/bloc/assets_overview/bloc/asset_overview_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:web_dex/bloc/cex_market_data/profit_loss/models/fiat_value.dart'
import 'package:web_dex/bloc/cex_market_data/profit_loss/models/profit_loss.dart';
import 'package:web_dex/bloc/cex_market_data/profit_loss/profit_loss_repository.dart';
import 'package:web_dex/bloc/cex_market_data/sdk_auth_activation_extension.dart';
import 'package:web_dex/bloc/coins_bloc/asset_coin_extension.dart';
import 'package:web_dex/model/coin.dart';

part 'asset_overview_event.dart';
Expand Down Expand Up @@ -95,7 +96,12 @@ class AssetOverviewBloc extends Bloc<AssetOverviewEvent, AssetOverviewState> {

await _sdk.waitForEnabledCoinsToPassThreshold(event.coins);

final profitLossesFutures = event.coins.map((coin) async {
final activeCoins = await event.coins.removeInactiveCoins(_sdk);
if (activeCoins.isEmpty) {
return;
}

final profitLossesFutures = activeCoins.map((coin) async {
// Catch errors that occur for single coins and exclude them from the
// total so that transaction fetching errors for a single coin do not
// affect the total investment calculation.
Expand All @@ -114,7 +120,7 @@ class AssetOverviewBloc extends Bloc<AssetOverviewEvent, AssetOverviewState> {
final profitLosses = await Future.wait(profitLossesFutures);

final totalInvestment = await _investmentRepository
.calculateTotalInvestment(event.walletId, event.coins);
.calculateTotalInvestment(event.walletId, activeCoins);

final profitAmount = profitLosses.fold(0.0, (sum, item) {
return sum + (item.lastOrNull?.profitLoss ?? 0.0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:rational/rational.dart';
import 'package:web_dex/bloc/cex_market_data/charts.dart';
import 'package:web_dex/bloc/cex_market_data/portfolio_growth/portfolio_growth_repository.dart';
import 'package:web_dex/bloc/cex_market_data/sdk_auth_activation_extension.dart';
import 'package:web_dex/bloc/coins_bloc/asset_coin_extension.dart';
import 'package:web_dex/mm2/mm2_api/rpc/base.dart';
import 'package:web_dex/model/coin.dart';
import 'package:web_dex/model/text_error.dart';
Expand Down Expand Up @@ -55,8 +56,13 @@ class PortfolioGrowthBloc
PortfolioGrowthPeriodChanged event,
Emitter<PortfolioGrowthState> emit,
) {
final (int totalCoins, int coinsWithKnownBalance, int coinsWithKnownBalanceAndFiat) =
_calculateCoinProgressCounters(event.coins);
final (
int totalCoins,
int coinsWithKnownBalance,
int coinsWithKnownBalanceAndFiat,
) = _calculateCoinProgressCounters(
event.coins,
);
final currentState = state;
if (currentState is PortfolioGrowthChartLoadSuccess) {
emit(
Expand Down Expand Up @@ -120,7 +126,9 @@ class PortfolioGrowthBloc
int totalCoins,
int coinsWithKnownBalance,
int coinsWithKnownBalanceAndFiat,
) = _calculateCoinProgressCounters(event.coins);
) = _calculateCoinProgressCounters(
event.coins,
);
return emit(
PortfolioGrowthChartUnsupported(
selectedPeriod: event.selectedPeriod,
Expand All @@ -131,8 +139,9 @@ class PortfolioGrowthBloc
);
}

final initialActiveCoins = await coins.removeInactiveCoins(_sdk);
await _loadChart(
coins,
initialActiveCoins,
event,
useCache: true,
).then(emit.call).catchError((Object error, StackTrace stackTrace) {
Expand All @@ -149,7 +158,7 @@ class PortfolioGrowthBloc

// Only remove inactivate/activating coins after an attempt to load the
// cached chart, as the cached chart may contain inactive coins.
final activeCoins = await _removeInactiveCoins(coins);
final activeCoins = await coins.removeInactiveCoins(_sdk);
if (activeCoins.isNotEmpty) {
await _loadChart(
activeCoins,
Expand Down Expand Up @@ -190,7 +199,9 @@ class PortfolioGrowthBloc
int totalCoins,
int coinsWithKnownBalance,
int coinsWithKnownBalanceAndFiat,
) = _calculateCoinProgressCounters(event.coins);
) = _calculateCoinProgressCounters(
event.coins,
);
emit(
GrowthChartLoadFailure(
error: TextError(error: 'Failed to load portfolio growth'),
Expand Down Expand Up @@ -238,8 +249,13 @@ class PortfolioGrowthBloc
final totalChange24h = await _calculateTotalChange24h(coins);
final percentageChange24h = await _calculatePercentageChange24h(coins);

final (int totalCoins, int coinsWithKnownBalance, int coinsWithKnownBalanceAndFiat) =
_calculateCoinProgressCounters(event.coins);
final (
int totalCoins,
int coinsWithKnownBalance,
int coinsWithKnownBalanceAndFiat,
) = _calculateCoinProgressCounters(
event.coins,
);

return PortfolioGrowthChartLoadSuccess(
portfolioGrowth: chart,
Expand All @@ -261,7 +277,7 @@ class PortfolioGrowthBloc
// Do not let transaction loading exceptions stop the periodic updates
try {
final supportedCoins = await _removeUnsupportedCoins(event);
final coins = await _removeInactiveCoins(supportedCoins);
final coins = await supportedCoins.removeInactiveCoins(_sdk);
return await _portfolioGrowthRepository.getPortfolioGrowthChart(
coins,
fiatCoinId: event.fiatCoinId,
Expand All @@ -274,18 +290,6 @@ class PortfolioGrowthBloc
}
}

Future<List<Coin>> _removeInactiveCoins(List<Coin> coins) async {
final coinsCopy = List<Coin>.of(coins);
final activeCoins = await _sdk.assets.getActivatedAssets();
final activeCoinsMap = activeCoins.map((e) => e.id).toSet();
for (final coin in coins) {
if (!activeCoinsMap.contains(coin.id)) {
coinsCopy.remove(coin);
}
}
return coinsCopy;
}

Future<PortfolioGrowthState> _handlePortfolioGrowthUpdate(
ChartData growthChart,
Duration selectedPeriod,
Expand All @@ -300,8 +304,13 @@ class PortfolioGrowthBloc
final totalChange24h = await _calculateTotalChange24h(coins);
final percentageChange24h = await _calculatePercentageChange24h(coins);

final (int totalCoins, int coinsWithKnownBalance, int coinsWithKnownBalanceAndFiat) =
_calculateCoinProgressCounters(coins);
final (
int totalCoins,
int coinsWithKnownBalance,
int coinsWithKnownBalanceAndFiat,
) = _calculateCoinProgressCounters(
coins,
);

return PortfolioGrowthChartLoadSuccess(
portfolioGrowth: growthChart,
Expand Down
18 changes: 4 additions & 14 deletions lib/bloc/cex_market_data/profit_loss/profit_loss_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:logging/logging.dart';
import 'package:web_dex/bloc/cex_market_data/charts.dart';
import 'package:web_dex/bloc/cex_market_data/profit_loss/profit_loss_repository.dart';
import 'package:web_dex/bloc/cex_market_data/sdk_auth_activation_extension.dart';
import 'package:web_dex/bloc/coins_bloc/asset_coin_extension.dart';
import 'package:web_dex/mm2/mm2_api/rpc/base.dart';
import 'package:web_dex/model/coin.dart';
import 'package:web_dex/model/text_error.dart';
Expand Down Expand Up @@ -51,6 +52,7 @@ class ProfitLossBloc extends Bloc<ProfitLossEvent, ProfitLossState> {
event.coins,
event.fiatCoinId,
);
final initialActiveCoins = await supportedCoins.removeInactiveCoins(_sdk);
// Charts for individual coins (coin details) are parsed here as well,
// and should be hidden if not supported.
if (supportedCoins.isEmpty && event.coins.length <= 1) {
Expand All @@ -63,7 +65,7 @@ class ProfitLossBloc extends Bloc<ProfitLossEvent, ProfitLossState> {

await _getProfitLossChart(
event,
supportedCoins,
initialActiveCoins,
useCache: true,
).then(emit.call).catchError((Object error, StackTrace stackTrace) {
const errorMessage = 'Failed to load CACHED portfolio profit/loss';
Expand All @@ -74,7 +76,7 @@ class ProfitLossBloc extends Bloc<ProfitLossEvent, ProfitLossState> {

// Fetch the un-cached version of the chart to update the cache.
await _sdk.waitForEnabledCoinsToPassThreshold(supportedCoins);
final activeCoins = await _removeInactiveCoins(supportedCoins);
final activeCoins = await supportedCoins.removeInactiveCoins(_sdk);
if (activeCoins.isNotEmpty) {
await _getProfitLossChart(
event,
Expand Down Expand Up @@ -232,16 +234,4 @@ class ProfitLossBloc extends Bloc<ProfitLossEvent, ProfitLossState> {
chartsList.removeWhere((element) => element.isEmpty);
return Charts.merge(chartsList)..sort((a, b) => a.x.compareTo(b.x));
}

Future<List<Coin>> _removeInactiveCoins(List<Coin> coins) async {
final coinsCopy = List<Coin>.of(coins);
final activeCoins = await _sdk.assets.getActivatedAssets();
final activeCoinsMap = activeCoins.map((e) => e.id).toSet();
for (final coin in coins) {
if (!activeCoinsMap.contains(coin.id)) {
coinsCopy.remove(coin);
}
}
return coinsCopy;
}
}
35 changes: 26 additions & 9 deletions lib/bloc/coins_bloc/asset_coin_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:komodo_defi_types/komodo_defi_types.dart';
import 'package:web_dex/app_config/app_config.dart';
import 'package:web_dex/model/coin.dart';
import 'package:web_dex/model/coin_type.dart';
import 'package:web_dex/shared/utils/extensions/collection_extensions.dart';

extension AssetCoinExtension on Asset {
Coin toCoin() {
Expand All @@ -15,7 +16,7 @@ extension AssetCoinExtension on Asset {
final logoImageUrl = config.valueOrNull<String>('logo_image_url');
final isCustomToken =
(config.valueOrNull<bool>('is_custom_token') ?? false) ||
logoImageUrl != null;
logoImageUrl != null;

final ProtocolData protocolData = ProtocolData(
platform: id.parentId?.id ?? platform ?? '',
Expand All @@ -38,8 +39,9 @@ extension AssetCoinExtension on Asset {
isTestCoin: protocol.isTestnet,
coingeckoId: id.symbol.coinGeckoId,
swapContractAddress: config.valueOrNull<String>('swap_contract_address'),
fallbackSwapContract:
config.valueOrNull<String>('fallback_swap_contract'),
fallbackSwapContract: config.valueOrNull<String>(
'fallback_swap_contract',
),
priority: priorityCoinsAbbrMap[id.id] ?? 0,
state: CoinState.inactive,
walletOnly: config.valueOrNull<bool>('wallet_only') ?? false,
Expand All @@ -49,8 +51,11 @@ extension AssetCoinExtension on Asset {
);
}

String? get contractAddress => protocol.config
.valueOrNull('protocol', 'protocol_data', 'contract_address');
String? get contractAddress => protocol.config.valueOrNull(
'protocol',
'protocol_data',
'contract_address',
);
String? get platform =>
protocol.config.valueOrNull('protocol', 'protocol_data', 'platform');
}
Expand Down Expand Up @@ -96,6 +101,8 @@ extension CoinTypeExtension on CoinSubClass {
return CoinType.erc20;
case CoinSubClass.krc20:
return CoinType.krc20;
case CoinSubClass.zhtlc:
return CoinType.zhtlc;
default:
return CoinType.utxo;
}
Expand Down Expand Up @@ -166,6 +173,8 @@ extension CoinSubClassExtension on CoinType {
return CoinSubClass.erc20;
case CoinType.krc20:
return CoinSubClass.krc20;
case CoinType.zhtlc:
return CoinSubClass.zhtlc;
}
}
}
Expand Down Expand Up @@ -201,10 +210,7 @@ extension AssetBalanceExtension on Coin {
KomodoDefiSdk sdk, {
bool activateIfNeeded = true,
}) {
return sdk.balances.watchBalance(
id,
activateIfNeeded: activateIfNeeded,
);
return sdk.balances.watchBalance(id, activateIfNeeded: activateIfNeeded);
}

/// Get the last-known balance for this coin.
Expand All @@ -227,3 +233,14 @@ extension AssetBalanceExtension on Coin {
return (balance * price).spendable.toDouble();
}
}

extension CoinListOps on List<Coin> {
Future<List<Coin>> removeInactiveCoins(KomodoDefiSdk sdk) async {
final activeCoins = await sdk.assets.getActivatedAssets();
final activeCoinsMap = activeCoins.map((e) => e.id).toSet();

return where(
(coin) => activeCoinsMap.contains(coin.id),
).unmodifiable().toList();
}
}
Loading
Loading