Gönderi

Cross-Chain Bridge Geliştirme: Ethereum ve Polygon Arasında Varlık Transferi

Ethereum ve Polygon arasında güvenli cross-chain bridge geliştirme. Lock-and-mint mekanizması, relayer implementasyonu ve güvenlik best practices.

Cross-Chain Bridge Geliştirme: Ethereum ve Polygon Arasında Varlık Transferi

Blockchain ekosisteminin fragmentasyonu, farklı zincirler arasında varlık transferini kritik bir ihtiyaç haline getirdi. Kullanıcılar Ethereum’daki token’larını Polygon’da kullanmak, arbitraj fırsatlarını değerlendirmek veya düşük gas fee’li zincirlerde işlem yapmak istiyorlar. Cross-chain bridge’ler bu sorunu çözüyor. Bu yazıda Ethereum ve Polygon arasında tam fonksiyonel bir bridge nasıl geliştireceğinizi, lock-and-mint mekanizmasını, güvenlik önlemlerini ve relayer implementasyonunu detaylı şekilde inceleyeceğiz.

Cross-Chain Bridge Temelleri

Blockchain bridge’leri, farklı zincirler arasında bilgi ve değer transferini sağlayan protokollerdir. İki temel yaklaşım vardır:

1. Lock-and-Mint (Kilit ve Basım)

Kaynak zincirde token’lar kilitlenir, hedef zincirde eşdeğer wrapped token basılır. Bu yaklaşım en yaygın kullanılan yöntemdir çünkü native token’ı korur ve çift harcama riskini ortadan kaldırır.

2. Burn-and-Mint (Yakma ve Basım)

Kaynak zincirde token yakılır, hedef zincirde yeniden basılır. Bu yöntem wrapped token’lar için daha uygundur.

Lock-and-Mint Mekanizması Lock-and-mint bridge mekanizmasının çalışma prensibi

Proje Mimarisi

Bridge’imiz üç ana bileşenden oluşacak:

  1. Source Chain Contract (Ethereum): Token’ları kilitler ve event emit eder
  2. Destination Chain Contract (Polygon): Wrapped token basar ve yakma işlemi yapar
  3. Relayer Service: Event’leri dinler ve zincirler arası mesajları iletir

Teknoloji Stack

1
2
3
4
5
6
# Smart Contract Development
npm install --save-dev hardhat @nomiclabs/hardhat-ethers ethers
npm install --save-dev @openzeppelin/contracts

# Python Relayer
pip install web3 python-dotenv asyncio aiohttp redis

Cross-chain bridge geliştirmede güvenlik en kritik faktördür. Tüm kodu production’a almadan önce mutlaka security audit yaptırın.

Smart Contract Implementasyonu

Ethereum (Source Chain) Bridge Contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";

/**
 * @title EthereumBridge
 * @dev Ethereum tarafındaki bridge contract'ı
 * Token'ları kilitler ve Polygon'a transfer için event emit eder
 */
