Розробка wrapped-токена
Wrapped-токен — це токен, який представляє інший актив у співвідношенні 1:1. WETH — це ETH, завернутий у ERC-20 інтерфейс. WBTC — це Bitcoin на Ethereum, забезпечений реальним BTC у кастодіана. wstETH — це stETH у неребалансирующійся формі. За простотою ідеї крияться кілька принципово різних архітектурних моделей з дуже різними гарантіями безпеки.
Три моделі wrapped-токенів
Custodial wrap (WBTC-модель). Оригінальний актив зберігається у централізованого кастодіана (BitGo для WBTC). Коли користувач вносить BTC — аккредитований minter створює WBTC on-chain. При redemption — кастодіан видає BTC. Контракт простий, ризик — довіра до кастодіана. BitGo тримає ~150K BTC ($9B+). Єдина точка відмови.
Smart contract wrap (WETH-модель). Смарт-контракт є кастодіаном. Користувач відправляє ETH → отримує WETH 1:1. При unwrap — повертає WETH → отримує ETH. Немає довіри до третьої сторони, повна прозорість резервів on-chain. Працює тільки якщо обидва активи в одному блокчейні.
Cross-chain wrap з мостом. Актив блокується на одній цепи, wrapped версія мінтиться на іншій. Найбільш складно та ризикованно. Мосты — крупнейший источник взломів у DeFi (Ronin $625M, Wormhole $320M, Nomad $190M).
WETH контракт: деталі реалізації
WETH9 — один з найбільш копійованих контрактів у Ethereum. Оригінал у production з 2017 року та містить ~50 рядків коду. Але нюанси існують:
contract WETH9 {
string public name = "Wrapped Ether";
string public symbol = "WETH";
uint8 public decimals = 18;
mapping (address => uint) public balanceOf;
mapping (address => mapping (address => uint)) public allowance;
event Approval(address indexed src, address indexed guy, uint wad);
event Transfer(address indexed src, address indexed dst, uint wad);
event Deposit(address indexed dst, uint wad);
event Withdrawal(address indexed src, uint wad);
// Обертка через fallback — депозит при відправці ETH напрямку
receive() external payable {
deposit();
}
function deposit() public payable {
balanceOf[msg.sender] += msg.value;
emit Deposit(msg.sender, msg.value);
}
function withdraw(uint wad) public {
require(balanceOf[msg.sender] >= wad);
balanceOf[msg.sender] -= wad;
payable(msg.sender).transfer(wad);
emit Withdrawal(msg.sender, wad);
}
function totalSupply() public view returns (uint) {
return address(this).balance;
}
// ... ERC20 transfer/approve/transferFrom
}
Інваріант контракту: address(this).balance == totalSupply() завжди. Це робить WETH надійним: резерви верифіковані on-chain у реальному часі.
Важливе відхилення від звичайного ERC-20: totalSupply() вичислюється як address(this).balance, не зберігається окремо. Гарантує синхронність, але означає, що approve + transferFrom для ETH неможливе без WETH (звідси його необхідність для DeFi протоколів).
Cross-chain wrapped токен з lock-and-mint
Для токена, який повинен існувати в кількох сітях (наприклад, ваш ERC-20 токен на Ethereum та еквівалент на BSC):
Lock контракт на source chain (Ethereum):
contract TokenBridge {
IERC20 public immutable token;
address public immutable relayer; // trusted або децентралізований
mapping(bytes32 => bool) public processedMessages;
event TokensLocked(
address indexed sender,
uint256 amount,
uint256 destinationChainId,
address destinationAddress,
bytes32 messageId
);
function lock(
uint256 amount,
uint256 destinationChainId,
address destinationAddress
) external {
require(amount > 0, "Zero amount");
token.safeTransferFrom(msg.sender, address(this), amount);
bytes32 messageId = keccak256(
abi.encodePacked(msg.sender, amount, destinationChainId, destinationAddress, block.timestamp)
);
emit TokensLocked(msg.sender, amount, destinationChainId, destinationAddress, messageId);
}
// Релеер викликає unlock при burn на destination chain
function unlock(
address recipient,
uint256 amount,
bytes32 messageId,
bytes calldata relayerSignature
) external {
require(!processedMessages[messageId], "Already processed");
require(verifyRelayerSignature(recipient, amount, messageId, relayerSignature), "Invalid signature");
processedMessages[messageId] = true;
token.safeTransfer(recipient, amount);
}
}
Wrapped контракт на destination chain (BSC):
contract WrappedToken is ERC20, Ownable {
address public immutable bridge;
constructor(string memory name, string memory symbol, address _bridge)
ERC20(name, symbol) Ownable(msg.sender)
{
bridge = _bridge;
}
function mint(address to, uint256 amount) external {
require(msg.sender == bridge, "Only bridge");
_mint(to, amount);
}
function burn(address from, uint256 amount) external {
require(msg.sender == bridge, "Only bridge");
_burn(from, amount);
}
}
Relayer: централізований vs децентралізований
Найбільш критична частина cross-chain мосту — хто та як верифікує события на іншій цепи.
Централізований relayer — ваш сервер слухає события на source chain та викликає функції на destination chain. Просто в розробці, швидко, але централізована точка відмови. Якщо сервер скомпрометований — атакуючий може створювати безмежні mint без реального lock.
Multisig relayers — N незалежних операторів повинні підписати кожне повідомлення, контракт перевіряє threshold підписів. Використовується Multichain (до взлому), deBridge. Безпечніше, але складніше в оркестрації.
Decentralized messaging (LayerZero, Chainlink CCIP, Wormhole) — використовуйте існуючу верифіковану інфраструктуру замість власних relayers. LayerZero: Ultra Light Node верифікує block headers через on-chain Light Client + Oracle для фіналіності. Зменшує trust assumptions, але додає залежність від провайдера.
// Інтеграція с LayerZero
import "@layerzerolabs/lz-evm-sdk-v2/contracts/oft/OFT.sol";
contract MyToken is OFT {
constructor(
string memory name,
string memory symbol,
address lzEndpoint,
address owner
) OFT(name, symbol, lzEndpoint, owner) {}
// OFT стандарт автоматично реалізує cross-chain transfer
// через burn на source + mint на destination через LayerZero messaging
}
OFT (Omnichain Fungible Token) від LayerZero — готовий стандарт для cross-chain токенів з мінімальним кастомним кодом.
Proof of reserves
Для custodial wrapped токенів — публічна верифіковність резервів критична після коллапсу BUSD та інших централізованих stablecoin.
Chainlink Proof of Reserve — оракул, який верифікує off-chain резерви (наприклад, BTC у кастодіана) та публікує результат on-chain. Контракт може перевіряти резерви перед кожним mint:
AggregatorV3Interface public reserveFeed;
function mint(address to, uint256 amount) external onlyMinter {
(, int256 reserveBalance,,,) = reserveFeed.latestRoundData();
require(
int256(totalSupply() + amount) <= reserveBalance,
"Insufficient reserves"
);
_mint(to, amount);
}
Терміни та стек
| Тип wrapped-токена | Складність | Терміни |
|---|---|---|
| WETH-style (same chain) | Низька | 1–2 дні |
| Cross-chain з централізованим relayer | Середня | 1–2 тижні |
| Cross-chain через LayerZero/CCIP | Середня | 1 тиждень + інтеграційне тестування |
| Кастомний децентралізований мост | Висока | 6–12 тижнів + аудит |
Для cross-chain токенів з реальними активами — аудит обов'язковий. Мосты — найбільш атакуваний компонент всього DeFi.







