// File: @openzeppelin/contracts/utils/Context.sol
// 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;
}
}
// File: @openzeppelin/contracts/access/Ownable.sol
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
/**
* @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);
}
}
// File: @openzeppelin/contracts/utils/ReentrancyGuard.sol
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
// File: NeverHaveIEver.sol
pragma solidity ^0.8.26;
contract NeverHaveIEver is Ownable, ReentrancyGuard {
// Price constants
uint256 public constant INITIAL_PRICE_WEI = 630_000_000_000_000; // 0.00063 ETH = ~$1 at $1577.10/ETH
uint256 public constant PRICE_DECIMALS = 18;
uint256 public constant PROTOCOL_FEE_BASIS_POINTS = 2000; // 20% in basis points (2000/10000)
struct Prompt {
string content;
address author;
uint256 createdAt;
uint256 expiresAt;
uint256 totalConfessions;
uint256 totalPayout;
uint256 totalReveals;
bool isActive;
bool hasActiveReveals; // Tracks if anyone has paid to reveal during active period
}
struct Confession {
address user;
bool hasRevealed;
}
mapping(uint256 => Prompt) public prompts;
mapping(uint256 => mapping(address => Confession)) public confessions;
mapping(address => uint256) public authorEarnings;
uint256 public nextPromptId;
uint256 public priceInWei;
uint256 public protocolFeeBasisPoints; // Basis points (e.g., 2000 = 20%)
event PromptCreated(uint256 indexed promptId, address indexed author, string content, uint256 expiresAt);
event ConfessionRevealed(uint256 indexed promptId, address indexed user, uint256 amount);
event PriceUpdated(uint256 oldPrice, uint256 newPrice);
event EarningsWithdrawn(address indexed author, uint256 amount);
event PayoutDistributed(
uint256 indexed promptId,
address indexed author,
uint256 authorAmount,
uint256 protocolAmount
);
error PromptNotFound();
error PromptExpired();
error PromptNotActive();
error AlreadyRevealed();
error InsufficientPayment();
error NoEarningsToWithdraw();
error TransferFailed();
error NoActiveReveals(); // New error for expired prompts without active reveals
error InvalidProtocolFee();
constructor() Ownable(msg.sender) {
priceInWei = INITIAL_PRICE_WEI;
protocolFeeBasisPoints = PROTOCOL_FEE_BASIS_POINTS;
}
function createPrompt(string memory content, uint256 durationInHours) external payable nonReentrant {
if (msg.value < priceInWei) revert InsufficientPayment();
uint256 promptId = nextPromptId++;
uint256 expiresAt = block.timestamp + (durationInHours * 1 hours);
prompts[promptId] = Prompt({
content: content,
author: msg.sender,
createdAt: block.timestamp,
expiresAt: expiresAt,
totalConfessions: 0,
totalPayout: 0,
totalReveals: 0,
isActive: true,
hasActiveReveals: false
});
// 100% of creation fee goes to contract owner
authorEarnings[owner()] += msg.value;
emit PromptCreated(promptId, msg.sender, content, expiresAt);
emit PayoutDistributed(promptId, owner(), msg.value, 0); // 100% to protocol
}
function payToReveal(uint256 promptId) external payable nonReentrant {
Prompt storage prompt = prompts[promptId];
if (prompt.author == address(0)) revert PromptNotFound();
if (!prompt.isActive) revert PromptNotActive();
if (confessions[promptId][msg.sender].hasRevealed) revert AlreadyRevealed();
if (msg.value < priceInWei) revert InsufficientPayment();
// Check if prompt is expired and has no active reveals
if (block.timestamp > prompt.expiresAt && !prompt.hasActiveReveals) revert NoActiveReveals();
confessions[promptId][msg.sender] = Confession({
user: msg.sender,
hasRevealed: true
});
prompt.totalPayout += msg.value;
prompt.hasActiveReveals = true;
prompt.totalReveals++;
// Calculate and distribute earnings using configurable protocol fee
uint256 protocolShare = (msg.value * protocolFeeBasisPoints) / 10000;
uint256 authorShare = msg.value - protocolShare;
// Send ETH directly to author and protocol
(bool success1, ) = prompt.author.call{value: authorShare}("");
if (!success1) revert TransferFailed();
authorEarnings[owner()] += protocolShare;
emit ConfessionRevealed(promptId, msg.sender, msg.value);
emit PayoutDistributed(promptId, prompt.author, authorShare, protocolShare);
}
function withdrawEarnings() external nonReentrant {
uint256 amount = authorEarnings[msg.sender];
if (amount == 0) revert NoEarningsToWithdraw();
authorEarnings[msg.sender] = 0;
(bool success, ) = msg.sender.call{value: amount}("");
if (!success) revert TransferFailed();
emit EarningsWithdrawn(msg.sender, amount);
}
function updatePrice(uint256 newPriceInWei) external onlyOwner {
uint256 oldPrice = priceInWei;
priceInWei = newPriceInWei;
emit PriceUpdated(oldPrice, newPriceInWei);
}
function getPriceInEth() external view returns (uint256) {
return priceInWei;
}
function getPriceInUsd(uint256 ethPriceInUsd) external view returns (uint256) {
// Returns price in USD with 2 decimals (e.g., 100 = $1.00)
return (priceInWei * ethPriceInUsd) / (10 ** PRICE_DECIMALS);
}
function getPrompt(uint256 promptId) external view returns (
string memory content,
address author,
uint256 createdAt,
uint256 expiresAt,
uint256 totalConfessions,
uint256 totalPayout,
uint256 totalReveals,
bool isActive
) {
Prompt storage prompt = prompts[promptId];
return (
prompt.content,
prompt.author,
prompt.createdAt,
prompt.expiresAt,
prompt.totalConfessions,
prompt.totalPayout,
prompt.totalReveals,
prompt.isActive
);
}
function getConfession(uint256 promptId, address user) external view returns (bool hasRevealed) {
Confession storage confession = confessions[promptId][user];
return confession.hasRevealed;
}
function getAuthorEarnings(address author) external view returns (uint256) {
return authorEarnings[author];
}
function updateProtocolFee(uint256 newProtocolFeeBasisPoints) external onlyOwner {
if (newProtocolFeeBasisPoints > 10000) revert InvalidProtocolFee();
protocolFeeBasisPoints = newProtocolFeeBasisPoints;
}
receive() external payable {
revert("Direct ETH transfers not allowed");
}
}
{
"compilationTarget": {
"NeverHaveIEver.sol": "NeverHaveIEver"
},
"evmVersion": "cancun",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyRevealed","type":"error"},{"inputs":[],"name":"InsufficientPayment","type":"error"},{"inputs":[],"name":"InvalidProtocolFee","type":"error"},{"inputs":[],"name":"NoActiveReveals","type":"error"},{"inputs":[],"name":"NoEarningsToWithdraw","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"PromptExpired","type":"error"},{"inputs":[],"name":"PromptNotActive","type":"error"},{"inputs":[],"name":"PromptNotFound","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[],"name":"TransferFailed","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"promptId","type":"uint256"},{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ConfessionRevealed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"EarningsWithdrawn","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":true,"internalType":"uint256","name":"promptId","type":"uint256"},{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":false,"internalType":"uint256","name":"authorAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"protocolAmount","type":"uint256"}],"name":"PayoutDistributed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldPrice","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newPrice","type":"uint256"}],"name":"PriceUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"promptId","type":"uint256"},{"indexed":true,"internalType":"address","name":"author","type":"address"},{"indexed":false,"internalType":"string","name":"content","type":"string"},{"indexed":false,"internalType":"uint256","name":"expiresAt","type":"uint256"}],"name":"PromptCreated","type":"event"},{"inputs":[],"name":"INITIAL_PRICE_WEI","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PRICE_DECIMALS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PROTOCOL_FEE_BASIS_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"authorEarnings","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"confessions","outputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"bool","name":"hasRevealed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"content","type":"string"},{"internalType":"uint256","name":"durationInHours","type":"uint256"}],"name":"createPrompt","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"author","type":"address"}],"name":"getAuthorEarnings","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"promptId","type":"uint256"},{"internalType":"address","name":"user","type":"address"}],"name":"getConfession","outputs":[{"internalType":"bool","name":"hasRevealed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPriceInEth","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"ethPriceInUsd","type":"uint256"}],"name":"getPriceInUsd","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"promptId","type":"uint256"}],"name":"getPrompt","outputs":[{"internalType":"string","name":"content","type":"string"},{"internalType":"address","name":"author","type":"address"},{"internalType":"uint256","name":"createdAt","type":"uint256"},{"internalType":"uint256","name":"expiresAt","type":"uint256"},{"internalType":"uint256","name":"totalConfessions","type":"uint256"},{"internalType":"uint256","name":"totalPayout","type":"uint256"},{"internalType":"uint256","name":"totalReveals","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextPromptId","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":"uint256","name":"promptId","type":"uint256"}],"name":"payToReveal","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"priceInWei","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"prompts","outputs":[{"internalType":"string","name":"content","type":"string"},{"internalType":"address","name":"author","type":"address"},{"internalType":"uint256","name":"createdAt","type":"uint256"},{"internalType":"uint256","name":"expiresAt","type":"uint256"},{"internalType":"uint256","name":"totalConfessions","type":"uint256"},{"internalType":"uint256","name":"totalPayout","type":"uint256"},{"internalType":"uint256","name":"totalReveals","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"internalType":"bool","name":"hasActiveReveals","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"protocolFeeBasisPoints","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newPriceInWei","type":"uint256"}],"name":"updatePrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newProtocolFeeBasisPoints","type":"uint256"}],"name":"updateProtocolFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawEarnings","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]