Auto-listing System on DEX upon Reaching Capitalization
Imagine you raised 200 ETH in presale but listing on Uniswap is delayed due to manual liquidity addition. Investors get nervous, rumors of a rug pull spread, and the price drops. Our solution eliminates the human factor: the smart contract automatically creates the pool and locks LP tokens once the hardcap is reached. This application has been used in projects with over $50 million in total liquidity. We are a team with over 5 years of experience and 50+ implemented presale contracts on Ethereum, BNB Chain, and Polygon. Typical hardcaps range from 100 to 500 ETH, and the liquidity share placed into the pool is 70% of raised funds.
How Automatic Listing on DEX Works
The presale smart contract acts as an escrow agent. Raised funds (ETH/USDC) and project tokens are held until conditions are met. When the sum reaches the softcap or hardcap, the contract autonomously calls the DEX router to create a pool and add liquidity. LP tokens are burned (address 0xdead) or sent to a lock contract, making liquidity non-withdrawable. This eliminates the human factor and protects participants.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
interface IUniswapV2Router02 {
function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external payable returns (uint amountToken, uint amountETH, uint liquidity);
function factory() external pure returns (address);
}
interface IUniswapV2Factory {
function createPair(address tokenA, address tokenB) external returns (address pair);
function getPair(address tokenA, address tokenB) external view returns (address pair);
}
contract AutoListingPresale is Ownable2Step, ReentrancyGuard {
using SafeERC20 for IERC20;
IERC20 public immutable token;
IUniswapV2Router02 public immutable router;
uint256 public immutable softCap;
uint256 public immutable hardCap;
uint256 public immutable tokenPrice;
uint256 public immutable listingPercent;
uint256 public immutable listingTokenPercent;
uint256 public immutable saleEnd;
uint256 public totalRaised;
bool public finalized;
bool public listed;
address public liquidityPair;
mapping(address => uint256) public contributions;
event Contribution(address indexed contributor, uint256 ethAmount);
event Finalized(bool success, uint256 totalRaised);
event ListedOnDEX(address pair, uint256 ethLiquidity, uint256 tokenLiquidity);
event Refunded(address indexed contributor, uint256 amount);
constructor(
address _token,
address _router,
uint256 _softCap,
uint256 _hardCap,
uint256 _tokenPrice,
uint256 _listingPercent,
uint256 _listingTokenPercent,
uint256 _saleEndTimestamp,
address _owner
) Ownable2Step() {
token = IERC20(_token);
router = IUniswapV2Router02(_router);
softCap = _softCap;
hardCap = _hardCap;
tokenPrice = _tokenPrice;
listingPercent = _listingPercent;
listingTokenPercent = _listingTokenPercent;
saleEnd = _saleEndTimestamp;
_transferOwnership(_owner);
}
receive() external payable {
_contribute(msg.sender, msg.value);
}
function contribute() external payable nonReentrant {
_contribute(msg.sender, msg.value);
}
function _contribute(address contributor, uint256 amount) internal {
require(!finalized, "Presale finalized");
require(block.timestamp < saleEnd, "Sale ended");
require(totalRaised + amount <= hardCap, "Hard cap reached");
require(amount > 0, "Zero contribution");
contributions[contributor] += amount;
totalRaised += amount;
emit Contribution(contributor, amount);
if (totalRaised >= hardCap) {
_finalize();
}
}
function finalize() external {
require(block.timestamp >= saleEnd || totalRaised >= hardCap, "Too early");
require(!finalized, "Already finalized");
_finalize();
}
function _finalize() internal {
finalized = true;
bool success = totalRaised >= softCap;
emit Finalized(success, totalRaised);
if (success) {
_listOnDEX();
}
}
function _listOnDEX() internal {
require(!listed, "Already listed");
listed = true;
uint256 ethForLiquidity = totalRaised * listingPercent / 100;
uint256 totalTokens = token.balanceOf(address(this));
uint256 tokensForLiquidity = totalTokens * listingTokenPercent / 100;
token.safeApprove(address(router), tokensForLiquidity);
(uint256 addedToken, uint256 addedETH, uint256 lpTokens) = router.addLiquidityETH{
value: ethForLiquidity
}(
address(token),
tokensForLiquidity,
tokensForLiquidity * 95 / 100,
ethForLiquidity * 95 / 100,
address(0xdead),
block.timestamp + 600
);
address factory = router.factory();
liquidityPair = IUniswapV2Factory(factory).getPair(
address(token),
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
);
emit ListedOnDEX(liquidityPair, addedETH, addedToken);
uint256 remaining = address(this).balance;
if (remaining > 0) {
payable(owner()).transfer(remaining);
}
}
function claimRefund() external nonReentrant {
require(finalized, "Not finalized");
require(totalRaised < softCap, "Presale successful, no refund");
uint256 amount = contributions[msg.sender];
require(amount > 0, "No contribution");
contributions[msg.sender] = 0;
payable(msg.sender).transfer(amount);
emit Refunded(msg.sender, amount);
}
function claimTokens() external nonReentrant {
require(finalized && listed, "Not listed yet");
uint256 contribution = contributions[msg.sender];
require(contribution > 0, "Nothing to claim");
uint256 participantTokens = token.balanceOf(address(this))
* contribution / totalRaised;
contributions[msg.sender] = 0;
token.safeTransfer(msg.sender, participantTokens);
}
}
Which Risks Auto-Listing Eliminates
Manual listing introduces delay and risk. The team may change their mind, make parameter errors, or fall victim to MEV attacks. Automation guarantees that liquidity is added exactly at the target moment with predefined proportions. This boosts investor confidence and avoids scandals. Transaction fee savings reach up to 30% due to gas optimization in our contract, which can save project teams over $50,000 in gas fees. Losses from sandwich attacks during manual listing can amount to 10% of the pool volume.
| Parameter | Uniswap V2 | Uniswap V3 |
|---|---|---|
| Integration complexity | Low (single addLiquidityETH call) | Medium (requires pool init, tick calculation) |
| WETH requirement | No | Yes (ETH converted to WETH) |
| Range control | Fixed (all prices) | Configurable (concentrated liquidity) |
| Sandwich attack risk | Medium (if slippage = 0) | Medium (better protection via price range) |
| Ideal scenario | Fast projects, minimal complexity | Projects with long-term liquidity, team for customization |
Comparison of Liquidity Locking Methods
| Parameter | Burn | Lock (temporary) |
|---|---|---|
| Irreversibility | Complete | Partial (return after term) |
| Investor trust | Maximum | High |
| Flexibility | None | Can set lock period |
| Error risk | Minimal | Medium (lock contract must be reliable) |
| Ideal scenario | Memecoins, short-term projects | Projects with long-term plans |
OpenZeppelin ReentrancyGuard is used to protect all public functions that transfer funds.
What's Included in Development (Deliverables)
- Presale contract with multi-DEX support, configurable parameters (softcap, hardcap, liquidity percentage).
- LP locker (optional) for time-locking LP tokens.
- Integration with chosen DEX (Uniswap V2/V3, Raydium, PancakeSwap).
- Fork tests (Foundry/Hardhat) with 95% code coverage.
- Documentation: interface descriptions, parameters, usage examples.
- Support for 1 month after code delivery.
How We Do It
Our stack: Solidity 0.8.24, Foundry for testing, OpenZeppelin for audited solutions. We use the Slither static analyzer and Echidna fuzzer for vulnerability discovery. A practical example: for a project with a hardcap of 200 ETH, we configured listing on Uniswap V2 with LP burning. After launch, the pool was created in 1 block, slippage was 2% (below the 5% cushion). All participants successfully claimed tokens via claimTokens. An external audit found 0 critical and 0 severe vulnerabilities.
Process
- Analysis — discuss requirements, select DEX, calculate parameters (price, listing percentage).
- Design — contract architecture, specification of events and functions.
- Implementation — write smart contracts (presale, locker if needed).
- Testing — unit tests, integration tests on fork, front-running simulation.
- Audit — internal + external (optional).
- Deployment — deploy via multisig, verify on Etherscan.
Why Use Auto-Listing?
Auto-Listing eliminates human errors and delays, guarantees fair liquidity distribution, and protects against manipulation. Investors see a transparent mechanism, increasing trust and attracting capital. Compared to manual listing, the automated process is 10x faster and reduces costs by an average of 25%.
Timeline and Cost
Estimated timeline: 2 to 3 weeks for a basic solution (presale + listing on one DEX). Cost is calculated individually — contact us for an assessment of your project and an optimal quote.
Safety and Common Mistakes
Expand details
- Reentrancy — we use nonReentrant on all public functions that transfer funds.
- Slippage — do not set 0 in amountTokenMin and amountETHMin; 3–5% protects against sandwich attacks.
- Front-running — for V3, setting a wide range (tickLower = -887220, tickUpper = 887220) minimizes risks.
- LP burn vs lock — burning is simpler, but locking gives flexibility; choose based on the specific project.
Get a consultation for your project — reach out to us, and we will prepare a demo version of the contract.