contract EthereumBridge is ReentrancyGuard, Ownable, Pausable {
    using SafeERC20 for IERC20;
    
    // Desteklenen token'lar
    mapping(address => bool) public supportedTokens;
    
    // Transfer ID tracking (replay attack önleme)
    mapping(bytes32 => bool) public processedTransfers;
    
    // Minimum ve maksimum transfer limitleri
    mapping(address => uint256) public minTransferAmount;
    mapping(address => uint256) public maxTransferAmount;
    
    // Bridge fee (basis points - 10000 = 100%)
    uint256 public bridgeFee = 10; // 0.1%
    address public feeCollector;
    
    // Event'ler
    event TokensLocked(
        bytes32 indexed transferId,
        address indexed token,
        address indexed sender,
        address recipient,
        uint256 amount,
        uint256 timestamp,
        uint256 nonce
    );
    
    event TokensReleased(
        bytes32 indexed transferId,
        address indexed token,
        address indexed recipient,
        uint256 amount
    );
    
    event TokenAdded(address indexed token, uint256 minAmount, uint256 maxAmount);
    event FeeUpdated(uint256 newFee);
    
    // Nonce tracking (her kullanıcı için)
    mapping(address => uint256) public userNonces;
    
    constructor(address _feeCollector) {
        require(_feeCollector != address(0), "Invalid fee collector");
        feeCollector = _feeCollector;
    }
    
    /**
     * @dev Token'ı bridge'e kilitler ve Polygon'a transfer için event emit eder
     * @param token Transfer edilecek token adresi
     * @param amount Transfer miktarı
     * @param recipient Polygon'daki alıcı adres
     */
    function lockTokens(
        address token,
        uint256 amount,
        address recipient
    ) external nonReentrant whenNotPaused {
        require(supportedTokens[token], "Token not supported");
        require(amount >= minTransferAmount[token], "Amount too low");
        require(amount <= maxTransferAmount[token], "Amount too high");
        require(recipient != address(0), "Invalid recipient");
        
        // Transfer ID oluştur (benzersiz olmalı)
        uint256 nonce = userNonces[msg.sender]++;
        bytes32 transferId = keccak256(
            abi.encodePacked(
                msg.sender,
                recipient,
                token,
                amount,
                block.chainid,
                nonce,
                block.timestamp
            )
        );
        
        require(!processedTransfers[transferId], "Transfer already processed");
        processedTransfers[transferId] = true;
        
        // Fee hesapla
        uint256 fee = (amount * bridgeFee) / 10000;
        uint256 netAmount = amount - fee;
        
        // Token'ları contract'a transfer et
        IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
        
        // Fee'yi collector'a gönder
        if (fee > 0) {
            IERC20(token).safeTransfer(feeCollector, fee);
        }
        
        // Event emit et (relayer bu event'i dinleyecek)
        emit TokensLocked(
            transferId,
            token,
            msg.sender,
            recipient,
            netAmount,
            block.timestamp,
            nonce
        );
    }
    
    /**
     * @dev Polygon'dan gelen token'ları unlock eder
     * @param transferId Polygon'daki transfer ID
     * @param token Token adresi
     * @param recipient Alıcı adres
     * @param amount Miktar
     * @param signatures Validator imzaları
     */
    function releaseTokens(
        bytes32 transferId,
        address token,
        address recipient,
        uint256 amount,
        bytes[] calldata signatures
    ) external nonReentrant whenNotPaused {
        require(!processedTransfers[transferId], "Transfer already processed");
        require(supportedTokens[token], "Token not supported");
        
        // İmzaları doğrula (multi-sig validator sistemi)
        _verifySignatures(transferId, token, recipient, amount, signatures);
        
        processedTransfers[transferId] = true;
        
        // Token'ları unlock et
        IERC20(token).safeTransfer(recipient, amount);
        
        emit TokensReleased(transferId, token, recipient, amount);
    }
    
    /**
     * @dev Yeni token ekler
     */
    function addSupportedToken(
        address token,
        uint256 minAmount,
        uint256 maxAmount
    ) external onlyOwner {
        require(token != address(0), "Invalid token");
        require(maxAmount > minAmount, "Invalid limits");
        
        supportedTokens[token] = true;
        minTransferAmount[token] = minAmount;
        maxTransferAmount[token] = maxAmount;
        
        emit TokenAdded(token, minAmount, maxAmount);
    }
    
    /**
     * @dev Bridge fee'yi günceller
     */
    function updateBridgeFee(uint256 newFee) external onlyOwner {
        require(newFee <= 100, "Fee too high"); // Max 1%
        bridgeFee = newFee;
        emit FeeUpdated(newFee);
    }
    
    /**
     * @dev İmzaları doğrular (basitleştirilmiş - production'da daha complex olmalı)
     */
    function _verifySignatures(
        bytes32 transferId,
        address token,
        address recipient,
        uint256 amount,
        bytes[] calldata signatures
    ) internal view {
        // Production'da: Multi-sig doğrulama
        // Şimdilik basit bir check
        require(signatures.length >= 2, "Insufficient signatures");
        
        bytes32 messageHash = keccak256(
            abi.encodePacked(transferId, token, recipient, amount)
        );
        
        // ECDSA signature recovery ve validator check
        // Bu kısım production'da genişletilmeli
    }
    
    /**
     * @dev Emergency pause
     */
    function pause() external onlyOwner {
        _pause();
    }
    
    function unpause() external onlyOwner {
        _unpause();
    }
}

Polygon (Destination Chain) Bridge Contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";

/**
 * @title WrappedToken
 * @dev Polygon'da basılacak wrapped token (örn: wETH, wUSDC)
 */
contract WrappedToken is ERC20, ERC20Burnable, Ownable {
    address public bridge;
    
    constructor(
        string memory name,
        string memory symbol,
        address _bridge
    ) ERC20(name, symbol) {
        bridge = _bridge;
    }
    
    modifier onlyBridge() {
        require(msg.sender == bridge, "Only bridge");
        _;
    }
    
    function mint(address to, uint256 amount) external onlyBridge {
        _mint(to, amount);
    }
    
    function burnFrom(address account, uint256 amount) public override onlyBridge {
        super.burnFrom(account, amount);
    }
}

/**
 * @title PolygonBridge
 * @dev Polygon tarafındaki bridge contract'ı
 * Wrapped token basar ve geri transferde yakar
 */
