Разработка системы защиты от rug pull

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1Все 1306 услуг
Разработка системы защиты от rug pull
Сложный
~3-5 дней
Часто задаваемые вопросы

Направления блокчейн-разработки

Этапы блокчейн-разработки

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1306
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1218
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    920
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1147
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    610
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    885

Разработка системы защиты от rug pull

Rug pull — это когда команда проекта намеренно выводит ликвидность и/или ETH/токены из контракта, оставляя держателей с обесцененными токенами. Это не технический баг — это умысел. Но техника, которая делает rug pull возможным, вполне конкретна и предсказуема. Система защиты строится на двух уровнях: on-chain механики, которые физически не дают команде вывести ликвидность, и off-chain мониторинг, который алертирует о подозрительных действиях до момента исполнения.

Классификация rug pull векторов

Liquidity removal

Самый распространённый вектор. Команда добавляет ликвидность в Uniswap/PancakeSwap, запускает маркетинг, цена растёт → команда выводит ликвидность. LP токены у команды, ограничений нет.

Mint без ограничений

Команда деплоит токен с функцией mint(address, amount) доступной owner-у. В любой момент можно выминтить миллиарды токенов и продать их на рынке.

// ОПАСНЫЙ ПАТТЕРН
contract HoneypotToken is ERC20, Ownable {
    function mint(address to, uint256 amount) external onlyOwner {
        _mint(to, amount);  // Неограниченный mint для owner
    }
}

Hidden transfer restrictions

Контракт позволяет только определённым адресам продавать токен (sell restriction), блокируя обычных холдеров от продажи. Owner может добавлять/удалять адреса из blacklist или whitelist.

// ОПАСНЫЙ ПАТТЕРН — sell restriction
mapping(address => bool) private _blacklisted;

function _beforeTokenTransfer(address from, address to, uint256 amount) internal override {
    if (from != owner() && to != uniswapPair) {
        require(!_blacklisted[from], "Transfer restricted");
    }
    super._beforeTokenTransfer(from, to, amount);
}

Proxy upgrade backdoor

Контракт за UUPS или Transparent proxy с owner-ом способным upgrade без governance. После TGE и накопления ликвидности — owner делает upgrade который добавляет drain функцию.

Fee manipulation

Стандартный deflationary токен с fee. Команда может изменить fee до 99% — любая продажа отдаёт почти все средства команде.

uint256 public sellFee = 3;  // 3% — кажется разумно

// Позже:
function setSellFee(uint256 fee) external onlyOwner {
    sellFee = fee;  // Может стать 99%
}

On-chain защитные механики

Locked liquidity через Unicrypt / PinkLock

Простейшая защита: LP токены локируются в smart contract с timelock. Команда физически не может вывести ликвидность до истечения срока.

// Интерфейс Unicrypt Liquidity Locker
interface IUnicryptLocker {
    function lockLPToken(
        address lpToken,
        uint256 amount,
        uint256 unlockDate,   // UNIX timestamp
        address payable referral,
        bool feeInEth,
        address payable withdrawer
    ) external payable;
    
    function withdraw(
        address lpToken,
        uint256 lockIndex,
        uint256 lockId,
        uint256 amount
    ) external;
}

// При запуске: локируем LP на минимум 1 год
function lockInitialLiquidity(address lpToken, uint256 amount) external onlyOwner {
    IERC20(lpToken).approve(address(unicryptLocker), amount);
    
    unicryptLocker.lockLPToken{value: lockFee}(
        lpToken,
        amount,
        block.timestamp + 365 days,  // 1 год lock
        payable(address(0)),          // no referral
        true,                          // fee in ETH
        payable(msg.sender)
    );
    
    emit LiquidityLocked(lpToken, amount, block.timestamp + 365 days);
}

Проверка для пользователей: на Unicrypt или Team.Finance можно проверить lock по адресу токена или LP токена.

Renounced ownership и Ownable2Step

Если команда renounce ownership — никто не может вызывать onlyOwner функции. Это максимальная защита, но делает контракт immutable полностью, включая полезные функции.

