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
12 changes: 0 additions & 12 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,3 @@
# Komodo Wallet v0.9.1 Release Notes

This is a hotfix release that addresses critical issues with Trezor hardware wallet login functionality.

## 🐛 Bug Fixes

- **Trezor Login Issues** - Fixed critical bugs in the Trezor hardware wallet login flow that were preventing users from accessing their wallets.

**Full Changelog**: [0.9.0...0.9.1](https://github.com/KomodoPlatform/komodo-wallet/compare/0.9.0...0.9.1)

---

# Komodo Wallet v0.9.0 Release Notes

We are excited to announce Komodo Wallet v0.9.0. This release introduces HD wallet functionality, cross-platform fiat on-ramp improvements, a new feedback provider, and numerous bug fixes and dependency upgrades.
Expand Down
2 changes: 1 addition & 1 deletion docs/FLUTTER_VERSION.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Supported Flutter Version

This project supports Flutter `3.29.2`. We aim to keep the project up-to-date with the latest `stable` Flutter release.
This project supports Flutter `3.29.2` (latest stable release). We aim to keep the project up-to-date with the most recent stable Flutter versions.

## Recommended Approach: Multiple Flutter Versions

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 @@ -194,7 +194,7 @@ class AppBlocRoot extends StatelessWidget {
komodoDefiSdk,
)..add(
const PriceChartStarted(
symbols: ['BTC'],
symbols: ['KMD'],
period: Duration(days: 30),
),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import 'package:web_dex/bloc/cex_market_data/sdk_auth_activation_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';
import 'package:web_dex/shared/utils/extensions/legacy_coin_migration_extensions.dart';

part 'portfolio_growth_event.dart';
part 'portfolio_growth_state.dart';
Expand Down Expand Up @@ -121,13 +120,10 @@ class PortfolioGrowthBloc
await emit.forEach(
// computation is omitted, so null-valued events are emitted on a set
// interval.
Stream<Object?>.periodic(event.updateFrequency).asyncMap((_) async {
// Update prices before fetching chart data
await portfolioGrowthRepository.updatePrices();
return _fetchPortfolioGrowthChart(event);
}),
Stream<Object?>.periodic(event.updateFrequency)
.asyncMap((_) async => _fetchPortfolioGrowthChart(event)),
onData: (data) =>
_handlePortfolioGrowthUpdate(data, event.selectedPeriod, event.coins),
_handlePortfolioGrowthUpdate(data, event.selectedPeriod),
onError: (error, stackTrace) {
_log.shout('Failed to load portfolio growth', error, stackTrace);
return GrowthChartLoadFailure(
Expand Down Expand Up @@ -168,21 +164,10 @@ class PortfolioGrowthBloc
return state;
}

// Fetch prices before calculating total change
// This ensures we have the latest prices in the cache
await portfolioGrowthRepository.updatePrices();

final totalBalance = _calculateTotalBalance(coins);
final totalChange24h = _calculateTotalChange24h(coins);
final percentageChange24h = _calculatePercentageChange24h(coins);

return PortfolioGrowthChartLoadSuccess(
portfolioGrowth: chart,
percentageIncrease: chart.percentageIncrease,
selectedPeriod: event.selectedPeriod,
totalBalance: totalBalance,
totalChange24h: totalChange24h,
percentageChange24h: percentageChange24h,
);
}

Expand Down Expand Up @@ -220,71 +205,20 @@ class PortfolioGrowthBloc
PortfolioGrowthState _handlePortfolioGrowthUpdate(
ChartData growthChart,
Duration selectedPeriod,
List<Coin> coins,
) {
if (growthChart.isEmpty && state is PortfolioGrowthChartLoadSuccess) {
return state;
}

final percentageIncrease = growthChart.percentageIncrease;
final totalBalance = _calculateTotalBalance(coins);
final totalChange24h = _calculateTotalChange24h(coins);
final percentageChange24h = _calculatePercentageChange24h(coins);

// TODO? Include the center value in the bloc state instead of
// calculating it in the UI

return PortfolioGrowthChartLoadSuccess(
portfolioGrowth: growthChart,
percentageIncrease: percentageIncrease,
selectedPeriod: selectedPeriod,
totalBalance: totalBalance,
totalChange24h: totalChange24h,
percentageChange24h: percentageChange24h,
);
}

/// Calculate the total balance of all coins in USD
double _calculateTotalBalance(List<Coin> coins) {
double total = coins.fold(
0,
(prev, coin) => prev + (coin.lastKnownUsdBalance(sdk) ?? 0),
);

// Return at least 0.01 if total is positive but very small
if (total > 0 && total < 0.01) {
return 0.01;
}

return total;
}

/// Calculate the total 24h change in USD value
double _calculateTotalChange24h(List<Coin> coins) {
// Calculate the 24h change by summing the change percentage of each coin
// multiplied by its USD balance and divided by 100 (to convert percentage to decimal)
return coins.fold(
0.0,
(sum, coin) {
// Use the price change from the CexPrice if available
final usdBalance = coin.lastKnownUsdBalance(sdk) ?? 0.0;
// Get the coin price from the repository's prices cache
final price = portfolioGrowthRepository
.getCachedPrice(coin.id.symbol.configSymbol.toUpperCase());
final change24h = price?.change24h ?? 0.0;
return sum + (change24h * usdBalance / 100);
},
);
}

/// Calculate the percentage change over 24h for the entire portfolio
double _calculatePercentageChange24h(List<Coin> coins) {
final double totalBalance = _calculateTotalBalance(coins);
final double totalChange = _calculateTotalChange24h(coins);

// Avoid division by zero or very small balances
if (totalBalance <= 0.01) {
return 0.0;
}

// Return the percentage change
return (totalChange / totalBalance) * 100;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,8 @@ class PortfolioGrowthRepository {
/// The graph cache provider to store the portfolio growth graph data.
final PersistenceProvider<String, GraphCache> _graphCache;

/// The SDK needed for connecting to blockchain nodes
final KomodoDefiSdk _sdk;

/// The coins repository for detailed coin info
final CoinsRepo _coinsRepository;
final KomodoDefiSdk _sdk;

final _log = Logger('PortfolioGrowthRepository');

Expand Down Expand Up @@ -517,38 +514,4 @@ class PortfolioGrowthRepository {
}

Future<void> clearCache() => _graphCache.deleteAll();

/// Calculate the total 24h change in USD value for a list of coins
///
/// This method fetches the current prices for all coins and calculates
/// the 24h change by multiplying each coin's percentage change by its USD balance
Future<double> calculateTotalChange24h(List<Coin> coins) async {
// Fetch current prices including 24h change data
final prices = await _coinsRepository.fetchCurrentPrices() ?? {};

// Calculate the 24h change by summing the change percentage of each coin
// multiplied by its USD balance and divided by 100 (to convert percentage to decimal)
double totalChange = 0.0;
for (final coin in coins) {
final price = prices[coin.id.symbol.configSymbol.toUpperCase()];
final change24h = price?.change24h ?? 0.0;
final usdBalance = coin.lastKnownUsdBalance(_sdk) ?? 0.0;
totalChange += (change24h * usdBalance / 100);
}
return totalChange;
}

/// Get the cached price for a given coin symbol
///
/// This is used to avoid fetching prices for every calculation
CexPrice? getCachedPrice(String symbol) {
return _coinsRepository.getCachedPrice(symbol);
}

/// Update prices for all coins by fetching from market data
///
/// This method ensures we have up-to-date price data before calculations
Future<void> updatePrices() async {
await _coinsRepository.fetchCurrentPrices();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,16 @@ final class PortfolioGrowthChartLoadSuccess extends PortfolioGrowthState {
required this.portfolioGrowth,
required this.percentageIncrease,
required super.selectedPeriod,
required this.totalBalance,
required this.totalChange24h,
required this.percentageChange24h,
});

final ChartData portfolioGrowth;
final double percentageIncrease;
final double totalBalance;
final double totalChange24h;
final double percentageChange24h;

@override
List<Object> get props => <Object>[
portfolioGrowth,
percentageIncrease,
selectedPeriod,
totalBalance,
totalChange24h,
percentageChange24h,
];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ enum PriceChartPeriod {
}
}

// TODO: Localize this
String get formatted {
String get intervalString {
switch (this) {
case PriceChartPeriod.oneHour:
return '1h';
Expand Down
3 changes: 0 additions & 3 deletions lib/bloc/cex_market_data/price_chart/models/time_period.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ enum TimePeriod {
}
}

// TODO: Localize
String formatted() => name;

Duration get duration {
switch (this) {
case TimePeriod.oneHour:
Expand Down
7 changes: 0 additions & 7 deletions lib/bloc/coins_bloc/coins_repo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -658,11 +658,4 @@ class CoinsRepo {
result: withdrawDetails,
);
}

/// Get a cached price for a given coin symbol
///
/// This returns the price from the cache without fetching new data
CexPrice? getCachedPrice(String symbol) {
return _pricesCache[symbol];
}
}
53 changes: 7 additions & 46 deletions lib/bloc/trezor_init_bloc/trezor_init_bloc.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import 'dart:async';

import 'package:easy_localization/easy_localization.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:komodo_defi_sdk/komodo_defi_sdk.dart';
import 'package:komodo_defi_types/komodo_defi_types.dart';
import 'package:web_dex/app_config/app_config.dart';
import 'package:web_dex/shared/utils/password.dart';
import 'package:web_dex/bloc/coins_bloc/coins_repo.dart';
import 'package:web_dex/bloc/trezor_bloc/trezor_repo.dart';
import 'package:web_dex/generated/codegen_loader.g.dart';
Expand All @@ -20,24 +18,18 @@ import 'package:web_dex/model/kdf_auth_metadata_extension.dart';
import 'package:web_dex/model/text_error.dart';
import 'package:web_dex/model/wallet.dart';
import 'package:web_dex/shared/utils/utils.dart';
import 'package:komodo_defi_rpc_methods/komodo_defi_rpc_methods.dart'
show PrivateKeyPolicy;

part 'trezor_init_event.dart';
part 'trezor_init_state.dart';

const String _trezorPasswordKey = 'trezor_wallet_password';

class TrezorInitBloc extends Bloc<TrezorInitEvent, TrezorInitState> {
TrezorInitBloc({
required KomodoDefiSdk kdfSdk,
required TrezorRepo trezorRepo,
required CoinsRepo coinsRepository,
FlutterSecureStorage? secureStorage,
}) : _trezorRepo = trezorRepo,
_kdfSdk = kdfSdk,
_coinsRepository = coinsRepository,
_secureStorage = secureStorage ?? const FlutterSecureStorage(),
super(TrezorInitState.initial()) {
on<TrezorInitSubscribeStatus>(_onSubscribeStatus);
on<TrezorInit>(_onInit);
Expand All @@ -57,7 +49,6 @@ class TrezorInitBloc extends Bloc<TrezorInitEvent, TrezorInitState> {
final TrezorRepo _trezorRepo;
final KomodoDefiSdk _kdfSdk;
final CoinsRepo _coinsRepository;
final FlutterSecureStorage _secureStorage;
Timer? _statusTimer;

void _unsubscribeStatus() {
Expand Down Expand Up @@ -282,65 +273,35 @@ class TrezorInitBloc extends Bloc<TrezorInitEvent, TrezorInitState> {
/// into a static 'hidden' wallet to init trezor
Future<void> _loginToTrezorWallet({
String walletName = 'My Trezor',
String? password,
AuthOptions authOptions = const AuthOptions(
derivationMethod: DerivationMethod.hdWallet,
privKeyPolicy: PrivateKeyPolicy.trezor,
),
String password = 'hidden-login',
}) async {
try {
password ??= await _secureStorage.read(key: _trezorPasswordKey);
} catch (e, s) {
log(
'Failed to read trezor password from secure storage: $e',
path: 'trezor_init_bloc => _loginToTrezorWallet',
isError: true,
trace: s,
).ignore();
// If reading fails, password will remain null and a new one will be generated
}

if (password == null) {
password = generatePassword();
try {
await _secureStorage.write(key: _trezorPasswordKey, value: password);
} catch (e, s) {
log(
'Failed to write trezor password to secure storage: $e',
path: 'trezor_init_bloc => _loginToTrezorWallet',
isError: true,
trace: s,
).ignore();
// Continue with generated password even if storage write fails
}
}

final bool mm2SignedIn = await _kdfSdk.auth.isSignedIn();
if (state.kdfUser != null && mm2SignedIn) {
return;
}

// final walletName = state.status?.trezorStatus.name ?? 'My Trezor';
// final password =
// state.status?.details.deviceDetails?.deviceId ?? 'hidden-login';
final existingWallets = await _kdfSdk.auth.getUsers();
if (existingWallets.any((wallet) => wallet.walletId.name == walletName)) {
await _kdfSdk.auth.signIn(
walletName: walletName,
password: password,
options: authOptions,
options: const AuthOptions(derivationMethod: DerivationMethod.iguana),
);
await _kdfSdk.setWalletType(WalletType.trezor);
await _kdfSdk.confirmSeedBackup();
await _kdfSdk.addActivatedCoins(enabledByDefaultTrezorCoins);
return;
}

await _kdfSdk.auth.register(
walletName: walletName,
password: password,
options: authOptions,
options: const AuthOptions(derivationMethod: DerivationMethod.iguana),
);
await _kdfSdk.setWalletType(WalletType.trezor);
await _kdfSdk.confirmSeedBackup();
await _kdfSdk.addActivatedCoins(enabledByDefaultTrezorCoins);
}

Future<void> _logout() async {
Expand Down
Loading
Loading