Building a Liquidity Pool Smart Contract for Token Swapping

Rohit Goyal
Coinmonks
5 min readAug 21, 2023

--

# Exploring the Liquidity Management Smart Contract: Function Breakdown

In the world of decentralized finance (DeFi), smart contracts play a pivotal role in enabling various financial activities. One such contract is the `Liquidity` smart contract, designed to manage liquidity provision and token swapping between two ERC-20 tokens, DOGE and USDT. In this blog, we’ll dissect the key functions of this contract to understand its inner workings and the functionalities it offers.

Photo by Jievani Weerasinghe on Unsplash

`setAddresses(address _DOGE, address _usdt)` Function

The `setAddresses()` function is used by the contract owner to set the addresses of the DOGE and USDT tokens. This step is vital as it establishes the connection between the contract and the tokens it will be handling. The `IERC20` interfaces are utilized to create instances of the token contracts.

function setAddresses(address _DOGE, address _usdt) public onlyOwner {
DOGE = IERC20(_DOGE);
USDT = IERC20(_usdt);
}

`getReserves()` Function

The `getReserves()` function enables external parties to retrieve the current reserves of the DOGE and USDT tokens within the contract. These reserves are crucial for various calculations throughout the contract’s operations, ensuring accurate liquidity provision and token swapping.

function getReserves()
public
view
returns (uint256 _reserve1, uint256 _reserve2)
{
_reserve1 = reserve1;
_reserve2 = reserve2;
}

`quote(uint256 amountA, uint256 reserveA, uint256 reserveB)` Function

The `quote()` function is used internally to calculate the quote for token swapping. Given the desired `amountA` to swap and the current reserves of tokens A (`reserveA`) and B (`reserveB`), this function computes the corresponding amount of token B that will be received after the swap. It ensures that there is sufficient liquidity for the swap to occur.

function quote(
uint256 amountA,
uint256 reserveA,
uint256 reserveB
) internal pure returns (uint256) {
require(amountA > 0, "UniswapV2Library: INSUFFICIENT_AMOUNT");
require(
reserveA > 0 && reserveB > 0,
"UniswapV2Library: INSUFFICIENT_LIQUIDITY"
);
uint256 amountB = amountA.mul(reserveB) / reserveA;
return amountB;
}

`_addLiquidity(uint256 _DOGEQuantity, uint256 _USDTQuantity)` Function

The `_addLiquidity()` function calculates the optimal amounts of DOGE and USDT to be added for liquidity provisioning. It considers the current reserves and balances, as well as the desired quantities of both tokens. The calculated amounts ensure that the liquidity pool remains balanced and efficient.

function _addLiquidity(
uint256 _DOGEQuantity,
uint256 _USDTQuantity
) internal view returns (uint256 amountA, uint256 amountB) {
require(
_DOGEQuantity != 0 && _USDTQuantity != 0,
"token quantity could not be zero"
);
(uint256 reserveA, uint256 reserveB) = getReserves();
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (_DOGEQuantity, _USDTQuantity);
} else {
uint256 amount2Optimal = quote(_DOGEQuantity, reserveA, reserveB);
if (amount2Optimal <= _USDTQuantity) {
(amountA, amountB) = (_DOGEQuantity, amount2Optimal);
} else {
uint256 amountAOptimal = quote(
_USDTQuantity,
reserveB,
reserveA
);
assert(amountAOptimal <= _DOGEQuantity);
(amountA, amountB) = (amountAOptimal, _USDTQuantity);
}
}
}

`addLiquidity(uint256 amountDOGE, uint256 amountUSDT, address to)` Function

The `addLiquidity()` function allows users to deposit DOGE and USDT tokens to provide liquidity. It calculates the optimal amounts of tokens to add using the `_addLiquidity()` function, transfers the tokens from the user’s address to the contract, and mints LP tokens representing the provided liquidity.

function addLiquidity(
uint256 amountDOGE,
uint256 amountUSDT,
address to
) external returns (uint256 amountA, uint256 amountB, uint256 liquidity) {
(amountA, amountB) = _addLiquidity(amountDOGE, amountUSDT);
DOGE.safeTransferFrom(msg.sender, address(this), amountA);
USDT.safeTransferFrom(msg.sender, address(this), amountB);
liquidity = mintLPToken(to);
}

`mintLPToken(address to)` Function

The `mintLPToken()` function calculates the amount of LP tokens to mint when liquidity is added. It ensures that the initial liquidity amount meets the minimum requirement and distributes LP tokens proportionally to users.

function mintLPToken(address to) internal returns (uint256 liquidity) {
(uint256 _reserve0, uint256 _reserve1) = getReserves(); // gas savings
uint256 balance0 = DOGE.balanceOf(address(this));
uint256 balance1 = USDT.balanceOf(address(this));
uint256 amount0 = balance0.sub(_reserve0);
uint256 amount1 = balance1.sub(_reserve1);

uint256 _totalLiquidity = totalLiquidity;
if (_totalLiquidity == 0) {
liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
userLiquidity[address(0)] = MINIMUM_LIQUIDITY;
} else {
liquidity = Math.min(
amount0.mul(_totalLiquidity) / _reserve0,
amount1.mul(_totalLiquidity) / _reserve1
);
}
require(liquidity > 0, "INSUFFICIENT_LIQUIDITY_MINTED");
userLiquidity[to] += liquidity;
totalLiquidity += liquidity;
reserve1 = DOGE.balanceOf(address(this));
reserve2 = USDT.balanceOf(address(this));
}