contract PolygonBridge is ReentrancyGuard, Ownable, Pausable {
    // Ethereum token -> Polygon wrapped token mapping
    mapping(address => address) public wrappedTokens;
    
    // İşlenmiş transfer'lar
    mapping(bytes32 => bool) public processedTransfers;
    
    // Validator adresleri (multi-sig için)
    mapping(address => bool) public validators;
    address[] public validatorList;
    uint256 public requiredSignatures;
    
    event TokensMinted(
        bytes32 indexed transferId,
        address indexed wrappedToken,
        address indexed recipient,
        uint256 amount
    );
    
    event TokensBurned(
        bytes32 indexed transferId,
        address indexed sender,
        address indexed ethereumRecipient,
        address wrappedToken,
        uint256 amount,
        uint256 nonce
    );
    
    event WrappedTokenCreated(
        address indexed ethereumToken,
        address indexed wrappedToken
    );
    
    // User nonce tracking
    mapping(address => uint256) public userNonces;
    
    constructor(address[] memory _validators, uint256 _requiredSignatures) {
        require(_validators.length >= _requiredSignatures, "Invalid validator config");
        
        for (uint256 i = 0; i < _validators.length; i++) {
            require(_validators[i] != address(0), "Invalid validator");
            validators[_validators[i]] = true;
            validatorList.push(_validators[i]);
        }
        
        requiredSignatures = _requiredSignatures;
    }
    
    /**
     * @dev Ethereum'dan gelen transfer için wrapped token basar
     * @param transferId Ethereum'daki transfer ID
     * @param ethereumToken Ethereum'daki original token
     * @param recipient Token alacak adres
     * @param amount Basılacak miktar
     * @param signatures Validator imzaları
     */
    function mintTokens(
        bytes32 transferId,
        address ethereumToken,
        address recipient,
        uint256 amount,
        bytes[] calldata signatures
    ) external nonReentrant whenNotPaused {
        require(!processedTransfers[transferId], "Already processed");
        require(wrappedTokens[ethereumToken] != address(0), "Token not mapped");
        
        // İmzaları doğrula
        _verifySignatures(transferId, ethereumToken, recipient, amount, signatures);
        
        processedTransfers[transferId] = true;
        
        address wrappedToken = wrappedTokens[ethereumToken];
        
        // Wrapped token bas
        WrappedToken(wrappedToken).mint(recipient, amount);
        
        emit TokensMinted(transferId, wrappedToken, recipient, amount);
    }
    
    /**
     * @dev Wrapped token'ları yakarak Ethereum'a geri gönderir
     * @param wrappedToken Yakılacak wrapped token
     * @param amount Miktar
     * @param ethereumRecipient Ethereum'da alacak adres
     */
    function burnTokens(
        address wrappedToken,
        uint256 amount,
        address ethereumRecipient
    ) external nonReentrant whenNotPaused {
        require(ethereumRecipient != address(0), "Invalid recipient");
        
        // Wrapped token'ın gerçek olduğunu doğrula
        address ethereumToken = _getEthereumToken(wrappedToken);
        require(ethereumToken != address(0), "Invalid wrapped token");
        
        // Transfer ID oluştur
        uint256 nonce = userNonces[msg.sender]++;
        bytes32 transferId = keccak256(
            abi.encodePacked(
                msg.sender,
                ethereumRecipient,
                wrappedToken,
                amount,
                block.chainid,
                nonce,
                block.timestamp
            )
        );
        
        // Token'ları yak
        WrappedToken(wrappedToken).burnFrom(msg.sender, amount);
        
        // Event emit et (relayer dinleyecek)
        emit TokensBurned(
            transferId,
            msg.sender,
            ethereumRecipient,
            wrappedToken,
            amount,
            nonce
        );
    }
    
    /**
     * @dev Yeni wrapped token oluşturur
     */
    function createWrappedToken(
        address ethereumToken,
        string memory name,
        string memory symbol
    ) external onlyOwner {
        require(ethereumToken != address(0), "Invalid ethereum token");
        require(wrappedTokens[ethereumToken] == address(0), "Already exists");
        
        WrappedToken wrappedToken = new WrappedToken(name, symbol, address(this));
        wrappedTokens[ethereumToken] = address(wrappedToken);
        
        emit WrappedTokenCreated(ethereumToken, address(wrappedToken));
    }
    
    /**
     * @dev İmzaları doğrular
     */
    function _verifySignatures(
        bytes32 transferId,
        address ethereumToken,
        address recipient,
        uint256 amount,
        bytes[] calldata signatures
    ) internal view {
        require(signatures.length >= requiredSignatures, "Insufficient signatures");
        
        bytes32 messageHash = keccak256(
            abi.encodePacked(transferId, ethereumToken, recipient, amount)
        );
        
        bytes32 ethSignedMessageHash = keccak256(
            abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash)
        );
        
        address[] memory signers = new address[](signatures.length);
        
        // Her imzayı doğrula ve validator olduğundan emin ol
        for (uint256 i = 0; i < signatures.length; i++) {
            address signer = _recoverSigner(ethSignedMessageHash, signatures[i]);
            require(validators[signer], "Invalid signer");
            
            // Duplicate check
            for (uint256 j = 0; j < i; j++) {
                require(signers[j] != signer, "Duplicate signature");
            }
            
            signers[i] = signer;
        }
    }
    
    /**
     * @dev ECDSA signature'dan signer'ı recover eder
     */
    function _recoverSigner(
        bytes32 messageHash,
        bytes memory signature
    ) internal pure returns (address) {
        require(signature.length == 65, "Invalid signature length");
        
        bytes32 r;
        bytes32 s;
        uint8 v;
        
        assembly {
            r := mload(add(signature, 32))
            s := mload(add(signature, 64))
            v := byte(0, mload(add(signature, 96)))
        }
        
        if (v < 27) {
            v += 27;
        }
        
        require(v == 27 || v == 28, "Invalid signature v value");
        
        return ecrecover(messageHash, v, r, s);
    }
    
    /**
     * @dev Wrapped token'dan ethereum token adresini bulur
     */
    function _getEthereumToken(address wrappedToken) internal view returns (address) {
        for (uint256 i = 0; i < validatorList.length; i++) {
            // Mapping'i ters çevirerek bul
            // Production'da daha efficient bir yöntem kullanılmalı
        }
        return address(0);
    }
    
    function pause() external onlyOwner {
        _pause();
    }
    
    function unpause() external onlyOwner {
        _unpause();
    }
}

