From 33611b78f4e5f659fc84c5d11bc6b6e8f81d044e Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Mon, 2 Dec 2024 18:26:49 +0100 Subject: [PATCH] Refactor(tx history): Fix misrepresented fees field --- .../common_structures/common_structures.dart | 2 +- .../common_structures/general/fee_info.dart | 87 +++++++++++++++++++ .../general/withdraw_fee.dart | 52 ----------- .../transaction_history/transaction_info.dart | 6 +- .../withdrawal/v2_withdraw_request.dart | 4 +- .../example/lib/screens/withdrawal_page.dart | 14 +-- ...therscan_transaction_history_strategy.dart | 2 +- .../src/withdrawals/withdrawal_manager.dart | 4 +- .../lib/src/komodo_defi_types_base.dart | 2 +- .../lib/src/transactions/transaction.dart | 6 +- .../lib/src/withdrawal/withdrawal_types.dart | 12 +-- packages/komodo_defi_types/lib/types.dart | 1 + 12 files changed, 114 insertions(+), 78 deletions(-) create mode 100644 packages/komodo_defi_rpc_methods/lib/src/common_structures/general/fee_info.dart delete mode 100644 packages/komodo_defi_rpc_methods/lib/src/common_structures/general/withdraw_fee.dart diff --git a/packages/komodo_defi_rpc_methods/lib/src/common_structures/common_structures.dart b/packages/komodo_defi_rpc_methods/lib/src/common_structures/common_structures.dart index 92e5222..025f9e4 100644 --- a/packages/komodo_defi_rpc_methods/lib/src/common_structures/common_structures.dart +++ b/packages/komodo_defi_rpc_methods/lib/src/common_structures/common_structures.dart @@ -23,12 +23,12 @@ export 'activation/tokens_request.dart'; export 'activation/utxo_merge_params.dart'; export 'general/address_format.dart'; export 'general/balance_info.dart'; +export 'general/fee_info.dart'; export 'general/new_address_info.dart'; export 'general/scan_address_info.dart'; export 'general/sync_status.dart'; export 'general/token_balance.dart'; export 'general/wallet_info.dart'; -export 'general/withdraw_fee.dart'; export 'hd_wallet/account_balance_info.dart'; export 'hd_wallet/address_info.dart'; export 'hd_wallet/derivation_method.dart'; diff --git a/packages/komodo_defi_rpc_methods/lib/src/common_structures/general/fee_info.dart b/packages/komodo_defi_rpc_methods/lib/src/common_structures/general/fee_info.dart new file mode 100644 index 0000000..e75139d --- /dev/null +++ b/packages/komodo_defi_rpc_methods/lib/src/common_structures/general/fee_info.dart @@ -0,0 +1,87 @@ +import 'package:decimal/decimal.dart'; +import 'package:komodo_defi_types/komodo_defi_types.dart'; + +class FeeInfo { + const FeeInfo._({ + required this.type, + required this.amount, + this.gas, + this.gasPrice, + }); + + FeeInfo.utxoFixed(Decimal amount) + : this._(type: WithdrawalFeeType.utxo, amount: amount); + + factory FeeInfo.erc20(Decimal gasPrice, int gasLimit) { + final totalFee = _calculateTotalFee(gasPrice, gasLimit); + return FeeInfo._( + type: WithdrawalFeeType.eth, + gas: gasLimit, + gasPrice: gasPrice.toString(), + amount: totalFee, + ); + } + + factory FeeInfo.fromJson(Map json) { + final type = WithdrawalFeeType.parse(json.value('type')); + final gas = json.valueOrNull('gas'); + final gasPriceString = json.valueOrNull('gas_price'); + + // For ERC20-type fees, calculate total from gas if available + if (type == WithdrawalFeeType.eth && + gas != null && + gasPriceString != null) { + final gasPrice = Decimal.parse(gasPriceString); + final totalFee = _calculateTotalFee(gasPrice, gas); + return FeeInfo._( + type: type, + amount: totalFee, + gas: gas, + gasPrice: gasPriceString, + ); + } + + // For other types or when gas details aren't available + return FeeInfo._( + type: type, + amount: Decimal.parse( + json.valueOrNull('amount') ?? json.value('total_fee'), + ), + gas: gas, + gasPrice: gasPriceString, + ); + } + + final WithdrawalFeeType type; + final Decimal amount; + final int? gas; + final String? gasPrice; + + /// Gets the total fee amount in the native coin unit + Decimal get totalFee => amount; + + /// Gets the gas price in Gwei if available + Decimal? get gasPriceInGwei => + gasPrice != null ? Decimal.parse(gasPrice!) : null; + + /// Calculate total fee from gas parameters (gas price in Gwei) + static Decimal _calculateTotalFee(Decimal gasPriceGwei, int gasUnits) { + return (gasPriceGwei.toRational() * + (Decimal.fromInt(gasUnits).toRational() / + Decimal.fromInt(1000000000).toRational())) + .toDecimal(scaleOnInfinitePrecision: 18); + } + + Map toJson() { + return { + 'type': type.toString(), + 'amount': amount.toString(), + 'total_fee': amount.toString(), + if (gas != null) 'gas': gas, + if (gasPrice != null) 'gas_price': gasPrice, + }; + } + + @override + String toString() => toJson().toString(); +} diff --git a/packages/komodo_defi_rpc_methods/lib/src/common_structures/general/withdraw_fee.dart b/packages/komodo_defi_rpc_methods/lib/src/common_structures/general/withdraw_fee.dart deleted file mode 100644 index a6591db..0000000 --- a/packages/komodo_defi_rpc_methods/lib/src/common_structures/general/withdraw_fee.dart +++ /dev/null @@ -1,52 +0,0 @@ -import 'package:decimal/decimal.dart'; -import 'package:komodo_defi_types/komodo_defi_types.dart'; - -class WithdrawFee { - const WithdrawFee._({ - required this.type, - required this.amount, - this.gas, - this.gasPrice, - this.totalFee, - }); - - WithdrawFee.utxoFixed(Decimal amount) - : this._(type: WithdrawalFeeType.utxo, amount: amount); - - // TODO: Verify this constructor reflects - factory WithdrawFee.erc20(Decimal gasPrice, int gasLimit) { - return WithdrawFee._( - type: WithdrawalFeeType.eth, - gas: gasLimit, - gasPrice: gasPrice.toString(), - amount: gasPrice, - ); - } - - factory WithdrawFee.fromJson(Map json) { - return WithdrawFee._( - type: WithdrawalFeeType.parse(json.value('type')), - amount: Decimal.parse( - json.valueOrNull('amount') ?? json.value('total_fee'), - ), - gas: json.valueOrNull('gas'), - gasPrice: json.valueOrNull('gas_price'), - totalFee: Decimal.tryParse(json.valueOrNull('total_fee') ?? ''), - ); - } - final WithdrawalFeeType type; - final Decimal amount; - final int? gas; - final String? gasPrice; - final Decimal? totalFee; - - Map toJson() { - return { - 'type': type, - 'amount': amount.toString(), - if (gas != null) 'gas': gas, - if (gasPrice != null) 'gas_price': gasPrice, - if (totalFee != null) 'total_fee': totalFee.toString(), - }; - } -} diff --git a/packages/komodo_defi_rpc_methods/lib/src/common_structures/transaction_history/transaction_info.dart b/packages/komodo_defi_rpc_methods/lib/src/common_structures/transaction_history/transaction_info.dart index c20ebbc..d773df9 100644 --- a/packages/komodo_defi_rpc_methods/lib/src/common_structures/transaction_history/transaction_info.dart +++ b/packages/komodo_defi_rpc_methods/lib/src/common_structures/transaction_history/transaction_info.dart @@ -29,7 +29,7 @@ class TransactionInfo { confirmations: json.value('confirmations'), timestamp: json.value('timestamp'), feeDetails: json.containsKey('fee_details') - ? WithdrawFee.fromJson(json.value('fee_details')) + ? FeeInfo.fromJson(json.value('fee_details')) : null, transactionFee: json.valueOrNull('transaction_fee'), coin: json.value('coin'), @@ -47,7 +47,7 @@ class TransactionInfo { final int blockHeight; final int confirmations; final int timestamp; - final WithdrawFee? feeDetails; + final FeeInfo? feeDetails; final String? transactionFee; final String coin; final String internalId; @@ -82,7 +82,7 @@ class TransactionInfo { blockHeight: blockHeight, from: from, to: to, - fee: feeDetails?.amount, + fee: feeDetails, txHash: txHash, memo: memo, ); diff --git a/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/v2_withdraw_request.dart b/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/v2_withdraw_request.dart index 170027c..d69034d 100644 --- a/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/v2_withdraw_request.dart +++ b/packages/komodo_defi_rpc_methods/lib/src/rpc_methods/withdrawal/v2_withdraw_request.dart @@ -27,7 +27,7 @@ class WithdrawLegacyRequest final String coin; final String to; final Decimal amount; - final WithdrawFee? fee; + final FeeInfo? fee; final WithdrawalSource? from; final String? memo; final bool max; @@ -82,7 +82,7 @@ class WithdrawInitRequest final String coin; final String to; final String? amount; - final WithdrawFee? fee; + final FeeInfo? fee; final WithdrawalSource? from; final String? memo; final bool max; diff --git a/packages/komodo_defi_sdk/example/lib/screens/withdrawal_page.dart b/packages/komodo_defi_sdk/example/lib/screens/withdrawal_page.dart index 2bb1a50..e720038 100644 --- a/packages/komodo_defi_sdk/example/lib/screens/withdrawal_page.dart +++ b/packages/komodo_defi_sdk/example/lib/screens/withdrawal_page.dart @@ -27,7 +27,7 @@ class _WithdrawalScreenState extends State { PubkeyInfo? _selectedFromAddress; bool _isMaxAmount = false; - WithdrawFee? _selectedFee; + FeeInfo? _selectedFee; WithdrawalPreview? _preview; String? _error; bool _isIbcTransfer = false; @@ -81,7 +81,7 @@ class _WithdrawalScreenState extends State { setState(() { _preview = preview; - _selectedFee = preview.feeDetails; + _selectedFee = preview.fee; }); await _showPreviewDialog(params); @@ -106,7 +106,7 @@ class _WithdrawalScreenState extends State { children: [ Text('Amount: ${_preview!.totalAmount} ${widget.asset.id.id}'), Text('To: ${_preview!.to.first}'), - _buildFeeDetails(_preview!.feeDetails), + _buildFeeDetails(_preview!.fee), if (_preview!.kmdRewards != null) ...[ const SizedBox(height: 8), Text( @@ -144,7 +144,7 @@ class _WithdrawalScreenState extends State { } } - Widget _buildFeeDetails(WithdrawFee details) { + Widget _buildFeeDetails(FeeInfo details) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -247,7 +247,7 @@ class _WithdrawalScreenState extends State { final gasPrice = Decimal.tryParse(value); if (gasPrice != null) { setState(() { - _selectedFee = WithdrawFee.erc20( + _selectedFee = FeeInfo.erc20( gasPrice, _selectedFee?.gas ?? 21000, ); @@ -269,7 +269,7 @@ class _WithdrawalScreenState extends State { final gasLimit = int.tryParse(value); if (gasLimit != null) { setState(() { - _selectedFee = WithdrawFee.erc20( + _selectedFee = FeeInfo.erc20( Decimal.parse(_selectedFee?.gasPrice ?? '1'), gasLimit, ); @@ -314,7 +314,7 @@ class _WithdrawalScreenState extends State { ? null : (value) { setState(() { - _selectedFee = WithdrawFee.utxoFixed(value.first); + _selectedFee = FeeInfo.utxoFixed(value.first); }); }, ), diff --git a/packages/komodo_defi_sdk/lib/src/transaction_history/strategies/etherscan_transaction_history_strategy.dart b/packages/komodo_defi_sdk/lib/src/transaction_history/strategies/etherscan_transaction_history_strategy.dart index 33bb9d3..c960fb1 100644 --- a/packages/komodo_defi_sdk/lib/src/transaction_history/strategies/etherscan_transaction_history_strategy.dart +++ b/packages/komodo_defi_sdk/lib/src/transaction_history/strategies/etherscan_transaction_history_strategy.dart @@ -140,7 +140,7 @@ class EtherscanTransactionStrategy extends TransactionHistoryStrategy { confirmations: tx.value('confirmations'), timestamp: tx.value('timestamp'), feeDetails: tx.valueOrNull('fee_details') != null - ? WithdrawFee.fromJson( + ? FeeInfo.fromJson( tx.value('fee_details') ..setIfAbsentOrEmpty('type', 'Eth'), ) diff --git a/packages/komodo_defi_sdk/lib/src/withdrawals/withdrawal_manager.dart b/packages/komodo_defi_sdk/lib/src/withdrawals/withdrawal_manager.dart index 16d89c0..b32a457 100644 --- a/packages/komodo_defi_sdk/lib/src/withdrawals/withdrawal_manager.dart +++ b/packages/komodo_defi_sdk/lib/src/withdrawals/withdrawal_manager.dart @@ -54,7 +54,7 @@ class WithdrawalManager { amount: Decimal.parse(details.totalAmount), coin: parameters.asset, toAddress: parameters.toAddress, - fee: Decimal.parse(details.feeDetails.toString()), + fee: details.fee, kmdRewardsEligible: details.kmdRewards != null && (details.kmdRewards?.amount ?? '0') != '0', ), @@ -100,7 +100,7 @@ class WithdrawalManager { amount: Decimal.parse(result.totalAmount), coin: result.coin, toAddress: result.to.first, - fee: result.feeDetails.amount, + fee: result.fee, kmdRewardsEligible: result.kmdRewards != null, ), ); diff --git a/packages/komodo_defi_types/lib/src/komodo_defi_types_base.dart b/packages/komodo_defi_types/lib/src/komodo_defi_types_base.dart index a933a09..55e0384 100644 --- a/packages/komodo_defi_types/lib/src/komodo_defi_types_base.dart +++ b/packages/komodo_defi_types/lib/src/komodo_defi_types_base.dart @@ -1,6 +1,6 @@ // TODO: Put public facing types in this file. export 'package:komodo_defi_rpc_methods/komodo_defi_rpc_methods.dart' - show WithdrawFee; + show FeeInfo; typedef LogCallback = void Function(String message); diff --git a/packages/komodo_defi_types/lib/src/transactions/transaction.dart b/packages/komodo_defi_types/lib/src/transactions/transaction.dart index 8da2832..8470458 100644 --- a/packages/komodo_defi_types/lib/src/transactions/transaction.dart +++ b/packages/komodo_defi_types/lib/src/transactions/transaction.dart @@ -33,8 +33,8 @@ class Transaction extends Equatable { from: List.from(json.value('from')), to: List.from(json.value('to')), txHash: json.valueOrNull('tx_hash'), - fee: json.valueOrNull('fee') != null - ? Decimal.parse(json.value('fee')) + fee: json.containsKey('fee') + ? FeeInfo.fromJson(json.value('fee')) : null, memo: json.valueOrNull('memo'), ); @@ -52,7 +52,7 @@ class Transaction extends Equatable { // Null for cases such as SIA coin. TODO: Consider if there is a better way // represent this property usin final String? txHash; - final Decimal? fee; + final FeeInfo? fee; final String? memo; bool get isIncoming => amount > Decimal.zero; diff --git a/packages/komodo_defi_types/lib/src/withdrawal/withdrawal_types.dart b/packages/komodo_defi_types/lib/src/withdrawal/withdrawal_types.dart index 7b70c58..f6b7eb8 100644 --- a/packages/komodo_defi_types/lib/src/withdrawal/withdrawal_types.dart +++ b/packages/komodo_defi_types/lib/src/withdrawal/withdrawal_types.dart @@ -15,7 +15,7 @@ class WithdrawResult { required this.myBalanceChange, required this.blockHeight, required this.timestamp, - required this.feeDetails, + required this.fee, required this.coin, this.internalId, this.kmdRewards, @@ -34,7 +34,7 @@ class WithdrawResult { myBalanceChange: json.value('my_balance_change'), blockHeight: json.value('block_height'), timestamp: json.value('timestamp'), - feeDetails: WithdrawFee.fromJson(json.value('fee_details')), + fee: FeeInfo.fromJson(json.value('fee_details')), coin: json.value('coin'), internalId: json.valueOrNull('internal_id'), kmdRewards: json.containsKey('kmd_rewards') @@ -54,7 +54,7 @@ class WithdrawResult { final String myBalanceChange; final int blockHeight; final int timestamp; - final WithdrawFee feeDetails; + final FeeInfo fee; final String coin; final String? internalId; final KmdRewards? kmdRewards; @@ -71,7 +71,7 @@ class WithdrawResult { 'my_balance_change': myBalanceChange, 'block_height': blockHeight, 'timestamp': timestamp, - 'fee_details': feeDetails.toJson(), + 'fee_details': fee.toJson(), 'coin': coin, if (internalId != null) 'internal_id': internalId, if (kmdRewards != null) 'kmd_rewards': kmdRewards!.toJson(), @@ -110,7 +110,7 @@ class WithdrawalResult { final Decimal amount; final String coin; final String toAddress; - final Decimal fee; + final FeeInfo fee; final bool kmdRewardsEligible; } @@ -160,7 +160,7 @@ class WithdrawParameters { final String asset; final String toAddress; final Decimal? amount; - final WithdrawFee? fee; + final FeeInfo? fee; final WithdrawalSource? from; final String? memo; final bool? ibcTransfer; diff --git a/packages/komodo_defi_types/lib/types.dart b/packages/komodo_defi_types/lib/types.dart index e82c172..8edd6b1 100644 --- a/packages/komodo_defi_types/lib/types.dart +++ b/packages/komodo_defi_types/lib/types.dart @@ -25,6 +25,7 @@ export 'src/exceptions/http_exceptions.dart'; export 'src/generic/result.dart'; export 'src/generic/sync_status.dart'; export 'src/komodo_defi_types_base.dart'; +export 'src/legacy/legacy_coin_model.dart'; export 'src/protocols/base/exceptions.dart'; export 'src/protocols/base/protocol_class.dart'; export 'src/protocols/erc20/erc20_protocol.dart';