diff --git a/src/models/provider/provider-config.model.ts b/src/models/provider/provider-config.model.ts index 6812a5d3d..fa447b7b9 100644 --- a/src/models/provider/provider-config.model.ts +++ b/src/models/provider/provider-config.model.ts @@ -89,4 +89,12 @@ export class ProviderConfigModel { AddressUtils.toEth('0x4FEBDDe47Ab9a76200e57eFcC80b212a07b3e6cE') ); } + + get indexerGasEstimateEnabled() { + return this.config?.indexer_gas_estimate_enabled ?? false; + } + + toJSON() { + return this.config; + } } diff --git a/src/models/wallet/wallet.store.ts b/src/models/wallet/wallet.store.ts index 050cc34df..55b7a55a2 100644 --- a/src/models/wallet/wallet.store.ts +++ b/src/models/wallet/wallet.store.ts @@ -408,24 +408,23 @@ class WalletStore implements RPCObserver { return balance; }; - const balanceModel = new BalanceModel( - useEmptyFallback - ? { - staked: Balance.Empty, - vested: Balance.Empty, - available: Balance.Empty, - total: Balance.Empty, - locked: Balance.Empty, - availableForStake: Balance.Empty, - unlock: new Date(0), - } - : ({} as any), - ); + const balanceModel = new BalanceModel({ + staked: Balance.Empty, + vested: Balance.Empty, + available: Balance.Empty, + total: Balance.Empty, + locked: Balance.Empty, + availableForStake: Balance.Empty, + unlock: new Date(0), + }); + + let emptyBalancesCount = 0; Provider.getAllNetworks().forEach(p => { const balance = getBalanceData(p); if (!balance) { + emptyBalancesCount++; return; } @@ -452,6 +451,13 @@ class WalletStore implements RPCObserver { ); }); + if ( + emptyBalancesCount === Provider.getAllNetworks().length && + !useEmptyFallback + ) { + return new BalanceModel({} as any); + } + return balanceModel; }; diff --git a/src/services/indexer/indexer.ts b/src/services/indexer/indexer.ts index 3ccacf165..743c8ee96 100644 --- a/src/services/indexer/indexer.ts +++ b/src/services/indexer/indexer.ts @@ -18,6 +18,8 @@ import { import {createAsyncTask} from '@app/utils'; import { + GasEstimateRequest, + GasEstimateResponce, IndexerAddressesResponse, IndexerUpdatesResponse, ProviderConfig, @@ -362,4 +364,23 @@ export class Indexer { return false; } } + + async gasEstimate(params: GasEstimateRequest, chainId: number) { + try { + this.checkIndexerAvailability(); + + const response = await jsonrpcRequest( + RemoteConfig.get('proxy_server')!, + 'gasEstimate', + [params, chainId], + ); + + return response ?? {}; + } catch (err) { + if (err instanceof JSONRPCError) { + this.captureException(err, 'Indexer:gasEstimate', err.meta); + } + throw err; + } + } } diff --git a/src/services/indexer/indexer.types.ts b/src/services/indexer/indexer.types.ts index 4c6d68b6c..2ee43c931 100644 --- a/src/services/indexer/indexer.types.ts +++ b/src/services/indexer/indexer.types.ts @@ -99,6 +99,7 @@ export type ProviderConfig = { explorer_token_id_url: string; swap_default_token0: string; swap_default_token1: string; + indexer_gas_estimate_enabled: boolean; }; export type VerifyContractRequest = { @@ -115,3 +116,17 @@ export type VerifyContractResponse = { is_eip4361: boolean; contract: IContract | null; }; + +export type GasEstimateResponce = { + base_fee: string; + expected_fee: string; + gas_limit: string; + priority_fee: string; +}; + +export type GasEstimateRequest = { + from: string; + to: string; + value: string; + data?: string; +}; diff --git a/src/services/tron-network/tron-network.ts b/src/services/tron-network/tron-network.ts index e84304b94..3d0bc6825 100644 --- a/src/services/tron-network/tron-network.ts +++ b/src/services/tron-network/tron-network.ts @@ -6,6 +6,7 @@ import {Provider, ProviderModel} from '@app/models/provider'; import {Balance} from '../balance'; import {TxEstimationParams} from '../eth-network/types'; +import {Indexer} from '../indexer'; type TronSignedTransaction = { visible: boolean; @@ -251,51 +252,67 @@ export class TronNetwork { {from, to, value = Balance.Empty}: TxEstimationParams, provider = Provider.selectedProvider, ): Promise { - const tronWeb = new tron.TronWeb({ - fullHost: provider.ethRpcEndpoint, - }); - try { - logger.log('[estimateFeeSendTRX] Starting fee estimation:', { - from, - to, - value: value.toString(), + const tronWeb = new tron.TronWeb({ + fullHost: provider.ethRpcEndpoint, }); + if (provider.config.indexerGasEstimateEnabled) { + try { + const gasEstimateResponse = await Indexer.instance.gasEstimate( + { + from: AddressUtils.toTron(from), + to: AddressUtils.toTron(to), + value: String( + value.toParsedBalanceNumber() * 10 ** provider.decimals, + ), + }, + Number(provider.ethChainId), + ); + + return { + gasLimit: Balance.Empty, + maxBaseFee: Balance.Empty, + maxPriorityFee: Balance.Empty, + expectedFee: new Balance( + gasEstimateResponse.expected_fee, + provider.decimals, + provider.denom, + ), + }; + } catch (err) { + Logger.captureException(err, 'indexer gas estimate failed', { + request: { + from: AddressUtils.toTron(from), + to: AddressUtils.toTron(to), + value: String( + value.toParsedBalanceNumber() * 10 ** provider.decimals, + ), + }, + chain_id: provider.ethChainId, + }); + } + } // 1. Get account resources and check activation const accountResources = await this.getAccountBandwidth(from, provider); - logger.log('[estimateFeeSendTRX] Account resources:', accountResources); - // 2. Create transaction to get actual bandwidth consumption const valueSun = value.toParsedBalanceNumber() * SUN_PER_TRX; - logger.log('[estimateFeeSendTRX] Creating transaction with value:', { - valueTRX: value.toParsedBalanceNumber(), - valueSun, - }); - const transaction = await tronWeb.transactionBuilder.sendTrx( AddressUtils.toTron(to), valueSun, AddressUtils.toTron(from), ); - // 3. Calculate total bandwidth consumption const bandwidthConsumption = this.calculateBandwidthConsumption( transaction.raw_data_hex, ); - logger.log( - '[estimateFeeSendTRX] Bandwidth consumption:', - bandwidthConsumption, - ); // 5. Calculate required bandwidth fee let totalFeeInTrx = 0; // fee can be 0 if no bandwidth is consumed if (bandwidthConsumption > accountResources.totalBandwidth) { totalFeeInTrx = bandwidthConsumption / BANDWIDTH_PRICE_IN_SUN; } - // 6. Check account activation and calculate total fee const accountTo = await tronWeb.trx.getAccount(AddressUtils.toTron(to)); - const isAccountActive = !!accountTo.address; if (!isAccountActive) { @@ -327,11 +344,6 @@ export class TronNetwork { MIN_FEE_TRX, ); - logger.log('[estimateFeeSendTRX] Using fallback fee calculation:', { - standardBandwidthConsumption, - standardFeeInTrx, - }); - return { gasLimit: Balance.Empty, maxBaseFee: Balance.Empty, @@ -384,9 +396,8 @@ export class TronNetwork { // Calculate fee based on energy required const energyFee = energyEstimate.energy_required * ENERGY_FEE_RATE; - logger.log('[estimateFeeSendTRC20] Energy fee:', energyFee); // Get transaction structure for bandwidth estimation - const transaction = await tronWeb.transactionBuilder.triggerSmartContract( + const txWrapper = await tronWeb.transactionBuilder.triggerSmartContract( contractAddress, functionSelector, { @@ -396,25 +407,60 @@ export class TronNetwork { parameter, fromAddress, ); - logger.log('[estimateFeeSendTRC20] Transaction:', transaction); + + if (provider.config.indexerGasEstimateEnabled) { + try { + const gasEstimateResponse = await Indexer.instance.gasEstimate( + { + from: fromAddress, + to: contractAddress, + value: String( + value.toParsedBalanceNumber() * 10 ** provider.decimals, + ), + data: txWrapper.transaction.raw_data.contract[0].parameter.value + .data, + }, + Number(provider.ethChainId), + ); + + return { + gasLimit: Balance.Empty, + maxBaseFee: Balance.Empty, + maxPriorityFee: Balance.Empty, + expectedFee: new Balance( + gasEstimateResponse.expected_fee, + provider.decimals, + provider.denom, + ), + }; + } catch (err) { + Logger.captureException(err, 'indexer gas estimate failed', { + request: { + from: fromAddress, + to: contractAddress, + value: String( + value.toParsedBalanceNumber() * 10 ** provider.decimals, + ), + data: txWrapper.transaction.raw_data.contract[0].parameter.value + .data, + }, + chain_id: provider.ethChainId, + }); + } + } + const accountResources = await this.getAccountBandwidth(from, provider); - logger.log('[estimateFeeSendTRC20] Account resources:', accountResources); // Calculate bandwidth fee const bandwidthConsumption = this.calculateBandwidthConsumption( - transaction?.transaction?.raw_data_hex ?? '', - ); - logger.log( - '[estimateFeeSendTRC20] Bandwidth consumption:', - bandwidthConsumption, + txWrapper?.transaction?.raw_data_hex ?? '', ); + // 5. Calculate required bandwidth fee let totalFeeInTrx = energyFee / 10 ** 6; if (bandwidthConsumption > accountResources.totalBandwidth) { totalFeeInTrx = bandwidthConsumption / BANDWIDTH_PRICE_IN_SUN; } - logger.log('[estimateFeeSendTRC20] Total fee in TRX:', totalFeeInTrx); const accountTo = await tronWeb.trx.getAccount(AddressUtils.toTron(to)); - logger.log('[estimateFeeSendTRC20] Account to:', accountTo); const isAccountActive = !!accountTo.address; if (!isAccountActive) { totalFeeInTrx += 1; // Add 1 TRX activation fee