// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; /// V1 bridge between the Bremo sovereign chain and an ERC-20 deployment. /// /// TRUST MODEL, read this honestly: this is an OPERATOR-RUN bridge. The /// operator watches Deposit events on one side and calls release() on the /// other. Users must trust the operator. This is how many bridges start, /// but it is custodial. A trust-minimized bridge (light clients, multisig /// federation, or zk proofs) is a later milestone and documented in the /// README. Do not market this v1 as trustless. /// /// NativeVault is deployed on the Bremo sovereign chain and holds native /// BREMO. TokenVault is deployed on the EVM chain (e.g. Arbitrum) and holds /// the ERC-20. Combined circulating supply across both chains never exceeds /// 21,000,000 because release() can only pay out what users locked in. contract NativeVault { address public immutable operator; mapping(bytes32 => bool) public processed; event Deposit(address indexed from, address indexed recipientOnOtherChain, uint256 amount); event Release(address indexed to, uint256 amount, bytes32 indexed sourceTx); constructor(address _operator) { operator = _operator; } /// Lock native BREMO to receive ERC-20 BREMO at `recipient` on the other chain. function deposit(address recipient) external payable { require(msg.value > 0, "zero"); emit Deposit(msg.sender, recipient, msg.value); } /// Operator releases native BREMO to a user who locked ERC-20 on the other /// chain. sourceTx is the lock transaction hash on the other chain, used /// once so a lock cannot be replayed. function release(address payable to, uint256 amount, bytes32 sourceTx) external { require(msg.sender == operator, "not operator"); require(!processed[sourceTx], "already processed"); processed[sourceTx] = true; (bool ok, ) = to.call{value: amount}(""); require(ok, "transfer failed"); emit Release(to, amount, sourceTx); } } contract TokenVault { using SafeERC20 for IERC20; IERC20 public immutable token; address public immutable operator; mapping(bytes32 => bool) public processed; event Deposit(address indexed from, address indexed recipientOnOtherChain, uint256 amount); event Release(address indexed to, uint256 amount, bytes32 indexed sourceTx); constructor(IERC20 _token, address _operator) { token = _token; operator = _operator; } /// Lock ERC-20 BREMO to receive native BREMO at `recipient` on the Bremo chain. function deposit(address recipient, uint256 amount) external { require(amount > 0, "zero"); token.safeTransferFrom(msg.sender, address(this), amount); emit Deposit(msg.sender, recipient, amount); } function release(address to, uint256 amount, bytes32 sourceTx) external { require(msg.sender == operator, "not operator"); require(!processed[sourceTx], "already processed"); processed[sourceTx] = true; token.safeTransfer(to, amount); emit Release(to, amount, sourceTx); } }