Компромисс — ограниченный owner через Ownable2Step с ограниченными полномочиями:

contract ProtectedToken is ERC20, Ownable2Step {
    // Owner может ТОЛЬКО менять marketing wallet и fee параметры
    // в заранее определённых пределах
    
    uint256 public constant MAX_SELL_FEE = 500;  // максимум 5% — hardcoded, неизменно
    uint256 public sellFee = 300;  // текущие 3%
    
    function setSellFee(uint256 newFee) external onlyOwner {
        require(newFee <= MAX_SELL_FEE, "Fee exceeds maximum");
        sellFee = newFee;
        emit SellFeeUpdated(newFee);
    }
    
    // Нет mint функции
    // Нет blacklist/whitelist
    // Нет функции изменения transfer restrictions
}

Mint cap и fixed supply

Ограничение максимального supply hardcoded в контракте — не через переменную, а через константу:

uint256 public constant MAX_SUPPLY = 1_000_000_000 * 1e18;  // 1 billion, immutable

function mint(address to, uint256 amount) external onlyMinter {
    require(totalSupply() + amount <= MAX_SUPPLY, "Exceeds max supply");
    _mint(to, amount);
}

// После TGE — minter role отзывается
function renounceMinterRole() external onlyOwner {
    minter = address(0);
    emit MinterRevoked();
}

Timelock для critical functions

Любые изменения критических параметров должны проходить через timelock — задержку с публичным объявлением.

contract TimelockProtectedToken is ERC20 {
    uint256 public constant TIMELOCK_DURATION = 48 hours;
    
    struct PendingChange {
        bytes32 changeType;
        uint256 newValue;
        uint256 executableAt;
        bool executed;
    }
    
    mapping(bytes32 => PendingChange) public pendingChanges;
    
    function proposeFeeChange(uint256 newFee) external onlyOwner {
        require(newFee <= 500, "Too high");
        
        bytes32 changeId = keccak256(abi.encodePacked("fee", newFee, block.timestamp));
        pendingChanges[changeId] = PendingChange({
            changeType: "fee",
            newValue: newFee,
            executableAt: block.timestamp + TIMELOCK_DURATION,
            executed: false
        });
        
        emit FeeChangeProposed(changeId, newFee, block.timestamp + TIMELOCK_DURATION);
    }
    
    function executeFeeChange(bytes32 changeId) external onlyOwner {
        PendingChange storage change = pendingChanges[changeId];
        require(!change.executed, "Already executed");
        require(block.timestamp >= change.executableAt, "Timelock not passed");
        require(change.changeType == "fee", "Wrong type");
        
        change.executed = true;
        sellFee = change.newValue;
        
        emit FeeChanged(change.newValue);
    }
}

Timelock даёт сообществу 48 часов чтобы увидеть изменение и выйти до его применения.

Off-chain мониторинг: ранняя детекция

Анализ контракта перед покупкой

Автоматический scanner контракта до взаимодействия с токеном:

interface ContractRiskAssessment {
  address: string;
  risks: RiskItem[];
  score: number;  // 0 (safe) to 100 (high risk)
}

interface RiskItem {
  severity: 'critical' | 'high' | 'medium' | 'low';
  type: string;
  description: string;
}

