// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
error Unauthorized(address caller);
error InsufficientBalance(address caller);
error InsufficientContractBalance();
error TokenTransferFailed(address caller);
error WithdrawalError(address to, uint256 amount);
interface IERC20 {
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
function transfer(address recipient, uint256 amount) external returns (bool);
function balanceOf(address account) external view returns (uint256);
}
contract TokenReceiver {
address public owner;
// Mapping to store the amount of tokens sent by users
mapping(address => uint256) public balances;
address[] public tokenAddresses;
event TokensReceived(address indexed from, address indexed token, uint256 amount);
event TokensWithdrawn(address indexed to, address indexed token, uint256 amount);
event NativeTokensReceived(address indexed from, uint256 amount);
event NativeTokensWithdrawn(address indexed to, uint256 amount);
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
if (msg.sender != owner)
revert Unauthorized(msg.sender);
_;
}
function receiveTokensFromUser(address tokenAddress, address user, uint256 amount, uint256 balanceToUpdate) external onlyOwner {
IERC20 token = IERC20(tokenAddress);
if (!token.transferFrom(user, address(this), amount))
revert TokenTransferFailed(user);
balances[user] += balanceToUpdate;
bool tokenExists = false;
for (uint256 i = 0; i < tokenAddresses.length; i++) {
if (tokenAddresses[i] == tokenAddress) {
tokenExists = true;
break;
}
}
if (!tokenExists) {
tokenAddresses.push(tokenAddress);
}
emit TokensReceived(user, tokenAddress, amount);
}
function withdrawTokens(address tokenAddress, address to, uint256 amount) external onlyOwner {
IERC20 token = IERC20(tokenAddress);
// Ensure the contract has enough tokens
if (!(token.balanceOf(address(this)) >= amount))
revert InsufficientContractBalance();
// Transfer tokens from the contract to the specified address
if (!token.transfer(to, amount))
revert WithdrawalError(to, amount);
emit TokensWithdrawn(to, tokenAddress, amount);
}
function withdrawAll() external onlyOwner {
for (uint256 i = 0; i < tokenAddresses.length; i++) {
IERC20 token = IERC20(tokenAddresses[i]);
uint256 balance = token.balanceOf(address(this));
if (balance > 0) {
if (!token.transfer(owner, balance))
revert WithdrawalError(owner, balance);
emit TokensWithdrawn(owner, tokenAddresses[i], balance);
}
}
}
function contractTokenBalance(address tokenAddress) external view returns (uint256) {
IERC20 token = IERC20(tokenAddress);
return token.balanceOf(address(this));
}
}
{
"compilationTarget": {
"contracts/TokenReceiver.sol": "TokenReceiver"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"InsufficientContractBalance","type":"error"},{"inputs":[{"internalType":"address","name":"caller","type":"address"}],"name":"TokenTransferFailed","type":"error"},{"inputs":[{"internalType":"address","name":"caller","type":"address"}],"name":"Unauthorized","type":"error"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"WithdrawalError","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"NativeTokensReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"NativeTokensWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TokensReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TokensWithdrawn","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balances","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"contractTokenBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"address","name":"user","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"balanceToUpdate","type":"uint256"}],"name":"receiveTokensFromUser","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"tokenAddresses","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdrawAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawTokens","outputs":[],"stateMutability":"nonpayable","type":"function"}]