Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
798a163
init custom token import feature
naezith Oct 25, 2024
061fe34
add custom token import warning
naezith Oct 25, 2024
2450763
custom token import better state & error handling
naezith Oct 25, 2024
f14d7e8
custom token import details page
naezith Oct 25, 2024
70744a2
custom token import loading status
naezith Oct 25, 2024
499e975
custom token import reset form
naezith Oct 26, 2024
b4848dd
custom token import network uses CoinType
naezith Oct 26, 2024
9aad03f
remove unused import
naezith Oct 26, 2024
43ced97
custom token import buttons
naezith Oct 26, 2024
819b4df
custom token import redesign
naezith Oct 30, 2024
2e52a9f
custom token import dialog close on success
naezith Oct 30, 2024
e2ea25a
add cointype to evm platform coin conversion
naezith Nov 20, 2024
8062425
custom token import CoinsRepo getTokenInfo
naezith Nov 20, 2024
80476e7
custom token import KdfCustomTokenImportRepository
naezith Nov 20, 2024
b380b03
custom token import use real repository
naezith Nov 20, 2024
2bb077d
custom token import remove decimals field
naezith Nov 21, 2024
3f70314
custom token import activate platform coin
naezith Nov 21, 2024
e4787a5
custom token import fetch and display coin icon
naezith Nov 21, 2024
6c443de
add logoImageUrl field to Coin
naezith Nov 21, 2024
05a1f16
custom token import tokenData as Coin
naezith Nov 21, 2024
fb6c644
custom token import construct Coin and get balance
naezith Nov 21, 2024
547f442
Merge branch 'dev' into feature/custom-token-import
naezith Nov 22, 2024
f74e99d
custom token import remove balance fetch delay
naezith Nov 22, 2024
c6877ff
custom token import use ImageProvider for logo
naezith Nov 22, 2024
ea41e44
custom token import hide unsupported networks
naezith Nov 22, 2024
7c59c34
custom token import fill swap contract addresses
naezith Nov 22, 2024
bbebeac
custom token import EnableErc20Req ProtocolData
naezith Nov 22, 2024
7591a2c
custom token import _getCoin function
naezith Nov 22, 2024
5204ba4
custom token import rename tokenData to coin
naezith Nov 22, 2024
0119bd5
custom token import enable coin
naezith Nov 22, 2024
e956b6c
custom coin icon support by Charl
naezith Nov 25, 2024
6c146de
custom token import register coin icon
naezith Nov 25, 2024
ef0a454
custom token import remove logoImage field
naezith Nov 26, 2024
0e85724
custom token import handle known coins
naezith Nov 26, 2024
be6743a
Merge branch 'dev' into feature/custom-token-import
naezith Nov 27, 2024
f440752
custom token import persistence init
naezith Nov 27, 2024
b4b89c1
custom token import sync coinsBloc knownCoins
naezith Nov 27, 2024
b1b14c9
fiat page fix coin rows network text
naezith Nov 28, 2024
d28a6c6
migrate custom token RPCs to the SDK
takenagain Jan 23, 2025
644eb81
add custom token activation via SDK activation manager
takenagain Jan 23, 2025
fce0cd5
refactor: move SDK metadata extensions to a separate file
takenagain Jan 23, 2025
493833d
activate and load custom coins in known coins list
takenagain Jan 27, 2025
02807e7
refactor: move custom token storage to SDK
takenagain Jan 29, 2025
1eaee22
refactor: migrate mock transaction generation to SDK transaction model
takenagain Jan 30, 2025
e2e62e2
refactor: add AssetId to legacy Coin model
takenagain Jan 30, 2025
cf1629d
fix(coins-bloc): use kdf assets if coin is not in state cache
takenagain Feb 4, 2025
e19aae8
Merge branch 'dev' into feature/custom-token-import
takenagain Feb 4, 2025
e126f8a
fix: merge conflict resolution errors
takenagain Feb 4, 2025
4f0b819
fix(router): use ValueKey for MainLayout
takenagain Feb 5, 2025
859aa66
fix(price-charts): filter out CEX coins not in known assets list
takenagain Feb 5, 2025
1f6e74c
fix: missing custom token icon on hard refresh
takenagain Feb 6, 2025
f764c90
Merge remote-tracking branch 'origin/dev' into feature/custom-token-i…
takenagain Feb 12, 2025
f6b48e8
fix: broken non-ETH support by hardcoding protocol type to ERC20
takenagain Feb 12, 2025
3918881
fix: merge conflict regression to slower getEnabledCoins function
takenagain Feb 12, 2025
a7c83e2
Merge remote-tracking branch 'origin/dev' into feature/custom-token-i…
takenagain Feb 13, 2025
ce36bf1
style: apply formatting to modified files
takenagain Feb 13, 2025
94d6be5
fix(import-dialog): shrink dead space above button to 24px
takenagain Feb 13, 2025
e7f51dd
fix(fetch-custom-token): activate network coin before fetching info
takenagain Feb 13, 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
Binary file added assets/logo/not_found.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions assets/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,13 @@
"mmBotFirstTradePreview": "Preview of the first order",
"mmBotFirstTradeEstimate": "First trade estimate",
"mmBotFirstOrderVolume": "This is an estimate of the first order only. Following orders will be placed automatically using the configured volume of the available {} balance.",
"importCustomToken": "Import Custom Token",
"importTokenWarning": "Ensure the token is trustworthy before you import it.",
"importToken": "Import Token",
"selectNetwork": "Select Network",
"tokenContractAddress": "Token Contract Address",
"tokenNotFound": "Token is not found.",
"decimals": "Decimals",
"onlySendToThisAddress": "Only send {} to this address",
"scanTheQrCode": "Scan the QR code on any mobile device wallet",
"swapAddress": "Swap Address",
Expand Down
1 change: 1 addition & 0 deletions lib/bloc/auth_bloc/auth_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:komodo_defi_sdk/komodo_defi_sdk.dart';
import 'package:komodo_defi_types/komodo_defi_types.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';
import 'package:web_dex/model/wallet.dart';
import 'package:web_dex/shared/utils/utils.dart';

