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.