מערכת רישום אוטומטי ב-DEX בעת הגעה להון יעד
תארו לעצמכם שגייסתם 200 ETH במכירה מוקדמת, אך הרישום ב-Uniswap מתעכב עקב הוספת נזילות ידנית. המשקיעים נהיים עצבניים, שמועות על משיכת שטיח מתפשטות, והמחיר יורד. הפתרון שלנו מבטל את הגורם האנושי: החוזה החכם יוצר אוטומטית את מאגר הנזילות ונועל את אסימוני ה-LP ברגע שמגיעים להון היעד. יישום זה שימש בפרויקטים עם נזילות כוללת של למעלה מ-50 מיליון דולר. אנו צוות עם ניסיון של למעלה מ-5 שנים ויותר מ-50 חוזי מכירה מוקדמת שיושמו על Ethereum, BNB Chain ו-Polygon. הון היעד הטיפוסי נע בין 100 ל-500 ETH, וחלק הנזילות המוכנס למאגר הוא 70% מהכספים שגויסו.
כיצד פועל רישום אוטומטי ב-DEX
חוזה המכירה המוקדמת פועל כנאמן. הכספים שגויסו (ETH/USDC) ואסימוני הפרויקט מוחזקים עד להתקיימות התנאים. כאשר הסכום מגיע להון הרך או להון היעד, החוזה קורא אוטומטית לנתב ה-DEX כדי ליצור מאגר ולהוסיף נזילות. אסימוני ה-LP נשרפים (כתובת 0xdead) או נשלחים לחוזה נעילה, מה שהופך את הנזילות לבלתי ניתנת למשיכה. זה מבטל את הגורם האנושי ומגן על המשתתפים.
// 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);
}
} אילו סיכונים הרישום האוטומטי מבטל
רישום ידני יוצר עיכוב וסיכון. הצוות עלול לשנות את דעתו, לעשות שגיאות בפרמטרים, או ליפול קורבן להתקפות MEV. האוטומציה מבטיחה שנזילות מתווספת בדיוק ברגע המטרה עם פרופורציות מוגדרות מראש. זה מגביר את אמון המשקיעים ומונע שערוריות. החיסכון בעמלות עסקה מגיע עד 30% הודות לאופטימיזציית גז בחוזה שלנו, מה שיכול לחסוך לצוותי פרויקטים למעלה מ-50,000 דולר בעמלות גז. הפסדים מהתקפות סנדוויץ' במהלך רישום ידני יכולים להגיע ל-10% מנפח המאגר.
| פרמטר | Uniswap V2 | Uniswap V3 |
|---|---|---|
| מורכבות אינטגרציה | נמוכה (קריאת addLiquidityETH אחת) | בינונית (דורש אתחול מאגר, חישוב tick) |
| דרישת WETH | לא | כן (ETH מומר ל-WETH) |
| בקרת טווח | קבוע (כל המחירים) | ניתן להגדרה (נזילות מרוכזת) |
| סיכון להתקפת סנדוויץ' | בינוני (אם החלקה = 0) | בינוני (הגנה טובה יותר דרך טווח מחירים) |
| תרחיש אידיאלי | פרויקטים מהירים, מורכבות מינימלית | פרויקטים עם נזילות ארוכת טווח, צוות להתאמה אישית |
השוואת שיטות נעילת נזילות
| פרמטר | שריפה | נעילה (זמנית) |
|---|---|---|
| בלתי הפיכות | מוחלטת | חלקית (החזרה לאחר תקופה) |
| אמון משקיעים | מקסימלי | גבוה |
| גמישות | אין | ניתן להגדיר תקופת נעילה |
| סיכון לשגיאות | מינימלי | בינוני (חוזה הנעילה חייב להיות אמין) |
| תרחיש אידיאלי | מטבעות מם, פרויקטים קצרי טווח | פרויקטים עם תוכניות ארוכות טווח |
OpenZeppelin ReentrancyGuard משמש להגנה על כל הפונקציות הציבוריות שמעבירות כספים.
מה כלול בפיתוח (תוצרים)
- חוזה מכירה מוקדמת עם תמיכה במספר DEX, פרמטרים הניתנים להגדרה (הון רך, הון יעד, אחוז נזילות).
- נועל LP (אופציונלי) לנעילת אסימוני LP לזמן מוגדר.
- אינטגרציה עם ה-DEX הנבחר (Uniswap V2/V3, Raydium, PancakeSwap).
- בדיקות Fork (Foundry/Hardhat) עם כיסוי קוד של 95%.
- תיעוד: תיאורי ממשק, פרמטרים, דוגמאות שימוש.
- תמיכה לחודש אחד לאחר מסירת הקוד.
איך אנחנו עושים זאת
המחסנית שלנו: Solidity 0.8.24, Foundry לבדיקות, OpenZeppelin לפתרונות מבוקרים. אנו משתמשים במנתח הסטטי Slither ובפאזר Echidna לאיתור פגיעויות. דוגמה מעשית: עבור פרויקט עם הון יעד של 200 ETH, הגדרנו רישום ב-Uniswap V2 עם שריפת LP. לאחר ההשקה, המאגר נוצר בבלוק אחד, ההחלקה הייתה 2% (מתחת לכרית הביטחון של 5%). כל המשתתפים תבעו בהצלחה את האסימונים שלהם דרך // 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); } } . ביקורת חיצונית מצאה 0 פגיעויות קריטיות ו-0 פגיעויות חמורות.
תהליך
- ניתוח — דיון בדרישות, בחירת DEX, חישוב פרמטרים (מחיר, אחוז רישום).
- עיצוב — ארכיטקטורת חוזה, מפרט אירועים ופונקציות.
- יישום — כתיבת חוזים חכמים (מכירה מוקדמת, נועל אם נדרש).
- בדיקות — בדיקות יחידה, בדיקות אינטגרציה על fork, סימולציית front-running.
- ביקורת — פנימית + חיצונית (אופציונלי).
- פריסה — פריסה דרך multisig, אימות ב-Etherscan.
למה להשתמש ברישום אוטומטי?
רישום אוטומטי מבטל שגיאות אנוש ועיכובים, מבטיח חלוקת נזילות הוגנת, ומגן מפני מניפולציות. משקיעים רואים מנגנון שקוף, מה שמגביר אמון ומושך הון. בהשוואה לרישום ידני, התהליך האוטומטי מהיר פי 10 ומפחית עלויות בממוצע של 25%.
ציר זמן ועלות
ציר זמן משוער: 2 עד 3 שבועות לפתרון בסיסי (מכירה מוקדמת + רישום ב-DEX אחד). העלות מחושבת באופן אישי — צרו קשר להערכת הפרויקט שלכם והצעת מחיר אופטימלית.
בטיחות וטעויות נפוצות
הרחב פרטים
- Reentrancy — אנו משתמשים ב-nonReentrant על כל הפונקציות הציבוריות שמעבירות כספים.
- החלקה — אל תגדירו 0 ב-amountTokenMin ו-amountETHMin; 3–5% מגן מפני התקפות סנדוויץ'.
- Front-running — עבור V3, הגדרת טווח רחב (tickLower = -887220, tickUpper = 887220) ממזערת סיכונים.
- שריפת LP לעומת נעילה — שריפה פשוטה יותר, אך נעילה נותנת גמישות; בחרו בהתאם לפרויקט הספציפי.
קבלו ייעוץ לפרויקט שלכם — פנו אלינו, ונכין גרסת הדגמה של החוזה.







