// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// Add OpenZeppelin import
import "@openzeppelin/contracts/access/Ownable.sol";
interface IERC20 {
function transfer(address recipient, uint256 amount) external returns (bool);
}
interface IERC721 {
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool approved) external;
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
// Modify contract declaration to inherit Ownable
contract BasedSanta is Ownable {
// Remove santa address as it's handled by Ownable
// address public santa;
struct Present {
address tokenAddress;
uint256 amountOrTokenId;
bool isERC20; // True for ERC20, False for ERC721
bool sent; // Tracks whether the present has been sent
string description; // Added description field
}
mapping(uint256 => Present) public presents;
uint256 public presentCounter;
uint256 public nextPresentIndex; // Tracks the next unsent present
// Add mapping to track received addresses
mapping(address => bool) public hasReceivedPresent;
// Add mapping for allowlist
mapping(address => bool) public isAllowlisted;
// Track number of presents received per address
mapping(address => uint256) public presentsReceived;
// Add array to track allowlisted addresses
address[] public allowedAddresses;
event PresentCreated(uint256 presentId, address tokenAddress, uint256 amountOrTokenId, bool isERC20);
event PresentSent(uint256 presentId, address to);
event AddedToAllowlist(address indexed account);
// Add state variable for pause
bool public secondGiftsPaused;
// Add event for pause state changes
event SecondGiftsPauseStateChanged(bool isPaused);
constructor() Ownable(msg.sender) {
// Initialize allowlist with all addresses
address[4] memory allowlist = [
0x45db9d3457c2Cb05C4BFc7334a33ceE6e19d508F,
0x7606328c81514A19f70b3DA1535Fc819906d46Ab,
0xC14A874A16944A29843E860339E65d5ec44f8b30,
0xbD78783a26252bAf756e22f0DE764dfDcDa7733c
];
for(uint i = 0; i < allowlist.length; i++) {
isAllowlisted[allowlist[i]] = true;
}
}
function createERC20Present(address tokenAddress, uint256 amount, string memory description) external onlyOwner {
require(amount > 0, "Amount must be greater than zero");
uint256 amountInWei = amount * 10 ** 18;
presents[presentCounter] = Present({
tokenAddress: tokenAddress,
amountOrTokenId: amountInWei,
isERC20: true,
sent: false,
description: description
});
emit PresentCreated(presentCounter, tokenAddress, amountInWei, true);
presentCounter++;
}
function createERC721Present(address tokenAddress, uint256 tokenId, string memory description) external onlyOwner {
IERC721 nft = IERC721(tokenAddress);
require(nft.isApprovedForAll(msg.sender, address(this)), "Contract must be approved to transfer NFT");
presents[presentCounter] = Present({
tokenAddress: tokenAddress,
amountOrTokenId: tokenId,
isERC20: false,
sent: false,
description: description
});
emit PresentCreated(presentCounter, tokenAddress, tokenId, false);
presentCounter++;
}
function sendNextPresent(address to) external onlyOwner {
require(to != address(0), "Cannot send to the zero address");
require(nextPresentIndex < presentCounter, "No unsent presents available");
// Check presents received count with pause check
require(
presentsReceived[to] < 1 ||
(isAllowlisted[to] && presentsReceived[to] < 2 && !secondGiftsPaused),
"Address has reached present limit or second gifts are paused"
);
// Find the next unsent present
while (nextPresentIndex < presentCounter && presents[nextPresentIndex].sent) {
nextPresentIndex++;
}
require(nextPresentIndex < presentCounter, "No unsent presents available");
Present storage present = presents[nextPresentIndex];
if (present.isERC20) {
IERC20(present.tokenAddress).transfer(to, present.amountOrTokenId);
} else {
IERC721(present.tokenAddress).safeTransferFrom(address(this), to, present.amountOrTokenId);
}
present.sent = true;
presentsReceived[to]++; // Increment presents received count
emit PresentSent(nextPresentIndex, to);
nextPresentIndex++;
}
function getUnsentPresentsCount() external view returns (uint256) {
return presentCounter > nextPresentIndex ? presentCounter - nextPresentIndex : 0;
}
function getNextPresentDescription() external view returns (
string memory description
) {
require(nextPresentIndex < presentCounter, "No unsent presents available");
Present storage present = presents[nextPresentIndex];
// If the next present is already sent, there are no unsent presents
require(!present.sent, "No unsent presents available");
return present.description;
}
// Add withdrawal functions
function withdrawERC20(address tokenAddress, uint256 amount) external onlyOwner {
require(tokenAddress != address(0), "Invalid token address");
require(amount > 0, "Amount must be greater than zero");
IERC20(tokenAddress).transfer(owner(), amount);
}
function withdrawERC721(address tokenAddress, uint256 tokenId) external onlyOwner {
require(tokenAddress != address(0), "Invalid token address");
IERC721(tokenAddress).safeTransferFrom(address(this), owner(), tokenId);
}
// Optional: Add function to check if address can receive more presents
function canReceivePresent(address recipient) external view returns (bool) {
return presentsReceived[recipient] == 0 ||
(isAllowlisted[recipient] && presentsReceived[recipient] == 1 && !secondGiftsPaused);
}
// Add function to add addresses to allowlist
function addToAllowlist(address[] calldata addresses) external onlyOwner {
for(uint i = 0; i < addresses.length; i++) {
address account = addresses[i];
require(account != address(0), "Cannot add zero address");
if (!isAllowlisted[account]) { // Only if not already allowlisted
isAllowlisted[account] = true;
allowedAddresses.push(account);
emit AddedToAllowlist(account);
}
}
}
// Optional: Add function to remove addresses from allowlist
function removeFromAllowlist(address[] calldata addresses) external onlyOwner {
for(uint i = 0; i < addresses.length; i++) {
isAllowlisted[addresses[i]] = false;
}
}
// Add function to get all allowlisted addresses
function getAllowlist() external view returns (address[] memory) {
return allowedAddresses;
}
// Add pause/unpause functions
function pauseSecondGifts() external onlyOwner {
secondGiftsPaused = true;
emit SecondGiftsPauseStateChanged(true);
}
function unpauseSecondGifts() external onlyOwner {
secondGiftsPaused = false;
emit SecondGiftsPauseStateChanged(false);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
{
"compilationTarget": {
"contracts/BasedSanta.sol": "BasedSanta"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"}],"name":"AddedToAllowlist","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":"uint256","name":"presentId","type":"uint256"},{"indexed":false,"internalType":"address","name":"tokenAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountOrTokenId","type":"uint256"},{"indexed":false,"internalType":"bool","name":"isERC20","type":"bool"}],"name":"PresentCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"presentId","type":"uint256"},{"indexed":false,"internalType":"address","name":"to","type":"address"}],"name":"PresentSent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"isPaused","type":"bool"}],"name":"SecondGiftsPauseStateChanged","type":"event"},{"inputs":[{"internalType":"address[]","name":"addresses","type":"address[]"}],"name":"addToAllowlist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"allowedAddresses","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"}],"name":"canReceivePresent","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"description","type":"string"}],"name":"createERC20Present","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"string","name":"description","type":"string"}],"name":"createERC721Present","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getAllowlist","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getNextPresentDescription","outputs":[{"internalType":"string","name":"description","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getUnsentPresentsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"hasReceivedPresent","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isAllowlisted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextPresentIndex","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":"pauseSecondGifts","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"presentCounter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"presents","outputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"amountOrTokenId","type":"uint256"},{"internalType":"bool","name":"isERC20","type":"bool"},{"internalType":"bool","name":"sent","type":"bool"},{"internalType":"string","name":"description","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"presentsReceived","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"addresses","type":"address[]"}],"name":"removeFromAllowlist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"secondGiftsPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"sendNextPresent","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpauseSecondGifts","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"withdrawERC721","outputs":[],"stateMutability":"nonpayable","type":"function"}]