// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
abstract contract Context {
function _msgSender() internal view virtual returns (address) { return msg.sender; }
function _msgData() internal view virtual returns (bytes calldata) { return msg.data; }
}
abstract contract Pausable is Context {
event Paused(address account);
event Unpaused(address account);
bool private _paused;
constructor() { _paused = false; }
function paused() public view virtual returns (bool) { return _paused; }
modifier whenNotPaused() { require(!paused(), "Pausable: paused"); _; }
modifier whenPaused() { require(paused(), "Pausable: not paused"); _; }
function _pause() internal virtual whenNotPaused { _paused = true; emit Paused(_msgSender()); }
function _unpause() internal virtual whenPaused { _paused = false; emit Unpaused(_msgSender()); }
}
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() { _transferOwnership(_msgSender()); }
function owner() public view virtual returns (address) { return _owner; }
modifier onlyOwner() { require(owner() == _msgSender(), "Ownable: caller is not the owner"); _; }
function renounceOwnership() public virtual onlyOwner { _transferOwnership(address(0)); }
function transferOwnership(address newOwner) public virtual onlyOwner { require(newOwner != address(0), "Ownable: new owner is the zero address"); _transferOwnership(newOwner); }
function _transferOwnership(address newOwner) internal virtual {address oldOwner = _owner; _owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); }
}
interface IERC20 {
function totalSupply() external view returns (uint256);
function decimals() external view returns (uint8);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
}
interface IERC721 {
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function totalSupply() external view returns (uint256);
}
contract GtrNftTopupV3 is Ownable, Pausable {
bool public _tokenTopUpsPaused;
bool public _nftTopUpsPaused;
//token topups
mapping(address => IERC721) _tokenCompoundNftContractAddresses;
mapping(address => uint16) _tokenCompoundNftIds;
//nft top-ups
mapping(IERC20 => bool) public _validTokenContracts;
mapping(IERC721 => bool) public _validNftContracts;
address public _depositAddress;
uint256 public _depositTaxPercent;
uint256 public _depositTaxPercentDivisor;
event DepositRequested(address sender, address nftContract, uint256 nftId, address tokenContract, uint8 tokenDecimals, uint256 depositAmount, uint taxAmount);
event WithdrawalRequested(address sender, address nftContract, uint256 nftId, uint256 amount);
event CompoundUpdateRequested(address sender, address nftContract, uint256 nftId, uint8 percent);
event TokenCompoundUpdateRequested(address sender, address nftContract, uint256 nftId, uint8 percent);
constructor() {
//validate stablecoins for deposit
_validTokenContracts[IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7)] = true; //usdt mainnet
_validTokenContracts[IERC20(0x4Fabb145d64652a948d72533023f6E7A623C7C53)] = true; //busd mainnet
_validTokenContracts[IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48)] = true; //usdc mainnet
//validate existing GTR NFT contracts
_validNftContracts[IERC721(0x472B932cc83678f75A409326b71c7FCB16081Dd3)] = true; //diamond
_validNftContracts[IERC721(0xdAeC66BD252768DB641E8F51255260762708ACf4)] = true; //gold
_validNftContracts[IERC721(0x837204F046fF7F2a6E50cC4aec6aDc85D20a2A72)] = true; //silver
//set deposit settings
_depositAddress = 0x628789179aA833f3D8Aa319b3a3604991a682c2f;
_depositTaxPercent = 5;
_depositTaxPercentDivisor = 100;
//status
_tokenTopUpsPaused = false;
_nftTopUpsPaused = false;
}
function deposit(IERC721 nftContract, uint256 nftId, IERC20 tokenContract, uint256 amount) external whenNotPaused {
//validate tx
require(_validNftContracts[nftContract] == true, "NFT_INVALID");
require(nftContract.ownerOf(nftId) != address(0), "NFT_INVALID");
require(_validTokenContracts[tokenContract] == true, "ASSET_INVALID");
require(amount > 0, "AMOUNT_INVALID");
//transfer funds
require(tokenContract.balanceOf(msg.sender) >= amount, "BALANCE_INSUFFICIENT");
require(tokenContract.allowance(msg.sender, address(this)) >= amount, "ALLOWANCE_INSUFFICIENT");
(bool success, bytes memory data) = address(tokenContract).call(abi.encodeWithSelector(IERC20.transferFrom.selector, msg.sender, _depositAddress, amount));
require(success && (data.length == 0 || abi.decode(data, (bool))), "ERC20_TRANSFER_FAILED");
//emit event
uint256 taxAmount = (_depositTaxPercent > 0 && _depositTaxPercentDivisor > 0) ? (amount * _depositTaxPercent / _depositTaxPercentDivisor) : 0;
uint256 depositAmount = amount - taxAmount;
emit DepositRequested(msg.sender, address(nftContract), nftId, address(tokenContract), tokenContract.decimals(), depositAmount, taxAmount);
}
function withdraw(IERC721 nftContract, uint256 nftId, uint256 amount) external whenNotPaused {
require(_validNftContracts[nftContract] == true, "NFT_INVALID");
require(nftContract.ownerOf(nftId) == msg.sender, "NOT_ALLOWED");
//emit event
emit WithdrawalRequested(msg.sender, address(nftContract), nftId, amount);
}
function updateCompoundPercent(IERC721 nftContract, uint256 nftId, uint8 percent) external whenNotPaused {
require(_validNftContracts[nftContract] == true, "NFT_INVALID");
require(nftContract.ownerOf(nftId) == msg.sender, "NOT_ALLOWED");
//emit event
emit CompoundUpdateRequested(msg.sender, address(nftContract), nftId, percent);
}
//token compound
function getTokenCompoundDestination(address a) external view returns (address nftContract, uint16 nftId) {
return (address(_tokenCompoundNftContractAddresses[a]), _tokenCompoundNftIds[a]);
}
function setTokenCompoundDestination(IERC721 nftContract, uint16 nftId) external whenNotPaused {
require(_validNftContracts[nftContract] == true || address(nftContract) == address(0), "NFT_INVALID");
require(nftContract.ownerOf(nftId) != address(0), "NFT_INVALID");
if (_tokenCompoundNftContractAddresses[msg.sender] != nftContract) _tokenCompoundNftContractAddresses[msg.sender] = nftContract;
if (_tokenCompoundNftIds[msg.sender] != nftId) _tokenCompoundNftIds[msg.sender] = nftId;
//emit event
emit TokenCompoundUpdateRequested(msg.sender, address(nftContract), nftId, 100);
}
//admin functions
function setValidTokenContractStatus(IERC20 contractAddress, bool status) external onlyOwner {
_validTokenContracts[contractAddress] = status;
}
function setValidNftContractStatus(IERC721 contractAddress, bool status) external onlyOwner {
_validNftContracts[contractAddress] = status;
}
function setDepositWalletAddress(address to) external onlyOwner {
_depositAddress = to;
}
function setDepositTaxPercent(uint256 amount, uint256 divisor) external onlyOwner {
_depositTaxPercent = amount;
_depositTaxPercentDivisor = divisor;
}
//pausable implementation
function pause() external onlyOwner { _pause(); _tokenTopUpsPaused = true; _nftTopUpsPaused = true; }
function unpause() external onlyOwner { _unpause(); _tokenTopUpsPaused = false; _nftTopUpsPaused = false; }
function pauseTokenTopUps() external onlyOwner { _tokenTopUpsPaused = true; if (_nftTopUpsPaused == true) _pause(); }
function pauseNftTopUps() external onlyOwner { _nftTopUpsPaused = true; if (_tokenTopUpsPaused == true) _pause(); }
//default withdrawal functions
function withdrawToken(IERC20 token, uint256 amount, address to) external onlyOwner {
if (address(token) == address(0)) {
(bool success, ) = to.call{value: (amount == 0 ? address(this).balance : amount)}(new bytes(0));
require(success, "NATIVE_TRANSFER_FAILED");
} else {
(bool success, bytes memory data) = address(token).call(abi.encodeWithSelector(IERC20.transfer.selector, to, (amount == 0 ? token.balanceOf(address(this)) : amount)));
require(success && (data.length == 0 || abi.decode(data, (bool))), "ERC20_TRANSFER_FAILED");
}
}
receive() external payable {}
}
{
"compilationTarget": {
"GtrNftTopupV3.sol": "GtrNftTopupV3"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address","name":"nftContract","type":"address"},{"indexed":false,"internalType":"uint256","name":"nftId","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"percent","type":"uint8"}],"name":"CompoundUpdateRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address","name":"nftContract","type":"address"},{"indexed":false,"internalType":"uint256","name":"nftId","type":"uint256"},{"indexed":false,"internalType":"address","name":"tokenContract","type":"address"},{"indexed":false,"internalType":"uint8","name":"tokenDecimals","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"depositAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"taxAmount","type":"uint256"}],"name":"DepositRequested","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":false,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address","name":"nftContract","type":"address"},{"indexed":false,"internalType":"uint256","name":"nftId","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"percent","type":"uint8"}],"name":"TokenCompoundUpdateRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"address","name":"nftContract","type":"address"},{"indexed":false,"internalType":"uint256","name":"nftId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"WithdrawalRequested","type":"event"},{"inputs":[],"name":"_depositAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"_depositTaxPercent","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"_depositTaxPercentDivisor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"_nftTopUpsPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"_tokenTopUpsPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC721","name":"","type":"address"}],"name":"_validNftContracts","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"name":"_validTokenContracts","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC721","name":"nftContract","type":"address"},{"internalType":"uint256","name":"nftId","type":"uint256"},{"internalType":"contract IERC20","name":"tokenContract","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"a","type":"address"}],"name":"getTokenCompoundDestination","outputs":[{"internalType":"address","name":"nftContract","type":"address"},{"internalType":"uint16","name":"nftId","type":"uint16"}],"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":"pauseNftTopUps","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pauseTokenTopUps","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"divisor","type":"uint256"}],"name":"setDepositTaxPercent","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"setDepositWalletAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC721","name":"nftContract","type":"address"},{"internalType":"uint16","name":"nftId","type":"uint16"}],"name":"setTokenCompoundDestination","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC721","name":"contractAddress","type":"address"},{"internalType":"bool","name":"status","type":"bool"}],"name":"setValidNftContractStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"contractAddress","type":"address"},{"internalType":"bool","name":"status","type":"bool"}],"name":"setValidTokenContractStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC721","name":"nftContract","type":"address"},{"internalType":"uint256","name":"nftId","type":"uint256"},{"internalType":"uint8","name":"percent","type":"uint8"}],"name":"updateCompoundPercent","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC721","name":"nftContract","type":"address"},{"internalType":"uint256","name":"nftId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"to","type":"address"}],"name":"withdrawToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]