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: 10 additions & 2 deletions packages/komodo_coins/lib/src/asset_filter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,25 @@ class NoAssetFilterStrategy extends AssetFilterStrategy {
/// 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');
const TrezorAssetFilterStrategy({this.hiddenAssets = const {}})
: super('trezor');

final Set<String> hiddenAssets;

@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 ||
final isProtocolSupported = subClass == CoinSubClass.utxo ||
subClass == CoinSubClass.smartChain ||
subClass == CoinSubClass.qrc20;

final hasTrezorCoinField = coinConfig.containsKey('trezor_coin');
final isExcludedAsset = hiddenAssets.contains(asset.id.id);

return isProtocolSupported && hasTrezorCoinField && !isExcludedAsset;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'dart:convert';
import 'package:komodo_defi_rpc_methods/src/internal_exports.dart';

/// Generic response details wrapper for task status responses
class ResponseDetails<T, R extends GeneralErrorResponse> {
class ResponseDetails<T, R extends GeneralErrorResponse, D extends Object> {
ResponseDetails({required this.data, required this.error, this.description})
: assert(
[data, error, description].where((e) => e != null).length == 1,
Expand All @@ -14,7 +14,8 @@ class ResponseDetails<T, R extends GeneralErrorResponse> {
final R? error;

// Usually only non-null for in-progress tasks
final String? description;
/// Additional status information for in-progress tasks
final D? description;

void get throwIfError {
if (error != null) {
Expand All @@ -28,7 +29,9 @@ class ResponseDetails<T, R extends GeneralErrorResponse> {
return {
if (data != null) 'data': jsonEncode(data),
if (error != null) 'error': jsonEncode(error),
if (description != null) 'description': description,
if (description != null)
'description':
description is String ? description : jsonEncode(description),
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,11 @@ class AccountBalanceStatusResponse extends BaseResponse {
mmrpc: json.value<String>('mmrpc'),
status: status!,
// details: status == 'Ok' ? AccountBalanceInfo.fromJson(details) : details,
details: ResponseDetails<AccountBalanceInfo, GeneralErrorResponse>(
details: ResponseDetails<
AccountBalanceInfo,
GeneralErrorResponse,
String
>(
data:
status == SyncStatusEnum.success
? AccountBalanceInfo.fromJson(result.value<JsonMap>('details'))
Expand All @@ -106,7 +110,8 @@ class AccountBalanceStatusResponse extends BaseResponse {
}

final SyncStatusEnum status;
final ResponseDetails<AccountBalanceInfo, GeneralErrorResponse> details;
final ResponseDetails<AccountBalanceInfo, GeneralErrorResponse, String>
details;

@override
JsonMap toJson() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,36 +95,39 @@ class GetNewAddressTaskStatusResponse extends BaseResponse {

if (status == null) {
throw FormatException(
'Unrecognized task status: "$statusString". Expected one of: Ok, InProgress, Error',
'Unrecognized task status: "$statusString". '
'Expected one of: Ok, InProgress, Error',
);
}

final detailsJson = result['details'];
Object? description;
NewAddressInfo? data;
GeneralErrorResponse? error;

if (status == SyncStatusEnum.success) {
data = NewAddressInfo.fromJson(
(detailsJson as JsonMap).value<JsonMap>('new_address'),
);
} else if (status == SyncStatusEnum.error) {
error = GeneralErrorResponse.parse(detailsJson as JsonMap);
} else if (status == SyncStatusEnum.inProgress) {
description = TaskDescriptionParserFactory.parseDescription(detailsJson);
}

return GetNewAddressTaskStatusResponse(
mmrpc: json.value<String>('mmrpc'),
status: status,
details: ResponseDetails<NewAddressInfo, GeneralErrorResponse>(
data:
status == SyncStatusEnum.success
? NewAddressInfo.fromJson(
result
.value<JsonMap>('details')
.value<JsonMap>('new_address'),
)
: null,
error:
status == SyncStatusEnum.error
? GeneralErrorResponse.parse(result.value<JsonMap>('details'))
: null,
description:
status == SyncStatusEnum.inProgress
? result.value<String>('details')
: null,
details: ResponseDetails<NewAddressInfo, GeneralErrorResponse, Object>(
data: data,
error: error,
description: description,
),
);
}

final SyncStatusEnum status;
final ResponseDetails<NewAddressInfo, GeneralErrorResponse> details;
final ResponseDetails<NewAddressInfo, GeneralErrorResponse, Object> details;

@override
JsonMap toJson() {
Expand All @@ -133,6 +136,41 @@ class GetNewAddressTaskStatusResponse extends BaseResponse {
'result': {'status': status, 'details': details.toJson()},
};
}

/// Convert this RPC response into a [NewAddressState].
NewAddressState toNewAddressState(int taskId) {
switch (status) {
case SyncStatusEnum.success:
final addr = details.data!;
return NewAddressState(
status: NewAddressStatus.completed,
address: PubkeyInfo(
address: addr.address,
derivationPath: addr.derivationPath,
chain: addr.chain,
balance: addr.balance,
),
taskId: taskId,
);
case SyncStatusEnum.error:
return NewAddressState(
status: NewAddressStatus.error,
error: details.error?.error ?? 'Unknown error',
taskId: taskId,
);
case SyncStatusEnum.inProgress:
return NewAddressState.fromInProgressDescription(
details.description,
taskId,
);
case SyncStatusEnum.notStarted:
return NewAddressState(
status: NewAddressStatus.error,
error: 'Task not started',
taskId: taskId,
);
}
}
}

// Cancel Request
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'package:komodo_defi_types/komodo_defi_type_utils.dart';
import 'package:komodo_defi_types/komodo_defi_types.dart';

/// Task Description Parser Strategy Pattern
abstract class TaskDescriptionParser {
bool canParse(JsonMap json);
Object parse(JsonMap json);
}

class ConfirmAddressDescriptionParser implements TaskDescriptionParser {
@override
bool canParse(JsonMap json) => json.containsKey('ConfirmAddress');

@override
Object parse(JsonMap json) =>
ConfirmAddressDetails.fromJson(json.value<JsonMap>('ConfirmAddress'));
}

class TaskDescriptionParserFactory {
static final List<TaskDescriptionParser> _parsers = [
ConfirmAddressDescriptionParser(),
];

static Object? parseDescription(Object? detailsJson) {
if (detailsJson is String) {
return detailsJson;
} else if (detailsJson is JsonMap) {
for (final parser in _parsers) {
if (parser.canParse(detailsJson)) {
return parser.parse(detailsJson);
}
}

// Fallback to raw JsonMap
return detailsJson;
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export 'hd_wallet/get_new_address_task.dart';
export 'hd_wallet/hd_wallet_methods.dart';
export 'hd_wallet/scan_for_new_addresses_init.dart';
export 'hd_wallet/scan_for_new_addresses_status.dart';
export 'hd_wallet/task_description_parser.dart';
export 'methods.dart';
export 'nft/enable_nft.dart';
export 'nft/nft_rpc_namespace.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,20 @@ class ContextPrivKeyHDWalletStrategy extends PubkeyStrategy with HDWalletMixin {
balance: newAddress.balance,
);
}

@override
Stream<NewAddressState> getNewAddressStream(
AssetId assetId,
ApiClient client,
) async* {
try {
yield const NewAddressState(status: NewAddressStatus.processing);
final info = await getNewAddress(assetId, client);
yield NewAddressState.completed(info);
} catch (e) {
yield NewAddressState.error('Failed to generate address: $e');
}
}
}

/// HD wallet strategy for Trezor wallets
Expand All @@ -131,10 +145,48 @@ class TrezorHDWalletStrategy extends PubkeyStrategy with HDWalletMixin {
);
}

@override
Stream<NewAddressState> getNewAddressStream(
AssetId assetId,
ApiClient client, {
Duration pollingInterval = const Duration(milliseconds: 200),
}) async* {
try {
final initResponse = await client.rpc.hdWallet.getNewAddressTaskInit(
coin: assetId.id,
accountId: 0,
chain: 'External',
gapLimit: _gapLimit,
);

var finished = false;
while (!finished) {
final status = await client.rpc.hdWallet.getNewAddressTaskStatus(
taskId: initResponse.taskId,
forgetIfFinished: false,
);

final state = status.toNewAddressState(initResponse.taskId);
yield state;

if (state.status == NewAddressStatus.completed ||
state.status == NewAddressStatus.error ||
state.status == NewAddressStatus.cancelled) {
finished = true;
} else {
await Future<void>.delayed(pollingInterval);
}
}
} catch (e) {
yield NewAddressState.error('Failed to generate address: $e');
}
}

Future<NewAddressInfo> _getNewAddressTask(
AssetId assetId,
ApiClient client,
) async {
ApiClient client, {
Duration pollingInterval = const Duration(milliseconds: 200),
}) async {
final initResponse = await client.rpc.hdWallet.getNewAddressTaskInit(
coin: assetId.id,
accountId: 0,
Expand All @@ -150,7 +202,7 @@ class TrezorHDWalletStrategy extends PubkeyStrategy with HDWalletMixin {
);
result = (status.details..throwIfError).data;

await Future<void>.delayed(const Duration(milliseconds: 100));
await Future<void>.delayed(pollingInterval);
}
return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ class SingleAddressStrategy extends PubkeyStrategy {
);
}

@override
Stream<NewAddressState> getNewAddressStream(
AssetId assetId,
ApiClient client,
) async* {
yield NewAddressState.error(
'Single address coins do not support generating new addresses',
);
}

@override
Future<void> scanForNewAddresses(AssetId _, ApiClient __) async {
// No-op for single address coins
Expand Down
Loading
Loading