async function analyzeToken(tokenAddress: string): Promise<ContractRiskAssessment> {
  const risks: RiskItem[] = [];
  
  // 1. Проверяем наличие mint функции
  const hasMintFunction = await checkFunctionExists(tokenAddress, 'mint(address,uint256)');
  if (hasMintFunction) {
    const mintRestricted = await checkMintHasSupplyCap(tokenAddress);
    if (!mintRestricted) {
      risks.push({
        severity: 'critical',
        type: 'UNLIMITED_MINT',
        description: 'Owner can mint unlimited tokens'
      });
    }
  }
  
  // 2. Проверяем owner
  const owner = await getContractOwner(tokenAddress);
  const ownerRenounced = owner === ZERO_ADDRESS;
  if (!ownerRenounced) {
    // Проверяем что может делать owner
    const ownerCapabilities = await getOwnerCapabilities(tokenAddress);
    if (ownerCapabilities.canChangeTransferRestrictions) {
      risks.push({
        severity: 'high',
        type: 'TRANSFER_RESTRICTION',
        description: 'Owner can restrict token transfers (potential honeypot)'
      });
    }
  }
  
  // 3. LP lock status
  const lpToken = await getUniswapLPToken(tokenAddress);
  if (lpToken) {
    const lockStatus = await checkLiquidityLock(lpToken);
    if (!lockStatus.isLocked) {
      risks.push({
        severity: 'high',
        type: 'UNLOCKED_LIQUIDITY',
        description: 'Liquidity is not locked'
      });
    } else if (lockStatus.unlockTimestamp < Date.now() / 1000 + 30 * 24 * 3600) {
      risks.push({
        severity: 'medium',
        type: 'SHORT_LOCK',
        description: `Liquidity unlocks in ${lockStatus.daysRemaining} days`
      });
    }
  }
  
  // 4. Proxy check
  const isProxy = await isProxyContract(tokenAddress);
  if (isProxy) {
    const proxyAdmin = await getProxyAdmin(tokenAddress);
    const proxyAdminIsTimelocked = await isTimelocked(proxyAdmin);
    if (!proxyAdminIsTimelocked) {
      risks.push({
        severity: 'high',
        type: 'UPGRADEABLE_NO_TIMELOCK',
        description: 'Upgradeable contract without timelock on admin'
      });
    }
  }
  
  const score = calculateRiskScore(risks);
  return { address: tokenAddress, risks, score };
}

Real-time мониторинг транзакций

После покупки токена — продолжаем мониторинг:

// Следим за critical событиями
async function monitorToken(tokenAddress: string, alertCallback: (alert: Alert) => void) {
  const client = createPublicClient({ transport: webSocket(WS_RPC_URL) });
  
  // 1. Мониторинг OwnershipTransferred — renounce или смена owner
  client.watchContractEvent({
    address: tokenAddress,
    abi: OWNABLE_ABI,
    eventName: 'OwnershipTransferred',
    onLogs: (logs) => {
      const newOwner = logs[0].args.newOwner;
      if (newOwner === ZERO_ADDRESS) {
        alertCallback({ type: 'OWNERSHIP_RENOUNCED', severity: 'info', tokenAddress });
      } else {
        alertCallback({ type: 'OWNERSHIP_TRANSFERRED', severity: 'high', tokenAddress, data: { newOwner } });
      }
    }
  });
  
  // 2. Мониторинг крупных transfer от deployer/team адресов
  const deployer = await getContractDeployer(tokenAddress);
  client.watchContractEvent({
    address: tokenAddress,
    abi: ERC20_ABI,
    eventName: 'Transfer',
    args: { from: deployer },
    onLogs: async (logs) => {
      for (const log of logs) {
        const balance = await getBalance(tokenAddress, deployer);
        const totalSupply = await getTotalSupply(tokenAddress);
        const pct = (balance * 100n) / totalSupply;
        
        if (pct < 5n) {  // deployer продал >95% своих токенов
          alertCallback({ type: 'DEPLOYER_DUMP', severity: 'critical', tokenAddress });
        }
      }
    }
  });
  
  // 3. Мониторинг LP removals
  const lpToken = await getUniswapLPToken(tokenAddress);
  if (lpToken) {
    client.watchContractEvent({
      address: UNISWAP_V2_ROUTER,
      abi: UNISWAP_V2_ROUTER_ABI,
      eventName: 'RemoveLiquidity',
      onLogs: async (logs) => {
        // Проверяем что это наш токен
        for (const log of logs) {
          if (log.args.tokenA === tokenAddress || log.args.tokenB === tokenAddress) {
            alertCallback({ type: 'LIQUIDITY_REMOVED', severity: 'critical', tokenAddress, data: log.args });
          }
        }
      }
    });
  }
}

DEXTools / Token Sniffer интеграция

Вместо написания всего с нуля — интеграция с существующими API:

