Simple ERC20 token fees

Hey guys!
As I see a lot of people out there starting with solidity but wondering how do to simple token transfer fees I will showcase some solutions for that.

function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
        }
        _balances[to] += amount;

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }

This is the standart transfer function of openzeppelin.
Now we can see it subtracts the amount from the sender and gives it to the receiver.
Now where do we need to put our transfer fees?
We will put them after the amount was deducted from the sender's balance.
(I will just use some code of the transfer function as we don't need it all to understand what happens)
BURN FEE:
Let's say we want to have a burn fee of 1% how are we going to do this.
Well we know that we can burn tokens with the burn function so we can use it.

 uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
        }
         uint256 amountToBurn = amount / 100;
         amount -= amountToBurn;
         _totalSupply -= amountToBurn;
        _balances[to] += amount;

Now what have we done?
We defined a amount we want to burn (1%)
We deducted this burn amount from the amount that should be send to the receiver.
Now we need to decrease the totalsupply by the burn amount to finally burn the tokens.
The last step is adding the amount-fee to the receiver's balance.

MARKETING FEES:
A lot of projects have a so called marketing fee.
The marketing fee just takes a percentage of the amount and sends it to another wallet (the marketing wallet). Now it's up to you how you define marketing. Maybe it's actual marketing maybe it's just money that goes into the pocket of the project ower.
Anyways we can do the same as we did with the burn fee but just a bit different so we acutally don't do the same.
Oh also we will use a 1% marketing fee for this example.

uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
        }
         uint256 amountToMarketing = amount / 100;
         amount -= amountToMarketing;
         _balances[MARKETING WALLET] += amountToMarketing;
        _balances[to] += amount;

So what have we done here?
We have done something beatiful, same can't be said about you.
Anyways we again define our fee of 1%
We again deduct the marketing fee from the amount.
But the third step is different. Instead of sending the tokens to hell and burning them we are sending them to you, well we still send them to hell but we are not burning them.
You might ask yourself is this good for the project?
probably not gonna make that much of a difference.
Is it good for you?
Yes, Because you gonna make money of off every transfer

BUY / SELL FEE:
Now a buy or sell fee can be also a burn fee or marketing fee but what we did til now was a fee on every transfer.
So how can we check if the sender is buying or selling.
Very easy!
On a sell we send tokens to the router and get some money for it.
So if we send money to the router (to = router) then we sell so we better put a 90% tax on that one
And if the router sends us tokens it means we send him money (from = router) then we will just give him a 10% tax because we still need to earn our bread.
But how do we check it?
With if statements of course.

//define a router variable outside of the transfer function
 address router = ROUTER_ADDRESS

 uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
        }
        if(from == router) {
         //do what happens on buy
        } else if(to == router) {
        //do what happens on sell
        }
        _balances[to] += amount;

END:
Now this are the basics so better lean them, your parents didn't and now there is you.
Anyways if you want a liquidity fee you would interact with the router and add liquidity with the defined liquidity fee.
I hope this helped you and if not It was nice to steal your time!

Have a good one!

7 Likes

Thanks bros for this post. It's very helpful. I need your help. Can you send the money for Marketing_Fee to Marketing Wallet and pay in BNB not with token? Secondly, How do I pay Liquidity_Fee, either selling or buying BuyBack to Liquidity pool?

Also, I need to not perform transaction if paused

1 Like

Well, to receive the rewards in bnb you can write a function that converts your token into bnb through pancakeswap.
For the liquidity fee you need to swap 50% into bnb and use the addLiquidity function on the router to add the liquidity.

1 Like

Thanks Team_X for your quick reply, I really appreciate. Please can you give me sample code for it. It very urgent. Thanks in advance!

1 Like

See what I did so far;
I copy ERC20.sol into my code to insert my fee codes into _transfer function. First, I declare this above;

    uint256 private Burn_Fee = 1;
    uint256 private Marketing_Fee = 4;
    uint256 private Liquidity_Fee = 3;
    address private Marketing_Wallet = 0x622...;

Then inside _transfer function we have this;

    function _transfer(address from, address to, uint256 amount) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");
        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance.sub(amount);

            uint256 amountToBurn = amount.mul(Burn_Fee.div(100));
            uint256 amountToMarket = amount.mul(Marketing_Fee.div(100));
            uint256 amountToLiquidity = amount.mul(Liquidity_Fee.div(100));

            amount = amount.sub(amountToBurn.add(amountToMarket.add(amountToLiquidity)));
            _totalSupply -= amountToBurn; //Burnt
            //_balances[Marketing_Wallet] += amountToMarket;
            PayOutFees(from, Marketing_Wallet, amountToMarket);
        }

        _balances[to] += amount;

        emit Transfer(from, to, amount);
        _afterTokenTransfer(from, to, amount);
    }

Then, I created a payable function called PayOutFees to send BNB to receiver's wallet, see code below;

     function PayOutFees(address sender, address receiver, uint256 amount) public payable virtual returns (bool) {
        require(sender != address(0), "SoftMath: transfer sender the zero address");
        require(receiver != address(0), "SoftMath: transfer receiver the zero address");
        _beforeTokenTransfer(sender, receiver, amount);
        unchecked{
            _balances[receiver] += amount;
        }        
        emit Transfer(sender,receiver,amount);
        _afterTokenTransfer(sender, receiver, amount);
        return true;
    }

With this payable function will I be able to receive BNB, and also looking at the amount to send to my BNB...Is it not miscalculated figure. If my my token is TKN and sender (from) have 5000 TKN and send 1000 TKN to receiver (to)....spending his BNB to send it,
Assuming I charged 4% of TKN as Marketing_Fee, Out of 1000TKN sending, mine will be 40TKN but I want to transfer this to Markeking wallet as BNB equivalent.