Bridge Varlık Transfer Yöntemleri Farklı bridge tiplerinin varlık transfer mekanizmaları

Python Relayer İmplementasyonu

Relayer, her iki zinciri de izleyen ve transfer event’lerini işleyen critical bir bileşendir:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# relayer.py
import asyncio
import os
from web3 import Web3
from web3.middleware import geth_poa_middleware
from eth_account import Account
from eth_account.messages import encode_defunct
import json
from typing import Dict, List
from dotenv import load_dotenv
import redis
import logging

load_dotenv()

# Logging setup
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

class BridgeRelayer:
    """
    Cross-chain bridge relayer
    Ethereum ve Polygon arasında event'leri dinler ve işler
    """
    
    def __init__(self):
        # Web3 connections
        self.eth_w3 = Web3(Web3.WebsocketProvider(os.getenv("ETH_WSS_URL")))
        self.eth_w3.middleware_onion.inject(geth_poa_middleware, layer=0)
        
        self.poly_w3 = Web3(Web3.WebsocketProvider(os.getenv("POLYGON_WSS_URL")))
        self.poly_w3.middleware_onion.inject(geth_poa_middleware, layer=0)
        
        # Validator account (private key'den)
        self.validator_account = Account.from_key(os.getenv("VALIDATOR_PRIVATE_KEY"))
        
        # Contract instances
        self.eth_bridge = self._load_contract(
            self.eth_w3,
            os.getenv("ETH_BRIDGE_ADDRESS"),
            "EthereumBridge.json"
        )
        
        self.poly_bridge = self._load_contract(
            self.poly_w3,
            os.getenv("POLYGON_BRIDGE_ADDRESS"),
            "PolygonBridge.json"
        )
        
        # Redis for state management
        self.redis_client = redis.Redis(
            host='localhost',
            port=6379,
            decode_responses=True
        )
        
        logger.info("Bridge Relayer initialized")
        logger.info(f"Validator address: {self.validator_account.address}")
    
    def _load_contract(self, w3: Web3, address: str, abi_file: str):
        """Contract instance oluşturur"""
        with open(f"abis/{abi_file}", "r") as f:
            abi = json.load(f)
        
        return w3.eth.contract(
            address=Web3.to_checksum_address(address),
            abi=abi
        )
    
    async def monitor_ethereum_locks(self):
        """
        Ethereum'daki TokensLocked event'lerini dinler
        """
        logger.info("Monitoring Ethereum TokensLocked events...")
        
        # Event filter oluştur
        event_filter = self.eth_bridge.events.TokensLocked.create_filter(
            fromBlock='latest'
        )
        
        while True:
            try:
                # Yeni event'leri al
                for event in event_filter.get_new_entries():
                    await self._process_ethereum_lock(event)
                
                await asyncio.sleep(2)  # 2 saniye polling
                
            except Exception as e:
                logger.error(f"Ethereum monitoring error: {e}")
                await asyncio.sleep(5)
    
    async def _process_ethereum_lock(self, event):
        """
        Ethereum'da kilitlenmiş token için Polygon'da mint işlemi başlatır
        """
        args = event.args
        transfer_id = args.transferId.hex()
        
        logger.info(f"\nNew Ethereum Lock Detected")
        logger.info(f"   Transfer ID: {transfer_id}")
        logger.info(f"   Token: {args.token}")
        logger.info(f"   Amount: {args.amount}")
        logger.info(f"   Recipient: {args.recipient}")
        
        # Redis'te işlenmiş mi kontrol et
        if self.redis_client.get(f"processed:eth:{transfer_id}"):
            logger.info("   Already processed, skipping")
            return
        
        # Confirmation bekle (6 blok)
        await self._wait_confirmations(self.eth_w3, event.blockNumber, 6)
        
        # İmza oluştur
        signature = self._create_signature(
            transfer_id,
            args.token,
            args.recipient,
            args.amount
        )
        
        # Polygon'da mint transaction gönder
        try:
            tx_hash = await self._mint_on_polygon(
                transfer_id,
                args.token,
                args.recipient,
                args.amount,
                [signature]  # Production'da multiple validator imzaları
            )
            
            logger.info(f"   Minted on Polygon: {tx_hash.hex()}")
            
            # Redis'e işlenmiş olarak kaydet
            self.redis_client.setex(
                f"processed:eth:{transfer_id}",
                86400,  # 24 saat TTL
                "1"
            )
            
        except Exception as e:
            logger.error(f"   Mint failed: {e}")
    
    async def monitor_polygon_burns(self):
        """
        Polygon'daki TokensBurned event'lerini dinler
        """
        logger.info("Monitoring Polygon TokensBurned events...")
        
        event_filter = self.poly_bridge.events.TokensBurned.create_filter(
            fromBlock='latest'
        )
        
        while True:
            try:
                for event in event_filter.get_new_entries():
                    await self._process_polygon_burn(event)
                
                await asyncio.sleep(2)
                
            except Exception as e:
                logger.error(f"Polygon monitoring error: {e}")
                await asyncio.sleep(5)
    
    async def _process_polygon_burn(self, event):
        """
        Polygon'da yakılan token için Ethereum'da release işlemi başlatır
        """
        args = event.args
        transfer_id = args.transferId.hex()
        
        logger.info(f"\nNew Polygon Burn Detected")
        logger.info(f"   Transfer ID: {transfer_id}")
        logger.info(f"   Wrapped Token: {args.wrappedToken}")
        logger.info(f"   Amount: {args.amount}")
        logger.info(f"   Ethereum Recipient: {args.ethereumRecipient}")
        
        if self.redis_client.get(f"processed:poly:{transfer_id}"):
            logger.info("   Already processed, skipping")
            return
        
        # Confirmation bekle
        await self._wait_confirmations(self.poly_w3, event.blockNumber, 128)
        
        # İmza oluştur
        signature = self._create_signature(
            transfer_id,
            args.wrappedToken,
            args.ethereumRecipient,
            args.amount
        )
        
        # Ethereum'da release transaction gönder
        try:
            tx_hash = await self._release_on_ethereum(
                transfer_id,
                args.wrappedToken,
                args.ethereumRecipient,
                args.amount,
                [signature]
            )
            
            logger.info(f"   Released on Ethereum: {tx_hash.hex()}")
            
            self.redis_client.setex(
                f"processed:poly:{transfer_id}",
                86400,
                "1"
            )
            
        except Exception as e:
            logger.error(f"   Release failed: {e}")
    
    def _create_signature(
        self,
        transfer_id: str,
        token: str,
        recipient: str,
        amount: int
    ) -> bytes:
        """
        Transfer için validator imzası oluşturur
        """
        # Message hash oluştur
        message_hash = Web3.keccak(
            Web3.to_bytes(hexstr=transfer_id) +
            Web3.to_bytes(hexstr=token) +
            Web3.to_bytes(hexstr=recipient) +
            amount.to_bytes(32, 'big')
        )
        
        # Ethereum signed message format
        signable_message = encode_defunct(message_hash)
        
        # İmzala
        signed = self.validator_account.sign_message(signable_message)
        
        # v, r, s'yi bytes olarak birleştir
        signature = signed.r.to_bytes(32, 'big') + \
                     signed.s.to_bytes(32, 'big') + \
                     signed.v.to_bytes(1, 'big')
        
        return signature
    
    async def _mint_on_polygon(
        self,
        transfer_id: str,
        ethereum_token: str,
        recipient: str,
        amount: int,
        signatures: List[bytes]
    ) -> bytes:
        """
        Polygon'da wrapped token mint eder
        """
        nonce = self.poly_w3.eth.get_transaction_count(
            self.validator_account.address
        )
        
        # Transaction oluştur
        tx = self.poly_bridge.functions.mintTokens(
            Web3.to_bytes(hexstr=transfer_id),
            Web3.to_checksum_address(ethereum_token),
            Web3.to_checksum_address(recipient),
            amount,
            signatures
        ).build_transaction({
            'from': self.validator_account.address,
            'nonce': nonce,
            'gas': 300000,
            'gasPrice': self.poly_w3.eth.gas_price,
            'chainId': 137  # Polygon Mainnet
        })
        
        # İmzala ve gönder
        signed_tx = self.validator_account.sign_transaction(tx)
        tx_hash = self.poly_w3.eth.send_raw_transaction(signed_tx.rawTransaction)
        
        # Receipt bekle
        receipt = self.poly_w3.eth.wait_for_transaction_receipt(tx_hash)
        
        if receipt.status != 1:
            raise Exception("Transaction failed")
        
        return tx_hash
    
    async def _release_on_ethereum(
        self,
        transfer_id: str,
        token: str,
        recipient: str,
        amount: int,
        signatures: List[bytes]
    ) -> bytes:
        """
        Ethereum'da kilitlenen token'ları release eder
        """
        nonce = self.eth_w3.eth.get_transaction_count(
            self.validator_account.address
        )
        
        tx = self.eth_bridge.functions.releaseTokens(
            Web3.to_bytes(hexstr=transfer_id),
            Web3.to_checksum_address(token),
            Web3.to_checksum_address(recipient),
            amount,
            signatures
        ).build_transaction({
            'from': self.validator_account.address,
            'nonce': nonce,
            'gas': 200000,
            'gasPrice': self.eth_w3.eth.gas_price,
            'chainId': 1  # Ethereum Mainnet
        })
        
        signed_tx = self.validator_account.sign_transaction(tx)
        tx_hash = self.eth_w3.eth.send_raw_transaction(signed_tx.rawTransaction)
        
        receipt = self.eth_w3.eth.wait_for_transaction_receipt(tx_hash)
        
        if receipt.status != 1:
            raise Exception("Transaction failed")
        
        return tx_hash
    
    async def _wait_confirmations(self, w3: Web3, block_number: int, confirmations: int):
        """
        Belirtilen sayıda confirmation bekler
        """
        target_block = block_number + confirmations
        
        while True:
            current_block = w3.eth.block_number
            
            if current_block >= target_block:
                logger.info(f"   {confirmations} confirmations reached")
                break
            
            remaining = target_block - current_block
            logger.info(f"   Waiting for confirmations... ({remaining} blocks)")
            
            await asyncio.sleep(12)  # Ethereum block time
    
    async def run(self):
        """
        Relayer'ı başlatır (her iki zinciri de aynı anda izler)
        """
        logger.info("Starting Bridge Relayer...")
        
        tasks = [
            asyncio.create_task(self.monitor_ethereum_locks()),
            asyncio.create_task(self.monitor_polygon_burns())
        ]
        
        await asyncio.gather(*tasks)

