Skip to content

Commit

Permalink
fix: tron fee (#2230)
Browse files Browse the repository at this point in the history
* fix: tron fee

* fix: tron fee calculation
  • Loading branch information
iGroza authored Dec 2, 2024
1 parent 45b10ca commit c96e973
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 59 deletions.
4 changes: 2 additions & 2 deletions src/components/token/token-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ export const TokenRow = ({item, checked = false, onPress}: TokenRowProps) => {
const styles = createTheme({
notWhiteListed: {
backgroundColor: Color.bg6,
borderRadius: 12,
padding: 4,
borderRadius: 4,
paddingVertical: 4,
},
tokenName: {maxWidth: 220},
container: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,7 @@ export const TransactionConfirmation = observer(
)}
</Spacer>
<Button
disabled={
!fee?.expectedFee?.isPositive() && !disabled && !transactionSumError
}
disabled={!fee?.expectedFee && !disabled && !transactionSumError}
variant={ButtonVariant.contained}
i18n={I18N.transactionConfirmationSend}
onPress={onConfirmTransaction}
Expand Down
5 changes: 4 additions & 1 deletion src/components/wallet-card/protection-badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ export const ProtectionBadge = ({
onPressWalletConnect,
walletConnectSessions,
}: ProtectionBadgeProps) => {
const isImported = wallet.isImported || isSecondMnemonic;
const isImported =
wallet.isImported ||
isSecondMnemonic ||
wallet.type === WalletType.watchOnly;

const protectionStatus = useMemo(() => {
// Wallet is 2nd mnemonic (imported) or user have imported this wallet after SSS
Expand Down
1 change: 1 addition & 0 deletions src/models/wallet/wallet.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class WalletModel implements IWalletModel {
case WalletType.mnemonic:
case WalletType.sss:
case WalletType.hot:
case WalletType.watchOnly:
return true;
case WalletType.ledgerBt:
case WalletType.keystone:
Expand Down
6 changes: 5 additions & 1 deletion src/screens/HomeStack/HomeFeedStack/account-info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import {
TransactionStackParamList,
TransactionStackRoutes,
} from '@app/route-types';
import {IToken, IndexerTransaction, ModalType} from '@app/types';
import {HapticEffects, vibrate} from '@app/services/haptic';
import {IToken, IndexerTransaction, ModalType, WalletType} from '@app/types';

export const AccountInfoScreen = observer(() => {
const route = useTypedRoute<
Expand Down Expand Up @@ -60,6 +61,9 @@ export const AccountInfoScreen = observer(() => {

const onPressToken = useCallback(
(w: IWalletModel, token: IToken) => {
if (w.type === WalletType.watchOnly) {
return vibrate(HapticEffects.error);
}
navigation.navigate(HomeStackRoutes.Transaction, {
// @ts-ignore
screen: TransactionStackRoutes.TransactionAddress,
Expand Down
4 changes: 2 additions & 2 deletions src/screens/settings-developer-tools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -516,8 +516,8 @@ TRON:\n${AddressUtils.toTron(watchOnlyAddress)}`,
index === 1 ? 'eth_sendTransaction' : 'eth_signTransaction',
params: [
{
value: '1',
to: '0x415b829d862121f25fcdfdfadf7a705e45249dbc',
value: '1000000',
to: 'TXtLUKcumR3TNpCcCzr4tjjkpKpMeJ5H66',
},
],
};
Expand Down
4 changes: 2 additions & 2 deletions src/services/balance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ export class Balance implements IBalance, ISerializable {

toParsedBalanceNumber = () => {
const b = this.toBalanceString(
undefined,
undefined,
this.precission,
this.precission,
false,
true,
).replaceAll(' ', '');
Expand Down
2 changes: 1 addition & 1 deletion src/services/provider-sss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ export async function onAuthorized(
}>(RemoteConfig.get('sss_generate_shares_url')!, 'shares', [
verifier,
token,
false,
true,
]);
loggerAuthorized.log('Received node details', {
isNew: nodeDetailsRequest.isNew,
Expand Down
168 changes: 121 additions & 47 deletions src/services/tron-network/tron-network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ interface TronTRXTransaction {

type TronTransaction = TronTRC20Transaction | TronTRXTransaction;

const logger = Logger.create('TronNetwork', {});
const logger = Logger.create('TronNetwork', {
stringifyJson: true,
});

export function isTRC20Transaction(
tx: TronTransaction,
Expand All @@ -99,12 +101,22 @@ export function isTRXTransaction(
return !('fee_limit' in tx.raw_data);
}

// TRON CONSTANTS
// 2 bytes is the extra data hex protobuf
const DATA_HEX_PROTOBUF_EXTRA = 2;
// 64 bytes is the maximum result size in a TRON transaction
const MAX_RESULT_SIZE_IN_TX = 64;
// 67 is signature bytes length
const A_SIGNATURE = 67;
// 1 TRX = 1,000,000 SUN
const SUN_PER_TRX = 1_000_000;
const BANDWIDTH_PRICE_IN_SUN = 1000; // 1000 SUN per byte
// 1000 SUN per byte
const BANDWIDTH_PRICE_IN_SUN = 1000;
// 1 energy = 420 SUN
const ENERGY_FEE_RATE = 420;
// constant for minimum TRX transfer fee (0.1 TRX)
const MIN_FEE_TRX = 0.1;
// 65,000 energy is required for a TRC20 transfer
const TRC20_ENERGY_REQUIRED = 65_000;

export class TronNetwork {
static TOKEN_TRANSFER_SELECTOR = 'a9059cbb';
Expand Down Expand Up @@ -204,33 +216,39 @@ export class TronNetwork {
});

try {
const resources = await tronWeb.trx.getAccountResources(address);
const tronAddress = AddressUtils.toTron(address);

const resources = await tronWeb.trx.getAccountResources(tronAddress);
const freeNetRemaining = Math.max(
resources.freeNetLimit - resources.freeNetUsed || 0,
);
const netRemaining = Math.max(
resources.NetLimit - resources.NetUsed || 0,
);

return {
freeNetUsed: resources.freeNetUsed || 0,
freeNetLimit: resources.freeNetLimit || 0,
freeNetRemaining,
netUsed: resources.NetUsed || 0,
netLimit: resources.NetLimit || 0,
netRemaining,
totalBandwidth: freeNetRemaining + netRemaining,
};
} catch (error) {
logger.error('getAccountBandwidth', error);
logger.error('[getAccountBandwidth] Error:', error);
return {
freeNetUsed: 0,
freeNetLimit: 0,
netUsed: 0,
netLimit: 0,
freeNetRemaining: 0,
netRemaining: 0,
totalBandwidth: 0,
};
}
}

static calculateFeeTRX(bandwidthBytes: number) {
// Each byte costs 1000 SUN
const feeInSun = bandwidthBytes * BANDWIDTH_PRICE_IN_SUN;
// Convert SUN to TRX (1 TRX = 1,000,000 SUN)
const feeTRX = feeInSun / SUN_PER_TRX;
return feeTRX;
}

static async estimateFeeSendTRX(
{from, to, value = Balance.Empty}: TxEstimationParams,
provider = Provider.selectedProvider,
Expand All @@ -240,41 +258,83 @@ export class TronNetwork {
});

try {
logger.log('[estimateFeeSendTRX] Starting fee estimation:', {
from,
to,
value: value.toString(),
});

// 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),
value.toParsedBalanceNumber(),
valueSun,
AddressUtils.toTron(from),
);

const rawDataHex = transaction.raw_data_hex;
const bandwidthConsumption =
this.calculateBandwidthConsumption(rawDataHex);
const feeTRX = this.calculateFeeTRX(bandwidthConsumption);
// 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 fees = {
const isAccountActive = !!accountTo.address;

if (!isAccountActive) {
totalFeeInTrx += 1; // Add 1 TRX activation fee
totalFeeInTrx += 0.1; // Add 100 bandwidth fee
}

const result = {
gasLimit: Balance.Empty,
maxBaseFee: Balance.Empty,
maxPriorityFee: Balance.Empty,
expectedFee: new Balance(feeTRX, 0, provider.denom),
expectedFee: new Balance(totalFeeInTrx, 0, provider.denom),
};

return fees;
return result;
} catch (error) {
logger.error('estimateFeeSendTRX error:', error);
logger.error('[estimateFeeSendTRX] Error:', error);

// Fallback fee calculation using standard TRX transfer size
const STANDARD_TRX_TRANSFER_SIZE = 285; // Standard size for TRX transfer in bytes
const fallbackBandwidthConsumption = this.calculateBandwidthConsumption(
'0'.repeat(STANDARD_TRX_TRANSFER_SIZE * 2), // Convert bytes to hex string length
const standardBandwidthConsumption = this.calculateBandwidthConsumption(
'0'.repeat(285 * 2), // 285 bytes is typical TRX transfer size
);
const fallbackFeeTRX = this.calculateFeeTRX(fallbackBandwidthConsumption);
const standardFeeInTrx = Math.max(
(standardBandwidthConsumption * BANDWIDTH_PRICE_IN_SUN) / SUN_PER_TRX,
MIN_FEE_TRX,
);

logger.log('[estimateFeeSendTRX] Using fallback fee calculation:', {
standardBandwidthConsumption,
standardFeeInTrx,
});

// Return fallback fee estimation
return {
gasLimit: Balance.Empty,
maxBaseFee: Balance.Empty,
maxPriorityFee: Balance.Empty,
expectedFee: new Balance(fallbackFeeTRX, 0, provider.denom),
expectedFee: new Balance(standardFeeInTrx, 0, provider.denom),
};
}
}
Expand All @@ -296,26 +356,27 @@ export class TronNetwork {
{type: 'address', value: toAddress},
{
type: 'uint256',
value: value.toParsedBalanceNumber(),
value: value.toParsedBalanceNumber() * SUN_PER_TRX,
},
];

try {
const energyEstimate = await tronWeb.transactionBuilder.estimateEnergy(
contractAddress,
functionSelector,
{},
parameter,
fromAddress,
);

if (!energyEstimate?.energy_required) {
throw new Error('Failed to estimate energy');
let energyEstimate = {
energy_required: TRC20_ENERGY_REQUIRED,
};
try {
energyEstimate = await tronWeb.transactionBuilder.estimateEnergy(
contractAddress,
functionSelector,
{},
parameter,
fromAddress,
);
} catch (error) {
Logger.error('[estimateFeeSendTRC20] Error estimating energy:', error);
}

// Calculate fee based on energy required
// 1 energy = 420 SUN
const ENERGY_FEE_RATE = 420;
const energyFee = energyEstimate.energy_required * ENERGY_FEE_RATE;

// Get transaction structure for bandwidth estimation
Expand All @@ -330,20 +391,30 @@ export class TronNetwork {
fromAddress,
);

const accountResources = await this.getAccountBandwidth(from, provider);
// Calculate bandwidth fee
const bandwidthConsumption = this.calculateBandwidthConsumption(
transaction?.transaction?.raw_data_hex ?? '',
);
const bandwidthFee = this.calculateFeeTRX(bandwidthConsumption);

// Total fee is energy fee + bandwidth fee
const totalFee = energyFee * 1e-6 + bandwidthFee;
// 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;
}

const accountTo = await tronWeb.trx.getAccount(AddressUtils.toTron(to));
const isAccountActive = !!accountTo.address;
if (!isAccountActive) {
totalFeeInTrx += 1; // Add 1 TRX activation fee
totalFeeInTrx += 0.1; // Add 100 bandwidth fee
}

const fees = {
gasLimit: Balance.Empty,
maxBaseFee: Balance.Empty,
maxPriorityFee: Balance.Empty,
expectedFee: new Balance(totalFee, 0, provider.denom),
expectedFee: new Balance(totalFeeInTrx, 0, provider.denom),
};

return fees;
Expand All @@ -365,13 +436,16 @@ export class TronNetwork {
const bandwidthConsumption = this.calculateBandwidthConsumption(
transaction?.transaction?.raw_data_hex ?? '',
);
const feeTRX = this.calculateFeeTRX(bandwidthConsumption);

const fees = {
gasLimit: Balance.Empty,
maxBaseFee: Balance.Empty,
maxPriorityFee: Balance.Empty,
expectedFee: new Balance(feeTRX, 0, provider.denom),
expectedFee: new Balance(
bandwidthConsumption / BANDWIDTH_PRICE_IN_SUN,
0,
provider.denom,
),
};

return fees;
Expand Down

0 comments on commit c96e973

Please sign in to comment.