I've perused the liquidity docs on the relevant DEXes Uniswap and Pancakeswap, as well as on Github. I've found no good tutorials outlining how to add liquidity by code. It's like trying to complete a 5000 piece jigsaw puzzle by feeling the puzzle outlines by hand. Enough ranting.

Yes I'm aware it's better to hire a developer who already knows this, but as I'm learning to become a web3 developer myself I need to learn how to do this as well.

I will use excerpts from the Safemoon contract code since it's rather well-known.

pragma solidity 0.8.20; // pragma ^0.6.12;

// interface & library code omitted for brevity 

library SafeMath {}

interface IERC20 {}
interface IUniswapV2Factory {} 
interface IUniswapV2Pair {}
interface IUniswapV2Router01 {} 
interface IUniswapV2Router02 is IUniswapV2Router01 {}

abstract contract Context {
    function _msgSender() internal view virtual returns (address payable) {
        return payable(msg.sender);

abstract contract Ownable is Context {
    address private _owner;

    function owner() public view returns (address) { return _owner; }
    // remainder omitted for brevity

contract TestAutoLiquidity is Context, Ownable, IERC20 {

    using SafeMath for uint;

    IUniswapV2Router02 public immutable uniswapV2Router;
    address public immutable uniswapV2Pair;

    bool inSwapAndLiquify;
    bool public swapAndLiquifyEnabled = true;
    uint256 public _maxTxAmount = 5000000 * 10**6 * 10**9;
    uint256 private numTokensSellToAddToLiquidity = 500000 * 10**6 * 10**9;
    event MinTokensBeforeSwapUpdated(uint256 minTokensBeforeSwap);
    event SwapAndLiquifyEnabledUpdated(bool enabled);

    event SwapAndLiquify(uint tokensSwapped, uint ethReceived, uint tokensIntoLiquidity);
    modifier lockTheSwap { 
        inSwapAndLiquify = true; _; inSwapAndLiquify = false;

    constructor() {

        // # I heard this might actually be the Uniswap V1 router address, being a mistake in the deployed contract. 
        IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(0x05fF2B0DB69458A0750badebc4f9e13aDd608C7F);
        // Create a uniswap pair for this new token
        uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory()).createPair(

        // set the rest of the contract variables
        uniswapV2Router = _uniswapV2Router;

    function swapAndLiquify(uint256 contractTokenBalance) private lockTheSwap {
        // split the contract balance into halves
        uint256 half = contractTokenBalance.div(2);
        uint256 otherHalf = contractTokenBalance.sub(half);

        // capture the contract's current ETH balance.
        // this is so that we can capture exactly the amount of ETH that the
        // swap creates, and not make the liquidity event include any ETH that
        // has been manually sent to the contract
        uint256 initialBalance = address(this).balance;

        // swap tokens for ETH
        swapTokensForEth(half); // <- this breaks the ETH -> HATE swap when swap+liquify is triggered

        // how much ETH did we just swap into?
        uint256 newBalance = address(this).balance.sub(initialBalance);

        // add liquidity to uniswap
        addLiquidity(otherHalf, newBalance);
        emit SwapAndLiquify(half, newBalance, otherHalf);

    function swapTokensForEth(uint256 tokenAmount) private {
        // generate the uniswap pair path of token -> weth
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = uniswapV2Router.WETH();

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

        // make the swap
            0, // accept any amount of ETH

    function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private {
        // approve token transfer to cover all possible scenarios
        _approve(address(this), address(uniswapV2Router), tokenAmount);

        // add the liquidity
        uniswapV2Router.addLiquidityETH{value: ethAmount}(
            0, // slippage is unavoidable
            0, // slippage is unavoidable

    // other functions omitted for brevity
    function _transfer(address from, address to, uint256 amount) private {

        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");
        require(amount > 0, "Transfer amount must be greater than zero");
        if (from != owner() && to != owner()) {
            require(amount <= _maxTxAmount, "Transfer amount exceeds the maxTxAmount.");

        // is the token balance of this contract address over the min number of
        // tokens that we need to initiate a swap + liquidity lock?
        // also, don't get caught in a circular liquidity event.
        // also, don't swap & liquify if sender is uniswap pair.
        uint256 contractTokenBalance = balanceOf(address(this));
        if(contractTokenBalance >= _maxTxAmount)
            contractTokenBalance = _maxTxAmount;
        bool overMinTokenBalance = contractTokenBalance >= numTokensSellToAddToLiquidity;
        if (
            overMinTokenBalance &&
            !inSwapAndLiquify &&
            from != uniswapV2Pair &&
        ) {
            contractTokenBalance = numTokensSellToAddToLiquidity;
            //add liquidity
        //indicates if fee should be deducted from transfer
        bool takeFee = true;
        //if any account belongs to _isExcludedFromFee account then remove the fee
        if(_isExcludedFromFee[from] || _isExcludedFromFee[to]){
            takeFee = false;

        //transfer amount, it will take tax, burn, liquidity fee

Basically I need the relevant interfaces for the DEXes so I can look over them, and the functions to interact with them and how they work. I should be able to piece it together from there.

  • Router address for Uniswap V2 and V3 (plus testnet)
  • Router address for PancakeSwap V2 and V3 (plus testnet)

If possible a code example of very simple ERC20 token with auto-liquidity on transfer would be helpful. A generic ERC20 OpenZeppelin import for brevity is fine. It's the auto-liquidity and converting part that I need to learn.


I would have deployed the contract onto the blockchain, but no transactions have yet been made. I would then set the initial token price by manually deploying a small liquidity pair of wETH <=> MyTokenAddress or wBNB <=> MyTokenAddress.


Your Answer

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