Hope when using payable...It will send exact equivalent value of BNB for 40TKN to my Marketing_Fee wallet....or tell me what happen there?

I don't know I to go about the Liquidity_Fee stuff adding 50% of deduction to BNB and 50% to Token.

1 Like

First of all you need a Uniswap interface

interface IUniswapV2Router01 {
    function factory() external pure returns (address);

    function WETH() external pure returns (address);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint256 amountADesired,
        uint256 amountBDesired,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    )
        external
        returns (
            uint256 amountA,
            uint256 amountB,
            uint256 liquidity
        );

    function addLiquidityETH(
        address token,
        uint256 amountTokenDesired,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    )
        external
        payable
        returns (
            uint256 amountToken,
            uint256 amountETH,
            uint256 liquidity
        );

    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint256 liquidity,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountA, uint256 amountB);

    function removeLiquidityETH(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountToken, uint256 amountETH);

    function removeLiquidityWithPermit(
        address tokenA,
        address tokenB,
        uint256 liquidity,
        uint256 amountAMin,
        uint256 amountBMin,
        address to,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256 amountA, uint256 amountB);

    function removeLiquidityETHWithPermit(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256 amountToken, uint256 amountETH);

    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function swapTokensForExactTokens(
        uint256 amountOut,
        uint256 amountInMax,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function swapExactETHForTokens(
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable returns (uint256[] memory amounts);

    function swapTokensForExactETH(
        uint256 amountOut,
        uint256 amountInMax,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function swapExactTokensForETH(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function swapETHForExactTokens(
        uint256 amountOut,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable returns (uint256[] memory amounts);

    function quote(
        uint256 amountA,
        uint256 reserveA,
        uint256 reserveB
    ) external pure returns (uint256 amountB);

    function getAmountOut(
        uint256 amountIn,
        uint256 reserveIn,
        uint256 reserveOut
    ) external pure returns (uint256 amountOut);

    function getAmountIn(
        uint256 amountOut,
        uint256 reserveIn,
        uint256 reserveOut
    ) external pure returns (uint256 amountIn);

    function getAmountsOut(uint256 amountIn, address[] calldata path)
        external
        view
        returns (uint256[] memory amounts);

    function getAmountsIn(uint256 amountOut, address[] calldata path)
        external
        view
        returns (uint256[] memory amounts);
}

interface IUniswapV2Router02 is IUniswapV2Router01 {
    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountETH);

    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256 amountETH);

    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external;

    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable;

    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external;
}

Then you need to declare a router variable

IUniswapV2Router02 public uniswapV2Router; // set variable

Then you need a function that swaps your token for eth/bnb

function _swapTokensForEth(uint256 _amount) private returns(bool) {
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = uniswapV2Router.WETH();

        _approve(address(this), address(uniswapV2Router), _amount);

        uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            _amount,
            0,
            path,
            address(this),
            block.timestamp
        );
        return true;
    }

Then you need a function that adds liquidity

function _addLiquidity(uint256 _amount, uint256 _ethAmount) private {
        _approve(address(this), address(uniswapV2Router), _amount);

        uniswapV2Router.addLiquidityETH{value: _ethAmount}(
            address(this),
            _amount,
            0, 
            0, 
            address(this),
            block.timestamp
        );
    }

Then you need a function that combines the other functions into a liquidity fee

function _liquidityFee(uint256 _amount) private returns(uint256) {
        uint256 liquidityFeeAmount = _amount / 1000 * liquidityFee;
        uint256 half = liquidityFeeAmount / 2;
        uint256 otherHalf = liquidityFeeAmount - half;
        uint256 initialBalance = address(this).balance;
        _swapTokensForEth(half);
        uint256 newBalance = address(this).balance - initialBalance;
        _addLiquidity(otherHalf, newBalance);
        return liquidityFeeAmount;
    }

It takes 50% of the _amount (fee) and swaps it for bnb
then it uses the addLiquidity function to add BNB/Token liquidity and it burns/locks it directly..

For the marketing fee you can just use the swapTokensForEth function and then send the money to the marketing wallet.
This would look like this:

function  _marketingFee(uint _amount) private {
 _swapTokensForEth(_amount);
 payable(marketingWallet).transfer(IERC20(uniswapV2Router.WETH()).balanceOf(address(this)))
}
2 Likes

is this for the fee of buying and selling?

1 Like

Thanks for post @Team_X. Just curios about one thing. Isn't the pair contract we should check against for buy/sell fees? Pair contract is receiving/sending tokens, not router if I understood well?

1 Like

The code is kinda old and I must say there are some factual errors but the code probably works as far as I remember but it's still not best practice. The router is receiving token. If receiver is router and someone selling and it sender is router then someones buying.

1 Like

I need a code to set up buying and selling fees, what should I do? Please help me get the basic code, thank you very much.

1 Like

Hey, add this to your transfer function. The first if statement is for buyTax and the 2nd for sellTax. Please note that you need to know the address of the pool pair and the router.

if (sender == uniswapV2Pair && recipient != address(uniswapV2Router)) {
//buy
            taxFee = amount * buyFee
        }

        if (recipient == uniswapV2Pair && sender != address(uniswapV2Router)) {
//sell
            taxFee = amount * sellFee;
        }
2 Likes

That's great, man. I'm new so there are a lot of things I'm still confused about. I look forward to connecting and receiving your help. I'm willing to pay for your expertise.

1 Like

If you want to get a custom smart contract / token then you can contact me on telegram: @solidityX
Looking forward to talk to you!

1 Like

Thank you this has been very helpful