BridgeLock
0x9145FC4fc5e99770c649Cf58e7713422FCffDDFcVerifiedNative balance0USDX
Account typeSmart contract
NetworkTuven 99359
Overview
- Contract name
- BridgeLock
- Verification
- Verified source
- Creation transaction
- 0xbb094bff8fcf5be2e5658cb3e6658ae94aa4b0839644091b4398d2873d163c88
- Balance updated at block
- 13039
// 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/utils/ReentrancyGuard.sol";
import "./BridgeBase.sol";
/// @title BridgeLock
/// @notice Bridge lock contract deployed on the source chain (e.g. BSC).
/// Users lock tokens here; the relayer unlocks them when bridging back.
contract BridgeLock is BridgeBase, ReentrancyGuard {
using SafeERC20 for IERC20;
uint256 public nonce;
/// @notice Tracks which nonces have been consumed (for unlock replay protection).
mapping(uint256 => bool) public processedNonces;
/// @notice Fees collected per token (address(0) = native), withdrawable to `feeRecipient`.
/// Kept separate from user deposits so an unlock can never spend fees or vice versa.
mapping(address => uint256) public collectedFees;
event FeesWithdrawn(address indexed token, address indexed to, uint256 amount);
event Locked(
address indexed token,
address indexed from,
uint256 amount,
uint256 destChainId,
uint256 nonce
);
event Unlocked(
address indexed token,
address indexed to,
uint256 amount,
uint256 nonce
);
/// @param _relayer Address of the off-chain relayer that processes bridge messages
constructor(address _relayer) BridgeBase(_relayer) {}
/// @notice Lock ERC20 tokens to bridge them to the destination chain. The bridge
/// fee is taken here; `Locked.amount` is the NET amount the relayer mints.
/// @param token ERC20 token address
/// @param amount Amount of tokens to lock (gross, fee included)
/// @param destChainId Destination chain ID
function lock(address token, uint256 amount, uint256 destChainId) external whenNotPaused {
require(amount > 0, "BridgeLock: zero amount");
// Measure what actually arrived so fee-on-transfer tokens cannot over-credit.
uint256 before = IERC20(token).balanceOf(address(this));
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
uint256 received = IERC20(token).balanceOf(address(this)) - before;
require(received > 0, "BridgeLock: nothing received");
(uint256 fee, uint256 net) = _splitFee(received);
require(net > 0, "BridgeLock: amount too small");
collectedFees[token] += fee;
uint256 currentNonce = nonce;
nonce++;
if (fee > 0) emit FeeCollected(token, msg.sender, fee);
emit Locked(token, msg.sender, net, destChainId, currentNonce);
}
/// @notice Lock native tokens to bridge them to the destination chain. The bridge
/// fee is taken here; `Locked.amount` is the NET amount the relayer mints.
/// @param destChainId Destination chain ID
function lockNative(uint256 destChainId) external payable whenNotPaused {
require(msg.value > 0, "BridgeLock: zero amount");
(uint256 fee, uint256 net) = _splitFee(msg.value);
require(net > 0, "BridgeLock: amount too small");
collectedFees[address(0)] += fee;
uint256 currentNonce = nonce;
nonce++;
if (fee > 0) emit FeeCollected(address(0), msg.sender, fee);
emit Locked(address(0), msg.sender, net, destChainId, currentNonce);
}
/// @notice Send the fees collected for `token` (address(0) = native) to `feeRecipient`.
/// Callable by anyone; the destination is fixed by the owner.
function withdrawFees(address token) external nonReentrant returns (uint256 amount) {
amount = collectedFees[token];
require(amount > 0, "BridgeLock: no fees");
collectedFees[token] = 0;
address to = feeRecipient;
if (token == address(0)) {
(bool success, ) = to.call{value: amount}("");
require(success, "BridgeLock: native transfer failed");
} else {
IERC20(token).safeTransfer(to, amount);
}
emit FeesWithdrawn(token, to, amount);
}
/// @notice Unlock tokens back to a user (called by relayer when bridging back).
/// @param token ERC20 token address (address(0) for native)
/// @param to Recipient address
/// @param amount Amount to unlock
/// @param _nonce Unique nonce from the burn event on the destination chain
function unlock(address token, address to, uint256 amount, uint256 _nonce)
external
onlyRelayer
nonReentrant
whenNotPaused
{
require(!processedNonces[_nonce], "BridgeLock: nonce already processed");
require(amount > 0, "BridgeLock: zero amount");
require(to != address(0), "BridgeLock: zero recipient");
processedNonces[_nonce] = true;
if (token == address(0)) {
(bool success, ) = to.call{value: amount}("");
require(success, "BridgeLock: native transfer failed");
} else {
IERC20(token).safeTransfer(to, amount);
}
emit Unlocked(token, to, amount, _nonce);
}
/// @notice Allow the contract to receive native tokens (for unlocking native).
receive() external payable {}
}
Contract ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "_relayer",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "EnforcedPause",
"type": "error"
},
{
"inputs": [],
"name": "ExpectedPause",
"type": "error"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "fee",
"type": "uint256"
}
],
"name": "FeeCollected",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "oldRecipient",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newRecipient",
"type": "address"
}
],
"name": "FeeRecipientUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "oldFeeBps",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "newFeeBps",
"type": "uint256"
}
],
"name": "FeeUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "FeesWithdrawn",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "destChainId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "nonce",
"type": "uint256"
}
],
"name": "Locked",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "Paused",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "oldRelayer",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newRelayer",
"type": "address"
}
],
"name": "RelayerUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "nonce",
"type": "uint256"
}
],
"name": "Unlocked",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "Unpaused",
"type": "event"
},
{
"inputs": [],
"name": "BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_FEE_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "collectedFees",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeBps",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeRecipient",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "destChainId",
"type": "uint256"
}
],
"name": "lock",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "destChainId",
"type": "uint256"
}
],
"name": "lockNative",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "nonce",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pause",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "paused",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "processedNonces",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "relayer",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_feeBps",
"type": "uint256"
}
],
"name": "setFeeBps",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_feeRecipient",
"type": "address"
}
],
"name": "setFeeRecipient",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_relayer",
"type": "address"
}
],
"name": "setRelayer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_nonce",
"type": "uint256"
}
],
"name": "unlock",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "unpause",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "withdrawFees",
"outputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]