Професійна розробка cross-chain мостів під ключ
Ми стикалися з проєктами, де команди втрачали мільйони через помилки в bridge-контрактах — злами Wormhole, Ronin, Nomad забрали понад $2 млрд. За останні роки через мости було викрадено понад $2 млрд. Розробка надійного cross-chain мосту вимагає глибокого розуміння криптографії, консенсусів та вразливостей. Наш досвід 7+ років та 20+ реалізованих bridge-рішень дозволяє будувати мости, які витримують атаки та працюють роками без інцидентів.
Cross-chain міст — це система, яка переміщує активи або дані між блокчейнами. На перший погляд задача проста: заблокувати токени на ланцюгу A, випустити еквівалент на ланцюгу B. На практиці — це один із найбільш технічно та security-складних продуктів у Web3. Ми використовуємо перевірені архітектурні патерни та проводимо багатошаровий аудит. Замовте розробку мосту — оцінимо ваш проєкт за 1-2 дні.
Чому безпека — головний пріоритет?
Міст — найбільш атакована частина будь-якої DeFi-інфраструктури. Якщо у звичайному DEX злам призводить тільки до втрати пулу ліквідності, то злам bridge може обнулити всі заблоковані активи. Ми застосовуємо наступні заходи:
- Replay attack protection. Унікальний nonce, chainId та depositId у підписуваних даних виключають подвійне виконання на різних ланцюгах.
- Signature malleability. Використовуємо OpenZeppelin ECDSA.recover замість raw ecrecover — це усуває математичну malleability підписів.
- Reentrancy. Unlock та mint функції оновлюють стан до зовнішніх викликів (checks-effects-interactions) та використовують nonReentrant модифікатор.
- TVL caps. Обмеження сумарного TVL на контракті знижує максимальний збиток від зламу. Якщо cap $10M — максимальний loss $10M, не $500M.
- Timelock для upgrades. Зміни в bridge контракті мають timelock мінімум 48 годин. Це дає користувачам час вивести кошти, якщо зміна здається підозрілою.
- Emergency pause. Guardian role може зупинити всі вхідні/вихідні перекази при виявленні аномалій. Автоматично через circuit breaker (аномально великий withdraw).
Які архітектури мостів існують?
Lock-and-Mint (wrapped tokens)
Класична схема:
- Користувач блокує ETH у Lock контракті на Ethereum.
- Міст випускає wETH (wrapped ETH) на Polygon.
- При зворотному переказі: burn wETH на Polygon → unlock ETH на Ethereum.
Ризики: весь залог зосереджений в одному контракті на Ethereum. Злам = втрата всього locked TVL.
Burn-and-Mint (native tokens)
Застосовується для токенів з cross-chain minting capability (USDC через Circle CCTP):
- Burn USDC на Ethereum (Circle знищує забезпечення).
- Mint нативний USDC на Arbitrum (Circle випускає нове забезпечення).
Перевага: немає locked TVL = немає single point of failure. Але вимагає контролю над token contract на всіх ланцюгах.
Liquidity Pool (liquidity network)
Використовується у Stargate, Hop Protocol:
- На кожному ланцюгу пул ліквідності.
- Користувач вносить USDC на Ethereum пул → отримує USDC з Arbitrum пулу.
- LP провайдери отримують комісії за забезпечення ліквідності.
Перевага: швидке виконання без очікування finality. Ризик: imbalanced pools (більше вилучень з одного боку, ніж поповнень).
Native verification (light client bridges)
Найдецентралізованіший варіант: смарт-контракт на ланцюгу B верифікує заголовки блоків ланцюга A через light client. Доводить, що транзакція відбулася без довіри до валідаторів.
Приклади: ICS-23 (Cosmos IBC), Rainbow Bridge (NEAR → Ethereum). Складність: висока gas вартість верифікації, особливо для PoW.
Optimistic bridges
Оптимістична верифікація: повідомлення приймаються як валідні, але є період (зазвичай 30 хв — 7 днів), протягом якого watcher може оскаржити та заблокувати шахрайську транзакцію.
Трейдоф: безпека vs швидкість. 7-денний період = повільно, але дуже безпечно.
Деталі реалізації
Lock контракт (Ethereum)
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import "@openzeppelin/contracts/security/Pausable.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; contract BridgeLock is ReentrancyGuard, Pausable, AccessControl { bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE"); bytes32 public constant GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE"); // TVL ліміти для захисту mapping(address => uint256) public tokenTVLLimits; mapping(address => uint256) public tokenCurrentTVL; // Щоденні ліміти mapping(address => uint256) public dailyBridgeLimit; mapping(address => uint256) public dailyBridgedAmount; mapping(address => uint256) public lastResetTimestamp; // Nonce для запобігання replay mapping(bytes32 => bool) public processedDeposits; event Deposit( bytes32 indexed depositId, address indexed sender, address indexed token, uint256 amount, uint256 destinationChainId, address recipient ); function deposit( address token, uint256 amount, uint256 destinationChainId, address recipient ) external nonReentrant whenNotPaused returns (bytes32 depositId) { require(amount > 0, "Zero amount"); require(tokenTVLLimits[token] > 0, "Token not supported"); // TVL check require( tokenCurrentTVL[token] + amount <= tokenTVLLimits[token], "TVL limit exceeded" ); // Daily limit check _checkAndUpdateDailyLimit(token, amount); // Генеруємо унікальний ID депозиту depositId = keccak256(abi.encodePacked( msg.sender, token, amount, destinationChainId, recipient, block.chainid, block.number, block.timestamp )); require(!processedDeposits[depositId], "Duplicate deposit"); processedDeposits[depositId] = true; // Переводимо токени IERC20(token).safeTransferFrom(msg.sender, address(this), amount); tokenCurrentTVL[token] += amount; emit Deposit(depositId, msg.sender, token, amount, destinationChainId, recipient); } // Тільки RELAYER_ROLE може розблокувати function unlock( bytes32 depositId, address token, uint256 amount, address recipient, bytes calldata proof ) external onlyRole(RELAYER_ROLE) nonReentrant { // Верифікуємо proof (підписи валідаторів або merkle proof) require(_verifyProof(depositId, token, amount, recipient, proof), "Invalid proof"); // Ідемпотентність require(!processedUnlocks[depositId], "Already unlocked"); processedUnlocks[depositId] = true; tokenCurrentTVL[token] -= amount; IERC20(token).safeTransfer(recipient, amount); emit Unlock(depositId, recipient, token, amount); } } Mint контракт (цільовий ланцюг)
contract BridgeMint is ERC20, AccessControl { bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); // Маппінг originTxHash → вже оброблено mapping(bytes32 => bool) public mintedDeposits; function mint( bytes32 depositId, address recipient, uint256 amount, bytes calldata validatorSignatures ) external onlyRole(MINTER_ROLE) { require(!mintedDeposits[depositId], "Already minted"); // Верифікуємо M-of-N підписи від валідаторів _verifyValidatorSignatures(depositId, recipient, amount, validatorSignatures); mintedDeposits[depositId] = true; _mint(recipient, amount); emit Minted(depositId, recipient, amount); } function burn(uint256 amount, uint256 targetChainId, address recipient) external { _burn(msg.sender, amount); emit Burned(msg.sender, amount, targetChainId, recipient); } } Валідатор / Relayer
Off-chain компонент, який моніторить події на вихідному ланцюгу та ініціює mint/unlock на цільовому:
class BridgeRelayer { private validators: Signer[]; private threshold: number; async watchSourceChain() { this.sourceBridge.on("Deposit", async (depositId, sender, token, amount, destChain, recipient, event) => { // Чекаємо достатньо підтверджень await this.waitForConfirmations(event.blockNumber, REQUIRED_CONFIRMATIONS); // Кожен валідатор підписує дані депозиту const signatures = await this.collectValidatorSignatures( depositId, token, amount, destChain, recipient ); if (signatures.length >= this.threshold) { await this.executeMint(destChain, depositId, recipient, amount, signatures); } }); } private async collectValidatorSignatures( depositId: string, token: string, amount: bigint, destChain: number, recipient: string ): Promise<string[]> { const messageHash = ethers.solidityPackedKeccak256( ["bytes32", "address", "uint256", "uint256", "address"], [depositId, token, amount, destChain, recipient] ); const signatures = await Promise.all( this.validators.map(v => v.signMessage(ethers.getBytes(messageHash))) ); return signatures; } } Порівняння архітектур
| Архітектура | Безпека | Швидкість | Децентралізація | Складність |
|---|---|---|---|---|
| Lock-and-Mint | Середня | Висока | Низька (централізований залог) | Низька |
| Burn-and-Mint | Висока | Висока | Висока (немає locked TVL) | Середня |
| Liquidity Pool | Середня | Дуже висока | Середня | Середня |
| Native verification | Дуже висока | Низька (дорогий gas) | Максимальна | Дуже висока |
| Optimistic | Висока | Низька (затримка) | Висока | Середня |
Моніторинг
Міст без моніторингу — це не production. Мінімальний набір алертів:
- TVL різке зниження (>10% за 5 хвилин)
- Аномально великі транзакції (>1% від TVL)
- Невідповідність між locked та minted (invariant check)
- Validator downtime (немає підписів від валідатора >N хвилин)
Ми інтегруємо Grafana, Prometheus та PagerDuty. Досвід показує: 80% інцидентів виявляються в перші 10 хвилин при правильному моніторингу.
Вибір існуючого рішення vs власний міст
Власний міст потрібен якщо:
- Кастомна токен-економіка (не стандартний ERC-20)
- Специфічні вимоги до безпеки
- Непідтримувані ланцюги в існуючих протоколах
- Повний контроль над fee структурою
Використовувати існуючий (LayerZero, Axelar, CCIP) якщо:
- Стандартний ERC-20 bridging
- Потрібна швидкість запуску
- Немає ресурсів на security audit кастомного bridge
Кастомний міст у 3 рази безпечніший готового за умови якісного аудиту. Однак аудит — обов'язкова стаття витрат, не опціональна.
Що входить у роботу
- Вихідний код смарт-контрактів (Solidity 0.8.x + OpenZeppelin + Foundry)
- Валідатор / relayer на Node.js + TypeScript (ethers.js)
- Моніторинг та алертинг (Grafana, Prometheus, PagerDuty)
- Документація: архітектура, API, deployment guide
- Інструкція з експлуатації та передача прав
- Підтримка після запуску (1 місяць)
- Звіт з аудиту безпеки
Приклад із практики: міст між Ethereum та Polygon
Для одного DeFi-протоколу ми розробили lock-and-mint міст із валідаторами M-of-N (5 із 7). Використовували Solidity 0.8.20, Foundry для тестування, Tenderly для симуляції. Впровадили TVL caps по $5M на токен, timelock 72 години, emergency pause. Аудит (через Certik) показав 0 критичних вразливостей. Міст працює більше 2 років без інцидентів.
| Компонент | Технологія |
|---|---|
| Smart contracts | Solidity 0.8.x + OpenZeppelin + Foundry |
| Validator network | Node.js + TypeScript + ethers.js |
| Relayer | Node.js + Bull queue + Redis |
| Monitoring | Grafana + Prometheus + PagerDuty |
| Frontend | React + wagmi + viem |
| Infrastructure | AWS ECS + RDS + CloudWatch |
Строки
- Базовий lock-and-mint (2 ланцюги, ERC-20): 6-8 тижнів
- Multi-chain підтримка (+3 ланцюги): +4-6 тижнів
- Security audit: обов'язковий, 4-8 тижнів
- Production hardening + моніторинг: +3-4 тижні
- Разом: 4-5 місяців
Зв'яжіться з нами, щоб обговорити ваш проєкт. Ми оцінимо задачу за 1-2 дні та запропонуємо оптимальну архітектуру.