Сервис API Что проверяет
Token Sniffer /api/v2/tokens/{chain}/{address} Honeypot, mint, blacklist, proxies
GoPlus Security api.gopluslabs.io/api/v1/token_security/{chainId} 30+ security checks
DEXTools API (платный) Locks, holder distribution, trading patterns
Rugcheck.xyz rugcheck.xyz/api/tokens/{address}/report Solana и EVM токены
async function getGoPlus SecurityReport(chainId: number, tokenAddress: string) {
  const response = await fetch(
    `https://api.gopluslabs.io/api/v1/token_security/${chainId}?contract_addresses=${tokenAddress}`
  );
  const data = await response.json();
  const report = data.result[tokenAddress.toLowerCase()];
  
  return {
    isHoneypot: report.is_honeypot === '1',
    canTakeBackOwnership: report.can_take_back_ownership === '1',
    ownerCanChangeBalance: report.owner_change_balance === '1',
    hasMintFunction: report.is_mintable === '1',
    isProxy: report.is_proxy === '1',
    sellTax: parseFloat(report.sell_tax || '0'),
    buyTax: parseFloat(report.buy_tax || '0'),
    liquidityLocked: report.lp_locked === '1',
    holders: report.holder_count,
    top10HolderPercent: parseFloat(report.token_name || '0')
  };
}

Honeypot detection: симуляция продажи

Лучший способ проверить honeypot — симулировать sell транзакцию через fork:

async function simulateSell(
  tokenAddress: string,
  amount: bigint,
  holderAddress: string
): Promise<{ canSell: boolean; receivedAmount: bigint; errorReason?: string }> {
  
  // Форкаем mainnet локально
  const anvil = await startAnvil({ forkUrl: MAINNET_RPC, forkBlockNumber: 'latest' });
  
  try {
    // Имперсонируем holder адрес
    await anvil.impersonateAccount(holderAddress);
    
    const router = getContract({ address: UNISWAP_V2_ROUTER, abi: ROUTER_ABI });
    const token = getContract({ address: tokenAddress, abi: ERC20_ABI });
    
    // Approve
    await token.write.approve([UNISWAP_V2_ROUTER, amount], { account: holderAddress });
    
    // Simulate sell
    const ethBalanceBefore = await anvil.getBalance(holderAddress);
    
    await router.write.swapExactTokensForETHSupportingFeeOnTransferTokens(
      [amount, 0n, [tokenAddress, WETH], holderAddress, BigInt(Date.now()) + 1000n],
      { account: holderAddress }
    );
    
    const ethBalanceAfter = await anvil.getBalance(holderAddress);
    const receivedETH = ethBalanceAfter - ethBalanceBefore;
    
    return { canSell: true, receivedAmount: receivedETH };
    
  } catch (error) {
    return { canSell: false, receivedAmount: 0n, errorReason: error.message };
  } finally {
    await anvil.close();
  }
}

Эта симуляция ловит honeypot который технически позволяет транзакцию пройти, но transfer fee = 99%.

Интеграция в продукт

Система защиты от rug pull может быть реализована как:

  1. Browser extension — предупреждение при попытке approve или swap для risky токена
  2. DEX frontend middleware — проверка перед подтверждением swap транзакции
  3. Portfolio tracker — мониторинг уже купленных токенов
  4. Telegram/Discord bot — алерты для токенов из watchlist

Ни один автоматический инструмент не даёт 100% защиты — опытный мошенник может создать контракт, который проходит все автоматические проверки. Но поднятие планки усилий делает атаки менее выгодными и отсекает большинство opportunistic rug pulls.

Компоненты системы и сроки

Компонент Описание Срок
Contract analyzer Статический анализ bytecode + ABI 3–4 нед
Honeypot simulator Anvil fork + симуляция продажи 2–3 нед
Real-time monitor WebSocket event listener + алерты 2–3 нед
LP lock checker Интеграция с Unicrypt, PinkLock, Team.Finance 1–2 нед
3rd party integration GoPlus, Token Sniffer API 1 нед
Frontend/bot UI или Telegram бот для алертов 2–4 нед
База данных История проверок, кеширование 1–2 нед