# Main
if __name__ == "__main__":
    relayer = BridgeRelayer()
    asyncio.run(relayer.run())

Blockchain Relayer Mimarisi Relayer servisinin zincirler arası mesaj iletim mimarisi

Relayer’larınızı birden fazla sunucuda çalıştırarak redundancy sağlayın. Redis ile state synchronization yapın.

Deployment ve Test

Hardhat Deployment Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
const hre = require("hardhat");

async function main() {
  console.log("Deploying Bridge Contracts...\n");
  
  // Ethereum Bridge Deploy
  console.log("Deploying Ethereum Bridge...");
  const EthereumBridge = await hre.ethers.getContractFactory("EthereumBridge");
  const feeCollector = "0x..."; // Fee collector adresi
  const ethBridge = await EthereumBridge.deploy(feeCollector);
  await ethBridge.deployed();
  console.log(`Ethereum Bridge: ${ethBridge.address}\n`);
  
  // Polygon Bridge Deploy
  console.log("Deploying Polygon Bridge...");
  const PolygonBridge = await hre.ethers.getContractFactory("PolygonBridge");
  const validators = [
    "0x...",  // Validator 1
    "0x...",  // Validator 2
    "0x..."   // Validator 3
  ];
  const requiredSignatures = 2; // 2-of-3 multi-sig
  
  const polyBridge = await PolygonBridge.deploy(validators, requiredSignatures);
  await polyBridge.deployed();
  console.log(`Polygon Bridge: ${polyBridge.address}\n`);
  
  // USDC Token Support Ekle
  console.log("Adding USDC support...");
  const usdcAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; // Mainnet USDC
  
  const addTokenTx = await ethBridge.addSupportedToken(
    usdcAddress,
    hre.ethers.utils.parseUnits("10", 6),  // Min: 10 USDC
    hre.ethers.utils.parseUnits("1000000", 6)  // Max: 1M USDC
  );
  await addTokenTx.wait();
  console.log("USDC support added to Ethereum Bridge\n");
  
  // Wrapped USDC Deploy
  console.log("Creating Wrapped USDC on Polygon...");
  const createWrappedTx = await polyBridge.createWrappedToken(
    usdcAddress,
    "Wrapped USDC",
    "wUSDC"
  );
  const receipt = await createWrappedTx.wait();
  const wrappedUSDC = receipt.events[0].args.wrappedToken;
  console.log(`Wrapped USDC: ${wrappedUSDC}\n`);
  
  // Deployment summary
  console.log("Deployment Summary");
  console.log("=".repeat(50));
  console.log(`Ethereum Bridge: ${ethBridge.address}`);
  console.log(`Polygon Bridge:  ${polyBridge.address}`);
  console.log(`Wrapped USDC:    ${wrappedUSDC}`);
  console.log(`Validators:      ${validators.length}`);
  console.log(`Required Sigs:   ${requiredSignatures}`);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

Test Suite

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// test/Bridge.test.js
const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("Cross-Chain Bridge", function () {
  let ethBridge, polyBridge, usdc, wUSDC;
  let owner, user, validator1, validator2, validator3;
  
  beforeEach(async function () {
    [owner, user, validator1, validator2, validator3] = await ethers.getSigners();
    
    // Mock USDC deploy
    const MockERC20 = await ethers.getContractFactory("MockERC20");
    usdc = await MockERC20.deploy("USDC", "USDC", 6);
    
    // Bridges deploy
    const EthereumBridge = await ethers.getContractFactory("EthereumBridge");
    ethBridge = await EthereumBridge.deploy(owner.address);
    
    const PolygonBridge = await ethers.getContractFactory("PolygonBridge");
    polyBridge = await PolygonBridge.deploy(
      [validator1.address, validator2.address, validator3.address],
      2  // 2-of-3
    );
    
    // Setup
    await ethBridge.addSupportedToken(
      usdc.address,
      ethers.utils.parseUnits("1", 6),
      ethers.utils.parseUnits("1000000", 6)
    );
    
    await polyBridge.createWrappedToken(
      usdc.address,
      "Wrapped USDC",
      "wUSDC"
    );
    
    const wrappedAddr = await polyBridge.wrappedTokens(usdc.address);
    wUSDC = await ethers.getContractAt("WrappedToken", wrappedAddr);
    
    // User'a USDC ver
    await usdc.mint(user.address, ethers.utils.parseUnits("10000", 6));
  });
  
  it("Should lock tokens on Ethereum", async function () {
    const amount = ethers.utils.parseUnits("100", 6);
    
    // Approve
    await usdc.connect(user).approve(ethBridge.address, amount);
    
    // Lock
    const tx = await ethBridge.connect(user).lockTokens(
      usdc.address,
      amount,
      user.address
    );
    
    const receipt = await tx.wait();
    const event = receipt.events.find(e => e.event === "TokensLocked");
    
    expect(event.args.amount).to.equal(amount.mul(9990).div(10000)); // 0.1% fee
    expect(await usdc.balanceOf(ethBridge.address)).to.equal(amount.mul(9990).div(10000));
  });
  
  it("Should mint wrapped tokens on Polygon", async function () {
    // Transfer ID simüle et
    const transferId = ethers.utils.keccak256(
      ethers.utils.defaultAbiCoder.encode(
        ["address", "address", "uint256"],
        [user.address, usdc.address, 100]
      )
    );
    
    const amount = ethers.utils.parseUnits("100", 6);
    
    // İmzalar oluştur (validator1 ve validator2)
    const messageHash = ethers.utils.keccak256(
      ethers.utils.defaultAbiCoder.encode(
        ["bytes32", "address", "address", "uint256"],
        [transferId, usdc.address, user.address, amount]
      )
    );
    
    const sig1 = await validator1.signMessage(ethers.utils.arrayify(messageHash));
    const sig2 = await validator2.signMessage(ethers.utils.arrayify(messageHash));
    
    // Mint
    await polyBridge.mintTokens(
      transferId,
      usdc.address,
      user.address,
      amount,
      [sig1, sig2]
    );
    
    expect(await wUSDC.balanceOf(user.address)).to.equal(amount);
  });
  
  it("Should burn and release tokens", async function () {
    // Önce mint et
    const amount = ethers.utils.parseUnits("100", 6);
    // ... (önceki test'teki mint işlemi)
    
    // Wrapped token'ları yak
    await wUSDC.connect(user).approve(polyBridge.address, amount);
    
    const tx = await polyBridge.connect(user).burnTokens(
      wUSDC.address,
      amount,
      user.address
    );
    
    const receipt = await tx.wait();
    const event = receipt.events.find(e => e.event === "TokensBurned");
    
    expect(event.args.amount).to.equal(amount);
    expect(await wUSDC.balanceOf(user.address)).to.equal(0);
  });
  
  it("Should reject insufficient signatures", async function () {
    const transferId = ethers.utils.randomBytes(32);
    const amount = ethers.utils.parseUnits("100", 6);
    
    const messageHash = ethers.utils.keccak256(
      ethers.utils.defaultAbiCoder.encode(
        ["bytes32", "address", "address", "uint256"],
        [transferId, usdc.address, user.address, amount]
      )
    );
    
    const sig1 = await validator1.signMessage(ethers.utils.arrayify(messageHash));
    
    // Sadece 1 imza (2 gerekli)
    await expect(
      polyBridge.mintTokens(
        transferId,
        usdc.address,
        user.address,
        amount,
        [sig1]
      )
    ).to.be.revertedWith("Insufficient signatures");
  });
});

Güvenlik Önlemleri ve Best Practices

1. Multi-Signature Validation

Production bridge’lerde mutlaka multi-sig validator sistemi kullanın:

1
2
3
4
5
6
7
8
9
10
# Minimum 3-of-5 validator setup
VALIDATOR_ADDRESSES = [
    "0x...",  # Validator 1
    "0x...",  # Validator 2
    "0x...",  # Validator 3
    "0x...",  # Validator 4
    "0x...",  # Validator 5
]

REQUIRED_SIGNATURES = 3  # 3-of-5

2. Rate Limiting

Bridge’e hızlı ve büyük transfer saldırılarını önlemek için:

// Daily transfer limits
mapping(address => uint256) public dailyVolume;
mapping(address => uint256) public lastResetTime;
uint256 public dailyLimit = 1000000 * 10**6; // 1M USDC

function checkRateLimit(address token, uint256 amount) internal {
    if (block.timestamp > lastResetTime[token] + 1 days) {
        dailyVolume[token] = 0;
        lastResetTime[token] = block.timestamp;
    }
    
    require(
        dailyVolume[token] + amount <= dailyLimit,
        "Daily limit exceeded"
    );
    
    dailyVolume[token] += amount;
}

3. Emergency Pause Mechanism

Kritik güvenlik açığı tespit edildiğinde bridge’i durdurabilmek:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Admin dashboard'dan pause
async def emergency_pause():
    """
    Tüm bridge contract'larını pause eder
    """
    logger.warning("🚨 EMERGENCY PAUSE ACTIVATED")
    
    # Ethereum bridge pause
    eth_tx = await eth_bridge.functions.pause().build_transaction({...})
    # ...
    
    # Polygon bridge pause
    poly_tx = await poly_bridge.functions.pause().build_transaction({...})
    # ...

4. Monitoring ve Alerting

Bridge health monitoring sistemi:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import asyncio
from datetime import datetime, timedelta

class BridgeMonitor:
    """
    Bridge sağlık kontrolü ve alert sistemi
    """
    
    async def check_bridge_health(self):
        """
        Periyodik health check
        """
        while True:
            # 1. Balance check
            await self._check_balances()
            
            # 2. Pending transfers check
            await self._check_pending_transfers()
            
            # 3. Validator status
            await self._check_validators()
            
            # 4. Gas price monitoring
            await self._check_gas_prices()
            
            await asyncio.sleep(300)  # Her 5 dakika
    
    async def _check_balances(self):
        """
        Bridge balance'larının tutarlı olduğunu kontrol eder
        """
        # Ethereum'daki locked amount
        eth_locked = await self.get_total_locked()
        
        # Polygon'daki minted amount
        poly_minted = await self.get_total_minted()
        
        # Fark %1'den fazla ise alert
        if abs(eth_locked - poly_minted) / eth_locked > 0.01:
            await self.send_alert(
                "Balance Mismatch Detected",
                f"ETH Locked: {eth_locked}\nPoly Minted: {poly_minted}"
            )

Performans Optimizasyonu

Batch Processing

Çok sayıda transfer için gas tasarrufu:

function lockTokensBatch(
    address[] calldata tokens,
    uint256[] calldata amounts,
    address[] calldata recipients
) external nonReentrant whenNotPaused {
    require(tokens.length == amounts.length, "Length mismatch");
    require(tokens.length == recipients.length, "Length mismatch");
    require(tokens.length <= 10, "Batch too large");
    
    for (uint256 i = 0; i < tokens.length; i++) {
        _lockTokensSingle(tokens[i], amounts[i], recipients[i]);
    }
}

Relayer Load Balancing

Multiple relayer instance çalıştırarak redundancy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# docker-compose.yml
version: '3.8'

services:
  relayer-1:
    build: .
    environment:
      - RELAYER_ID=1
      - REDIS_HOST=redis
    restart: always
  
  relayer-2:
    build: .
    environment:
      - RELAYER_ID=2
      - REDIS_HOST=redis
    restart: always
  
  relayer-3:
    build: .
    environment:
      - RELAYER_ID=3
      - REDIS_HOST=redis
    restart: always
  
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

Sonuç

Cross-chain bridge geliştirmek, karmaşık ancak blockchain ekosisteminin interoperability’si için kritik bir konudur. Bu yazıda ele aldığımız lock-and-mint mekanizması, güvenli ve ölçeklenebilir bir bridge implementasyonu için temel oluşturur.

Production ortamına geçmeden önce mutlaka kapsamlı security audit yaptırın (CertiK, OpenZeppelin, vb.).

Bug bounty programı başlatarak community’nin güvenlik arayışından faydalanabilirsiniz.

Production checklist:

  • Testnet’te extensive testing yapın
  • Multi-sig wallet ve timelock kullanın
  • Insurance fund oluşturun
  • Rate limiting ve anti-spam mekanizmaları ekleyin

Cross-chain future’da bridge’ler blockchain adoption’ı için vazgeçilmez olacaktır.

Faydalı Kaynaklar

Bu gönderi CC BY 4.0 lisansı altındadır.