`burn(uint256 liquidity)` Function

The `burn()` function calculates the amounts of DOGE and USDT tokens to return to users when liquidity is removed. It determines the proportional distribution based on the provided liquidity and ensures that both tokens are returned in proper amounts.

function burn(
uint256 liquidity
) internal returns (uint256 amount0, uint256 amount1) {
uint256 balance0 = DOGE.balanceOf(address(this));
uint256 balance1 = USDT.balanceOf(address(this));
uint256 _totalLiquidity = totalLiquidity;
amount0 = liquidity.mul(balance0) / _totalLiquidity;
amount1 = liquidity.mul(balance1) / _totalLiquidity; // using balances ensures pro-rata distribution
require(
amount0 > 0 && amount1 > 0,
"INSUFFICIENT_LIQUIDITY_BURNED: Increase Liquidity amount"
);
totalLiquidity -= liquidity;
}

`removeLiquidity(uint256 liquidity, address to)` Function

The `removeLiquidity()` function allows users to remove liquidity from the pool. It calculates the amounts of DOGE and USDT tokens to be returned to the user and transfers the tokens accordingly. The user’s liquidity is updated, and the total liquidity and reserves are adjusted.

function removeLiquidity(
uint256 liquidity,
address to
) public returns (uint256 amountA, uint256 amountB) {
require(
userLiquidity[msg.sender] >= liquidity,
"INSUFFICIENT_LIQUIDITY"
);
(amountA, amountB) = burn(liquidity);

DOGE.safeTransfer(to, amountA);

USDT.safeTransfer(to, amountB);
userLiquidity[msg.sender] -= liquidity;
reserve1 = DOGE.balanceOf(address(this));
reserve2 = USDT.balanceOf(address(this));

}

`swapDOGEForUSDT(uint256 amountIn, address _to)` Function

The `swapDOGEForUSDT()` function facilitates the swapping of DOGE tokens for USDT tokens. It calculates the output amount of USDT tokens based on the input amount of DOGE tokens and performs the swap. The contract’s reserves are updated accordingly.

function swapDOGEForUSDT(uint256 amountIn, address _to) external {
require(amountIn > 0, "INSUFFICIENT_INPUT_AMOUNT");
(uint256 reserveIn, uint256 reserveOut) = getReserves();

require(
reserveIn > 0 && reserveOut > 0,
"UniswapV2Library: INSUFFICIENT_LIQUIDITY"
);
uint256 amountInWithFee = amountIn.mul(997);
uint256 numerator = amountInWithFee.mul(reserveOut);
uint256 denominator = reserveIn.mul(1000).add(amountInWithFee);
uint256 amountOut = numerator / denominator;
require(amountOut <= reserveOut, "UniswapV2: INSUFFICIENT_LIQUIDITY");
DOGE.safeTransferFrom(_to, address(this), amountIn);
USDT.safeTransfer(_to, amountOut);
reserve1 = DOGE.balanceOf(address(this));
reserve2 = USDT.balanceOf(address(this));

}

`swapUSDTForDOGE(uint256 amountIn, address _to)` Function

The `swapUSDTForDOGE()` function enables users to swap USDT tokens for DOGE tokens. Similar to the previous function, it calculates the output amount of DOGE tokens based on the input amount of USDT tokens and performs the swap, updating the contract’s reserves.

function swapUSDTForDOGE(uint256 amountIn, address _to) external {
require(amountIn > 0, "INSUFFICIENT_INPUT_AMOUNT");
(uint256 reserveOut, uint256 reserveIn) = getReserves();
require(
reserveIn > 0 && reserveOut > 0,
"UniswapV2Library: INSUFFICIENT_LIQUIDITY"
);
uint256 amountInWithFee = amountIn.mul(997);
uint256 numerator = amountInWithFee.mul(reserveOut);
uint256 denominator = reserveIn.mul(1000).add(amountInWithFee);
uint256 amountOut = numerator / denominator;
require(amountOut <= reserveOut, "UniswapV2: INSUFFICIENT_LIQUIDITY");
USDT.safeTransferFrom(_to, address(this), amountIn);
DOGE.safeTransfer(_to, amountOut);
reserve1 = DOGE.balanceOf(address(this));
reserve2 = USDT.balanceOf(address(this));

}

Conclusion

The `Liquidity` smart contract encapsulates the functionalities required for liquidity provision and token swapping within the DeFi ecosystem. By exploring each function’s purpose and operation, we gain insights into the contract’s capabilities. It’s important to note that this breakdown provides a high-level understanding, and the actual implementation may involve additional considerations, such as security measures and gas efficiency. The `Liquidity` contract exemplifies the power of smart contracts in revolutionizing financial transactions on the blockchain..

You can view the full code HERE.

Support us HERE
Thank You!

--

--

Rohit Goyal
Coinmonks

I write some words that work for me. Primarily give Knowledge about Blockchain and smart contract coding tips. Also share personal life lessons.