took a simple proof-of-concept bridge. decided to take it as a base and try to build something closer to a production-ready system, focusing on fixing the major security and centralization issues i found.
ended up adding decentralized validation, a separate relayer/validator system, economic incentives, and a proper lock&mint model. here's how it evolved
- problem: the original bridge had a single validator. a huge single point of failure and completely centralized.
- solution: added a
ValidatorManagercontract. now a group of independent validators have to sign off on every transaction. it's an m-of-n setup, so if one validator goes offline, the bridge keeps working.
- problem: one script was doing everything - listening, signing, and sending transactions. it was a mess and mixed up responsibilities.
- solution: split these roles:
- validators: they just listen for events (
Lock,BurnWrapped) and submit signatures to an api. cheap and simple. - relayers: anyone can run a relayer. they just poll the api for enough signatures and submit the final transaction to the destination chain, paying the gas.
- validators: they just listen for events (
- problem: why would anyone run a relayer and spend their own money on gas? without an incentive, the system wouldn't work.
- solution: added a
relayerFee. when a user locks tokens, they pay a small extra fee. this fee goes to the relayer who successfully processes their transaction. this creates a real, market-driven incentive for a permissionless relayer network.
NonceManager.sol: created a separate contract just to handle nonces. this prevents replay attacks and keeps the main bridge contract cleaner. the bridge is theownerof the nonce manager, so only it can change the state.claimFailedLockfunction: a safety net for users. if a transaction gets stuck and isn't processed within 24 hours, the user can get all their funds back, including the fee. this way, user funds are never lost.- standard stuff: used openzeppelin's
Pausablefor an emergency stop andReentrancyGuardto prevent re-entrancy attacks.
- lock (source chain):
- a user calls
lock(), payingamount + relayerFee. the bridge locks the tokens and emits an event.
- a user calls
- validate & sign (off-chain):
- validators see the event, sign the message, and send their signatures to the api server.
- relay (off-chain):
- a relayer polls the api. once enough signatures are collected, it grabs them.
- mint (destination chain):
- the relayer calls
mint()with the signatures. the destination bridge verifies everything, mints tokens to the user, and sends therelayerFeeto the relayer.
- the relayer calls
the reverse process (burnWrapped -> unlock) works the same way.
this is still just a foundation, to make it truly production-ready, here's what i'd do next:
-
decentralized signature aggregation:
- the api server is a single point of failure. i'd replace it with a p2p gossip protocol so validators can talk to each other directly, like how wormhole's guardians work.
-
decentralized governance (
Multisig):- the admin functions are currently unprotected. need to use a multisig wallet (gnosis safe)
-
persistent & scalable off-chain storage:
- the api server's in-memory "db" is just for testing. for a real system, i'd use something like postgresql + redis with replication to make sure signatures aren't lost on a server restart.
-
advanced relayer logic:
- build a smarter relayer that analyzes gas prices and profitability to decide which transactions to process first.
-
alerting:
- set up a monitoring system (prometheus/grafana?) to send alerts
-
upgradability:
- use uups
-
support more tokens and more chains:
- refactor the contracts to support any erc20/erc721s.
to run the full end-to-end environment locally.
- run two local blockchain nodes (required foundry setup).
- deploy the contracts to both.
- start all the off-chain services.
- run the
initiateLock.jsscript to kick off a transfer.
also can run hardhat test
Terminal 1 (wsl, ubuntu)
anvil --port 8545 --chain-id 31337Terminal 2 (wsl, ubuntu)
anvil --port 9545 --chain-id 31338Terminal 3
pnpm run deploy:allTerminal 4
pnpm run devTerminal 5
npx hardhat run scripts/initiateLock.js --network chain1