Contract Source Code:
<i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
/**
* Contract that exposes the needed ERC20 token functions
*/
interface ERC20Interface {
// Send _value amount of tokens to address _to
function transfer(address _to, uint256 _value) external returns (bool);
// Get the account balance of another account with address _owner
function balanceOf(address _owner) external view returns (uint256 balance);
} <i class='far fa-question-circle text-muted ms-2' data-bs-trigger='hover' data-bs-toggle='tooltip' data-bs-html='true' data-bs-title='Click on the check box to select individual contract to compare. Only 1 contract can be selected from each side.'></i>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import "./ERC20Interface.sol";
/**
* Contract that will forward any incoming Ether and tokens to a designated destination address
*/
contract Forwarder {
uint256 private constant _UNLOCKED = 1;
uint256 private constant _LOCKED = 2;
uint256 private lock;
// Address to which any funds sent to this contract will be forwarded
address public ownerAddress;
// Address that can trigger flush
address public tokenFlusherAddress;
event ForwardedDeposit(address indexed from, address indexed to, address indexed forwardedTo, uint256 _amount);
event TokensFlushed(address forwarderAddress, uint value, address tokenContractAddress);
event OwnerChanged(address indexed oldOwner, address indexed newOwner);
event TokenFlusherChanged(address indexed oldFlusher, address indexed newFlusher);
error InvalidOwner();
error InvalidFlusher();
error NotAuthorized();
error TransferFailed(string transferType);
error InsufficientBalance();
error NewValueMustBeDifferent();
error ReentrantCallDetected();
/**
* Create the contract, and sets the owner address and token flusher address
*/
constructor(address ownerAddress_, address tokenFlusherAddress_) {
if (ownerAddress_ == address(0)) {
revert InvalidOwner();
}
if (tokenFlusherAddress_ == address(0)) {
revert InvalidFlusher();
}
ownerAddress = ownerAddress_;
tokenFlusherAddress = tokenFlusherAddress_;
lock = _UNLOCKED;
}
/**
* Default function; Gets called when Ether is deposited, and forwards it to the owner address
*/
receive() external payable nonReentrant {
(bool success,) = ownerAddress.call{value: msg.value}("");
if (!success) {
revert TransferFailed("ETH forward");
}
emit ForwardedDeposit(msg.sender, address(this), ownerAddress, msg.value);
}
/**
* It is possible that funds were sent to this address before the contract was deployed.
* We can flush those funds to the parent address.
*/
function flush() public nonReentrant {
uint256 balance = address(this).balance;
if (balance == 0) {
revert InsufficientBalance();
}
(bool success,) = ownerAddress.call{value: balance}("");
if (!success) {
revert TransferFailed("ETH flush");
}
}
/**
* Execute a token transfer of the full balance from the forwarder token to the parent address
* @param tokenContractAddress the address of the ERC20 token contract
*/
function flushTokens(address tokenContractAddress) public onlyOwnerOrTokenFlusher {
ERC20Interface instance = ERC20Interface(tokenContractAddress);
uint256 forwarderBalance = instance.balanceOf(address(this));
if (forwarderBalance == 0) {
revert InsufficientBalance();
}
// Use low-level call to handle tokens that don't return a value
(bool success, bytes memory data) = address(instance).call(
abi.encodeWithSelector(instance.transfer.selector, ownerAddress, forwarderBalance)
);
if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
revert TransferFailed("Token flush");
}
emit TokensFlushed(address(this), forwarderBalance, tokenContractAddress);
}
modifier onlyOwnerOrTokenFlusher {
if (msg.sender != ownerAddress && msg.sender != tokenFlusherAddress) {
revert NotAuthorized();
}
_;
}
modifier onlyOwner {
if (msg.sender != ownerAddress) {
revert NotAuthorized();
}
_;
}
function changeOwner(address newOwner) public onlyOwner {
if (newOwner == address(0)) {
revert InvalidOwner();
}
if (newOwner == ownerAddress) {
revert NewValueMustBeDifferent();
}
address oldOwner = ownerAddress;
ownerAddress = newOwner;
emit OwnerChanged(oldOwner, newOwner);
}
function changeTokenFlusher(address newFlusher) public onlyOwner {
if (newFlusher == address(0)) {
revert InvalidFlusher();
}
if (newFlusher == tokenFlusherAddress) {
revert NewValueMustBeDifferent();
}
address oldFlusher = tokenFlusherAddress;
tokenFlusherAddress = newFlusher;
emit TokenFlusherChanged(oldFlusher, newFlusher);
}
modifier nonReentrant() {
if (lock == _LOCKED) {
revert ReentrantCallDetected();
}
lock = _LOCKED;
_;
lock = _UNLOCKED;
}
}