0

I want to collect some transfer fee besides base, weight, length fee before user makes a transaction.

Like if the base + weight + length fee = 0.1 And For every transaction I want to add additional 0.2 then total fee should become 0.3.

This 0.2 will go in treasury.

How can I implement this in substrate/parachain?

2 Answers 2

1

All of the logic around the transaction fee pallet is wrapped into the trait OnChargeTransaction:

https://paritytech.github.io/substrate/master/pallet_transaction_payment/trait.OnChargeTransaction.html

So you just need to implement this with the logic you want.

You can look at the implementation CurrencyAdapter which is used in Polkadot to see how it can be implemented, and probably you can modify it to do what you want.

For example:

pub struct CustomCurrencyAdapter<C, OU>(PhantomData<(C, OU)>);

impl<T, C, OU> OnChargeTransaction<T> for CustomCurrencyAdapter<C, OU>
where
    T: Config,
    C: Currency<<T as frame_system::Config>::AccountId>,
    C::PositiveImbalance: Imbalance<
        <C as Currency<<T as frame_system::Config>::AccountId>>::Balance,
        Opposite = C::NegativeImbalance,
    >,
    C::NegativeImbalance: Imbalance<
        <C as Currency<<T as frame_system::Config>::AccountId>>::Balance,
        Opposite = C::PositiveImbalance,
    >,
    OU: OnUnbalanced<NegativeImbalanceOf<C, T>>,
{
    type LiquidityInfo = Option<NegativeImbalanceOf<C, T>>;
    type Balance = <C as Currency<<T as frame_system::Config>::AccountId>>::Balance;

    /// Withdraw the predicted fee from the transaction origin.
    ///
    /// Note: The `fee` already includes the `tip`.
    fn withdraw_fee(
        who: &T::AccountId,
        _call: &T::RuntimeCall,
        _info: &DispatchInfoOf<T::RuntimeCall>,
        fee: Self::Balance,
        tip: Self::Balance,
    ) -> Result<Self::LiquidityInfo, TransactionValidityError> {
        if fee.is_zero() {
            return Ok(None)
        }

        let withdraw_reason = if tip.is_zero() {
            WithdrawReasons::TRANSACTION_PAYMENT
        } else {
            WithdrawReasons::TRANSACTION_PAYMENT | WithdrawReasons::TIP
        };


//here--->
        let fee = fee + whatever_you_want;

        match C::withdraw(who, fee, withdraw_reason, ExistenceRequirement::KeepAlive) {
            Ok(imbalance) => Ok(Some(imbalance)),
            Err(_) => Err(InvalidTransaction::Payment.into()),
        }
    }

    /// Hand the fee and the tip over to the `[OnUnbalanced]` implementation.
    /// Since the predicted fee might have been too high, parts of the fee may
    /// be refunded.
    ///
    /// Note: The `corrected_fee` already includes the `tip`.
    fn correct_and_deposit_fee(
        who: &T::AccountId,
        _dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
        _post_info: &PostDispatchInfoOf<T::RuntimeCall>,
        corrected_fee: Self::Balance,
        tip: Self::Balance,
        already_withdrawn: Self::LiquidityInfo,
    ) -> Result<(), TransactionValidityError> {
        if let Some(paid) = already_withdrawn {
            // Calculate how much refund we should return
            let refund_amount = paid.peek().saturating_sub(corrected_fee);
            // refund to the the account that paid the fees. If this fails, the
            // account might have dropped below the existential balance. In
            // that case we don't refund anything.
            let refund_imbalance = C::deposit_into_existing(who, refund_amount)
                .unwrap_or_else(|_| C::PositiveImbalance::zero());
            // merge the imbalance caused by paying the fees and refunding parts of it again.
            let adjusted_paid = paid
                .offset(refund_imbalance)
                .same()
                .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
            // Call someone else to handle the imbalance (fee and tip separately)
            let (tip, fee) = adjusted_paid.split(tip);
            OU::on_unbalanceds(Some(fee).into_iter().chain(Some(tip)));
        }
        Ok(())
    }
}

Search here---> in the code above.

3
  • Hey @shawn thanks for the reply. But is there a way to do it without changing code in transaction payment pallet ? because I am using parity substrate repo directly to for transaction payment pallet. or are you saying that I should create a custom CustomCurrencyAdapter in my code and use that in my code? Please correct If I am taking it in the wrong direction. Thanks Oct 4, 2022 at 21:07
  • This is an external trait implementation that you can plug into an unchanged transaction payment pallet.
    – Shawn Tabrizi
    Oct 4, 2022 at 22:18
  • Hey @shawn, I tried this but I am getting an error. Can you please help? error[E0308]: mismatched types --> network-node/runtime/dev/src/impls.rs:68:25 | 68 | let fee = fee + 3000; | ^^^^ expected associated type, found integer | = note: expected associated type <C as hidden_include::traits::Currency<<T as SysConfig>::AccountId>>::Balance found type {integer} What should I pass here? I want to add 0.3 to the overall fee Nov 16, 2022 at 4:36
0

Just in case you haven't resolved your last question asked after the above answer was accepted, Shawn's answer at Error in currency transfer should explain what is going on with your expected associated type issue and how to fix it.

I was unable to add this as a comment beneath your question unfortunately.

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.