Regarding the EVM gas fee used for calling precompile functions, how can I set that fee to zero so that the MetaMask will show zero fee at invoking those precompile functions?

I saw a demonstration of that zero gas transaction from Moombeam's tutorial https://youtu.be/V9KfvhoqLJ4?t=5348 and its original code is here https://github.com/PureStake/substrate-node-template/commit/142fe9a5ca9ca787925d3bd51c55db44caf444cd

But that video was posted about 1 year ago, so now the code has changed quite a lot...

I can achieve zero fee on MetaMask as the video above did by setting FixedGasPrice and GasWeightMapping below to return zeros in pallet_evm::Config:

impl pallet_evm::Config for Runtime {
  type FeeCalculator = FixedGasPrice;
  type GasWeightMapping = GasWeightMapping;

BUT that makes ALL transactions gasless! Not good. And in my parachain, that makes such feeless transactions not to be accepted by the validators somehow...

Here is Moonbeam recent code on precompile https://github.com/PureStake/moonbeam/blob/v0.26.0/precompiles/balances-erc20/src/lib.rs

My file structure:

|- balances-erc20 -> src/lib.rs
|- utils -> src/lib.rs

From Parallel Finance's transfer() function in precompiles/balances-erc20/src/lib.rs:

fn transfer(handle: &mut impl PrecompileHandle) -> EvmResult<PrecompileOutput> {
    handle.record_log_costs_manual(3, 32)?;

    // Parse input.
    let mut input = handle.read_input()?;

    let to: H160 = input.read::<Address>()?.into();
    let amount: U256 = input.read()?;

    // Build call with origin.
        let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
        let to = Runtime::AddressMapping::into_account_id(to);
        let amount = Self::u256_to_amount(amount)?;

        // Dispatch call (if enough gas).
            pallet_balances::Call::<Runtime, Instance>::transfer {
                dest: Runtime::Lookup::unlookup(to),
                value: amount,


    // Build output.

I found out this RuntimeHelper::<Runtime>::try_dispatch() function was imported from my precompiles/utils/src/lib.rs:

inside try_dispatch() in precompiles/utils/src/lib.rs ... see https://github.com/parallel-finance/parallel/blob/master/precompiles/utils/src/lib.rs#L171:

pub fn try_dispatch<Call>(...){
  let call = Runtime::Call::from(call);
  let dispatch_info = call.get_dispatch_info();

  let required_gas = Runtime::GasWeightMapping::weight_to_gas(dispatch_info.weight);

  let used_weight = call.dispatch(origin)
      .map_err(|e| revert(alloc::format!("Dispatched call failed with error: {:?}", e)))?

so I tried to set that required_gas to 0 in my precompiles/balances-erc20/src/lib.rs:

let mut call = pallet_balances::Call::<Runtime, Instance>::transfer {
   dest: Runtime::Lookup::unlookup(to),
   value: amount,
call.get_dispatch_info().weight= 0;


But that does not change the gas fee!??

This post talks briefly about base_fee but not very clear on how to do it... How to set base_fee and len_fee to zero?

And docs talking about base fee and length fee, etc... https://docs.substrate.io/build/tx-weights-fees/

Now, I have made a simple feeless_transfer() function from the above transfer() function. And my feeless_transfer() calls another feeless_transfer() that is in my pallet.

This Ok(Pays::No.into()) in the pallet functions will make the actual_weight to become None inside precompiles/utils/src/lib.rs: dispatch():

let actual_weight = call
            .map_err(|e| revert(alloc::format!("Dispatched call failed with error: {:?}", e)))?

And setting the #[pallet::weight(0)] above the pallet functions will make call.get_dispatch_info().weight to produce zero weight_fee.

Still, how can I get zero fee on MetaMask ?

  • Also would like to know this. It does seem like the balance is checked in the send_raw_transaction EVM call here. Want to know if there is a clean way to bypass this for certain precompiles.
    – Zeeshan
    Sep 22, 2022 at 12:58
  • I've the exact same question than you, how did you solved it in the end ?
    – lucgerrits
    Mar 8, 2023 at 15:39
  • not solved yet. what you see here is what I have so far... :(
    – Russo
    Mar 9, 2023 at 7:31

2 Answers 2


I got it to work using this pallet config:

dynamicFee.targetMinGasPrice: Option<U256>

dynamicFee.minGasPrice: U256

baseFee.elasticity: Permill

baseFee.baseFeePerGas: U256

Note: You have to set (Edit fee gas > Advanced) priority fee to 0 in metamask to get 0 fees.

  • Very good. Thanks
    – Russo
    Mar 17, 2023 at 8:26

yes, you need to set Pays::No as return value for a feeless transaction. And for eip1559 at least base_fee is required for gas fee calculation. not sure fully understand what you mean and better add more detail here

  • please see my update above
    – Russo
    Sep 26, 2022 at 14:38
  • can I cancel or remove that base_fee conditionally?
    – Russo
    Sep 26, 2022 at 15:46
  • 1
    no,I mean the concept in eip1559, if with metamask normally Max Priority fee and Max fee are estimated based on the previous block’s history,Base fee is defined by the protocol and will be burnt. And I remember there is some default implementation in frontier
    – Ron
    Sep 27, 2022 at 5:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.