Розробка кросс-чейн мосту
Кросс-чейн міст — це система, яка дозволяє переміщувати активи або дані між незалежними блокчейн-мережами. На перший погляд завдання просте: заблокувати токени на чейні A, випустити еквівалент на чейні B. На практиці — це один з найбільш технічно та з точки зору безпеки складних продуктів в Web3. Хаки мостів складають велику частину всіх втрат у крипто ($2B+ втрачено на Ronin, Wormhole, Nomad, Harmony).
Архітектурні паттерни
Lock-and-Mint (wrapped токени)
Класична схема:
- Користувач блокує ETH в Lock контракті на Ethereum
- Міст випускає wETH (wrapped ETH) на Polygon
- При зворотному переводі: спалити wETH на Polygon → розблокувати ETH на Ethereum
Ризики: весь залог зосереджений в одному контракті на Ethereum. Взлом = втрата всього заблокованого TVL. Wormhole втратив $320M саме так.
Burn-and-Mint (нативні токени)
Застосовується для токенів з можливістю кросс-чейн монетизації (USDC через Circle CCTP, USDT через Tether міст):
- Спалити USDC на Ethereum (Circle знищує забезпечення)
- Выпустити нативний USDC на Arbitrum (Circle видає нове забезпечення)
Перевага: немає заблокованого TVL = немає єдиної точки відмови. Але вимагає контролю над контрактом токена на всіх чейнах.
Liquidity Pool (liquidity network)
Використовується в Stargate, Hop Protocol:
- Пул ліквідності на кожному чейні
- Користувач вносить USDC на пул Ethereum → отримує USDC з пула Arbitrum
- LP-провайдери отримують комісії за надання ліквідності
Перевага: швидке виконання без очікування фіналізації. Ризик: дисбаланс пулів (більше виводів з однієї сторони, ніж внесків).
Native verification (light client мости)
Найбільш децентралізований варіант: смарт-контракт на чейні B верифікує заголовки блоків чейна A через light client. Доводить, що транзакція сталася без полаганння на валідаторів.
Приклади: ICS-23 (Cosmos IBC), Rainbow Bridge (NEAR → Ethereum). Складність: висока вартість газу для верифікації, особливо для PoW (Ethereum до merge).
Optimistic мости
Оптимістична верифікація: повідомлення приймаються як дійсні, але існує період (зазвичай 30 хв — 7 днів), протягом якого спостерігач може оспорити та заблокувати шахрайські транзакції. Використовується в Across Protocol, Connext (частково).
Компроміс: безпека 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
require(
tokenCurrentTVL[token] + amount <= tokenTVLLimits[token],
"TVL limit exceeded"
);
// Перевірка щоденного ліміту
_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;
}
}
Безпека — критичні моменти
Захист від replay атак. Одна і та ж підписана транзакція не повинна виконуватися двічі або на іншому чейні. Рішення: включати chainId і унікальний nonce в підписані дані.
Signature malleability. ECDSA підписи математично malleable — з однієї можна отримати іншу дійсну. OpenZeppelin ECDSA.recover вирішує це, але важливо його використовувати, а не raw ecrecover.
Reentrancy. Функції unlock/mint мають оновлювати стан ДО зовнішніх викликів (transfer). Паттерн checks-effects-interactions або модифікатор nonReentrant.
TVL caps. Обмеження загального TVL контракту знижує максимальний шкоду від хаку. Якщо ліміт $10M — максимальна втрата $10M, не $500M.
Timelock для оновлень. Зміни в bridge контракті повинні мати timelock (мінімум 48 годин). Це дає користувачам час вивести кошти, якщо зміна здається підозрілою.
Emergency pause. Guardian role може зупинити всі вхідні/вихідні передачі при виявленні аномалій. Автоматично через circuit breaker (аномально великий withdraw).
Моніторинг
Міст без моніторингу не є production. Мінімальний набір алертів:
- Раптове зниження TVL (>10% за 5 хвилин)
- Аномально великі транзакції (>1% від TVL)
- Невідповідність між заблокованими та выпущеними (проверка інваріанта)
- Downtime валідаторів (немає підписів від валідатора >N хвилин)
Вибір існуючого рішення vs власний міст
Будувати власний міст потрібно, якщо:
- Кастомна токен-економіка (не стандартний ERC-20)
- Специфічні вимоги безпеки
- Непідтримувані чейни в існуючих протоколах
- Повний контроль над структурою комісій
Використовувати існуючий (LayerZero, Axelar, CCIP), якщо:
- Стандартний ERC-20 bridging
- Потрібна швидкість запуску
- Немає ресурсів на аудит безпеки кастомного мосту
Вартість аудиту кастомного мосту — $50-200k. Це обов'язкова стаття, не опціональна.
Stack
| Компонент | Технологія |
|---|---|
| Смарт-контракти | Solidity 0.8.x + OpenZeppelin + Foundry |
| Validator network | Node.js + TypeScript + ethers.js |
| Relayer | Node.js + Bull queue + Redis |
| Моніторинг | Grafana + Prometheus + PagerDuty |
| Frontend | React + wagmi + viem |
| Інфраструктура | AWS ECS + RDS + CloudWatch |
Таймлайни
- Базовий lock-and-mint (2 чейни, ERC-20): 6-8 тижнів
- Мультичейн підтримка (+3 чейни): +4-6 тижнів
- Аудит безпеки: обов'язковий, 4-8 тижнів
- Production hardening + моніторинг: +3-4 тижні
- Всього: 4-5 місяців