Expand Down
13 changes: 1 addition & 12 deletions lib/bloc/cex_market_data/mockup/generate_demo_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -141,18 +141,7 @@ class DemoDataGenerator {
final totalAmount = transaction.balanceChanges.totalAmount;

adjustedTransactions.add(
Transaction(
id: transaction.id,
timestamp: transaction.timestamp,
assetId: transaction.assetId,
blockHeight: transaction.blockHeight,
from: transaction.from,
internalId: transaction.internalId,
confirmations: transaction.confirmations,
to: transaction.to,
txHash: transaction.txHash,
fee: transaction.fee,
memo: transaction.memo,
transaction.copyWith(
balanceChanges: BalanceChanges(
netChange: netChange * adjustmentFactor,
receivedByMe: received * adjustmentFactor,
Expand Down
50 changes: 38 additions & 12 deletions lib/bloc/coins_bloc/asset_coin_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,23 @@ extension AssetCoinExtension on Asset {
contractAddress: '',
);

final CoinType? type = _getCoinTypeFromProtocol(protocol);
if (type == null) {
throw ArgumentError.value(
protocol.subClass,
'protocol type',
'Unsupported protocol type',
);
}

final CoinType type = protocol.subClass.toCoinType();
// temporary measure to get metadata, like `wallet_only`, that isn't exposed
// by the SDK (and might be phased out completely later on)
// TODO: Remove this once the SDK exposes all the necessary metadata
final config = protocol.config;
final logoImageUrl = config.valueOrNull<String>('logo_image_url');
final isCustomToken =
(config.valueOrNull<bool>('is_custom_token') ?? false) ||
logoImageUrl != null;

return Coin(
type: type,
abbr: id.id,
id: id,
name: id.name,
logoImageUrl: logoImageUrl ?? '',
isCustomCoin: isCustomToken,
explorerUrl: config.valueOrNull<String>('explorer_url') ?? '',
explorerTxUrl: config.valueOrNull<String>('explorer_tx_url') ?? '',
explorerAddressUrl:
Expand All @@ -49,13 +48,17 @@ extension AssetCoinExtension on Asset {
);
}

CoinType? _getCoinTypeFromProtocol(ProtocolClass protocol) {
switch (protocol.subClass) {
String? get contractAddress => protocol.config
.valueOrNull('protocol', 'protocol_data', 'contract_address');
}

extension CoinTypeExtension on CoinSubClass {
CoinType toCoinType() {
switch (this) {
case CoinSubClass.ftm20:
return CoinType.ftm20;
case CoinSubClass.arbitrum:
return CoinType.arb20;
// ignore: deprecated_member_use
case CoinSubClass.slp:
return CoinType.slp;
case CoinSubClass.qrc20:
Expand Down Expand Up @@ -94,6 +97,29 @@ extension AssetCoinExtension on Asset {
return CoinType.utxo;
}
}

bool isEvmProtocol() {
switch (this) {
case CoinSubClass.avx20:
case CoinSubClass.bep20:
case CoinSubClass.ftm20:
case CoinSubClass.matic:
case CoinSubClass.hrc20:
case CoinSubClass.arbitrum:
case CoinSubClass.moonriver:
case CoinSubClass.moonbeam:
case CoinSubClass.ethereumClassic:
case CoinSubClass.ubiq:
case CoinSubClass.krc20:
case CoinSubClass.ewt:
case CoinSubClass.hecoChain:
case CoinSubClass.rskSmartBitcoin:
case CoinSubClass.erc20:
return true;
default:
return false;
}
}
}

extension CoinSubClassExtension on CoinType {
Expand Down
148 changes: 94 additions & 54 deletions lib/bloc/coins_bloc/coins_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:web_dex/blocs/trezor_coins_bloc.dart';
import 'package:web_dex/mm2/mm2_api/mm2_api.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';
import 'package:web_dex/model/wallet.dart';
import 'package:web_dex/shared/utils/utils.dart';

Expand Down Expand Up @@ -265,24 +266,22 @@ class CoinsBloc extends Bloc<CoinsEvent, CoinsState> {
changed = true;
// Create new coin instance with updated price
coins[entry.key] = coin.copyWith(usdPrice: usdPrice);

// Update wallet coins if exists
if (state.walletCoins.containsKey(coin.abbr)) {
emit(
state.copyWith(
walletCoins: {
...state.walletCoins,
coin.abbr:
state.walletCoins[entry.key]!.copyWith(usdPrice: usdPrice),
},
),
);
}
}
}

if (changed) {
emit(state.copyWith(coins: coins));
final newWalletCoins = state.walletCoins.map(
(String coinId, Coin coin) => MapEntry<String, Coin>(
coinId,
coin.copyWith(usdPrice: coins[coinId]!.usdPrice),
),
);
emit(
state.copyWith(
coins: coins,
walletCoins: {...state.walletCoins, ...newWalletCoins},
),
);
}

log('CEX prices updated', path: 'coins_bloc => updateCoinsCexPrices')
Expand Down Expand Up @@ -353,7 +352,12 @@ class CoinsBloc extends Bloc<CoinsEvent, CoinsState> {
try {
await _kdfSdk.addActivatedCoins(coins);
await Future.wait(
coins.map((coin) => _currentWalletBloc.addCoin(state.coins[coin]!)),
coins.map((coin) async {
final sdkCoin = state.coins[coin] ?? _coinsRepo.getCoin(coin);
if (sdkCoin != null) {
await _currentWalletBloc.addCoin(sdkCoin);
}
}),
);
} catch (e, s) {
log(
Expand All @@ -366,7 +370,12 @@ class CoinsBloc extends Bloc<CoinsEvent, CoinsState> {
return [];
}

final enableFutures = coins.map((coin) => _activateCoin(coin)).toList();
final enabledAssets = await _kdfSdk.assets.getEnabledCoins();
final coinsToActivate =
coins.where((coin) => !enabledAssets.contains(coin));

final enableFutures =
coinsToActivate.map((coin) => _activateCoin(coin)).toList();
final results = <Coin>[];
await for (final coin
in Stream<Coin>.fromFutures(enableFutures).asBroadcastStream()) {
Expand All @@ -389,10 +398,13 @@ class CoinsBloc extends Bloc<CoinsEvent, CoinsState> {
final activatingCoins = Map<String, Coin>.fromIterable(
coins
.map(
(coin) => state.coins[coin]?.copyWith(
state: CoinState.activating,
enabledType: _currentUserCache?.wallet.config.type,
),
(coin) {
final sdkCoin = state.coins[coin] ?? _coinsRepo.getCoin(coin);
return sdkCoin?.copyWith(
state: CoinState.activating,
enabledType: _currentUserCache?.wallet.config.type,
);
},
)
.where((coin) => coin != null)
.cast<Coin>(),
Expand All @@ -407,35 +419,50 @@ class CoinsBloc extends Bloc<CoinsEvent, CoinsState> {
}

Future<Coin> _activateCoin(String coinId) async {
Coin coin = state.coins[coinId]!;
final isLoggedIn = _currentUserCache != null;
if (!isLoggedIn || coin.isActive) {
return coin;
Coin? coin = state.coins[coinId] ?? _coinsRepo.getCoin(coinId);
if (coin == null) {
throw ArgumentError.value(coinId, 'coinId', 'Coin not found');
}

switch (_currentUserCache?.wallet.config.type) {
case WalletType.iguana:
case WalletType.hdwallet:
coin = await _activateIguanaCoin(coin);
case WalletType.trezor:
final asset = _kdfSdk.assets.available[coin.id];
if (asset == null) {
log('Failed to find asset for coin: ${coin.id}', isError: true);
return coin.copyWith(state: CoinState.suspended);
}
final accounts = await _trezorBloc.activateCoin(asset);
final state =
accounts.isNotEmpty ? CoinState.active : CoinState.suspended;
coin = coin.copyWith(state: state, accounts: accounts);
case WalletType.metamask:
case WalletType.keplr:
case null:
break;
try {
final isLoggedIn = _currentUserCache != null;
if (!isLoggedIn || coin.isActive) {
return coin;
}

switch (_currentUserCache?.wallet.config.type) {
case WalletType.iguana:
case WalletType.hdwallet:
coin = await _activateIguanaCoin(coin);
case WalletType.trezor:
coin = await _activateTrezorCoin(coin, coinId);
case WalletType.metamask:
case WalletType.keplr:
case null:
break;
}
} catch (e, s) {
log(
'Error activating coin ${coin!.id.toString()}',
isError: true,
trace: s,
);
}

return coin;
}

Future<Coin> _activateTrezorCoin(Coin coin, String coinId) async {
final asset = _kdfSdk.assets.available[coin.id];
if (asset == null) {
log('Failed to find asset for coin: ${coin.id}', isError: true);
return coin.copyWith(state: CoinState.suspended);
}
final accounts = await _trezorBloc.activateCoin(asset);
final state = accounts.isNotEmpty ? CoinState.active : CoinState.suspended;
return coin.copyWith(state: state, accounts: accounts);
}

Future<Coin> _activateIguanaCoin(Coin coin) async {
try {
log('Enabling a ${coin.name}', path: 'coins_bloc => enable').ignore();
Expand All @@ -460,28 +487,38 @@ class CoinsBloc extends Bloc<CoinsEvent, CoinsState> {
return List.empty();
}

final List<String> coins = currentWallet.config.activatedCoins
.map((abbr) => state.coins[abbr])
.whereType<Coin>()
.map((coin) => coin.abbr)
.toList();

return _activateCoins(coins, emit);
return _activateCoins(currentWallet.config.activatedCoins, emit);
}

Stream<List<Coin>> _reActivateSuspended(
Emitter<CoinsState> emit, {
int attempts = 1,
}) async* {
final List<String> coinsToBeActivated = [];

for (int i = 0; i < attempts; i++) {
final List<String> suspended = state.walletCoins.values
.where((coin) => coin.isSuspended)
.map((coin) => coin.abbr)
.toList();
if (suspended.isEmpty) return;

yield await _activateCoins(suspended, emit);
coinsToBeActivated.addAll(suspended);
coinsToBeActivated.addAll(_getUnactivatedWalletCoins());

if (coinsToBeActivated.isEmpty) return;
yield await _activateCoins(coinsToBeActivated, emit);
}
}

List<String> _getUnactivatedWalletCoins() {
final Wallet? currentWallet = _currentUserCache?.wallet;
if (currentWallet == null) {
return List.empty();
}

return currentWallet.config.activatedCoins
.where((coinId) => !state.walletCoins.containsKey(coinId))
.toList();
}

/// yields one coin at a time to provide visual feedback to the user as
Expand All @@ -507,10 +544,13 @@ class CoinsBloc extends Bloc<CoinsEvent, CoinsState> {
yield coin.copyWith(state: CoinState.suspended);
}

for (final Coin apiCoin in await _coinsRepo.getEnabledCoins()) {
if (!walletCoins.containsKey(apiCoin.abbr)) {
for (final String apiCoinId in await _kdfSdk.assets.getEnabledCoins()) {
if (!walletCoins.containsKey(apiCoinId)) {
// enabled on api side, but not on gui side - enable on gui side
yield apiCoin;
final apiCoin = await _coinsRepo.getEnabledCoin(apiCoinId);
if (apiCoin != null) {
yield apiCoin;
}
}
}
}
Expand Down
Loading