// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol"; import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol"; /** * @title wRET – Wrapped Retro Rabitts (1 wRET per RET NFT deposited in the vault) * @notice Mint/Burn restricted to a designated Vault contract. * - EIP-2612 Permit for gasless approvals. * - Pausable circuit breaker (owner). * - Owner is your wallet; transfer to multisig later if desired. */ contract wRET is ERC20, ERC20Permit, Ownable2Step, Pausable { /// @dev Vault allowed to mint/burn (set after deploying the vault). address public vault; error NotVault(); constructor(address _owner) ERC20("Wrapped Retro Rabitts", "wRET") ERC20Permit("Wrapped Retro Rabitts") { _transferOwnership(_owner); } /// @notice One-time (or updatable) wiring to your RET vault. function setVault(address _vault) external onlyOwner { vault = _vault; } modifier onlyVault() { if (msg.sender != vault) revert NotVault(); _; } /// @dev 18 decimals for DEX friendliness. function decimals() public pure override returns (uint8) { return 18; } // --- Safety switches --- function pause() external onlyOwner { _pause(); } function unpause() external onlyOwner { _unpause(); } // --- Vault-controlled supply --- function mint(address to, uint256 amount) external onlyVault whenNotPaused { _mint(to, amount); } function burn(address from, uint256 amount) external onlyVault whenNotPaused { _burn(from, amount); } } # Use your Base RPC (e.g., base-mainnet) configured in hardhat.config.ts npx hardhat run scripts/deploy_wret.ts --network base npx hardhat verify --network base 0xf165bf1090c83c69d47234aa9fbd62100db0d396 // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; /* * wRET (Fixed, Guarded) * - Total supply: 1,000,000 wRET (18 decimals), minted to owner. * - TRANSFERS ARE RESTRICTED: only addresses with TRANSFER_ROLE may send/receive. * => Grant role to your treasury and the MarketMaker so ALL movement is via MarketMaker. * - Price is handled here; the MarketMaker enforces 32.45 ETH/wRET. */ import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol"; import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol"; contract wRET is ERC20, ERC20Permit, Ownable2Step, AccessControl, Pausable { bytes32 public constant TRANSFER_ROLE = keccak256("TRANSFER_ROLE"); address public immutable RET_NFT; uint256 public constant FIXED_SUPPLY = 1_000_000 * 1e18; constructor(address owner_, address retNftAddress) ERC20("Wrapped Retro Rabitts", "wRET") ERC20Permit("Wrapped Retro Rabitts") { require(owner_ != address(0), "owner=0"); require(retNftAddress != address(0), "ret=0"); RET_NFT = retNftAddress; _transferOwnership(owner_); _grantRole(DEFAULT_ADMIN_ROLE, owner_); _grantRole(TRANSFER_ROLE, owner_); // owner can move initially _mint(owner_, FIXED_SUPPLY); } function pause() external onlyOwner { _pause(); } function unpause() external onlyOwner { _unpause(); } function decimals() public pure override returns (uint8) { return 18; } // Admin: grant/revoke transfer permission (e.g., to MarketMaker & treasury) function grantTransfer(address a) external onlyRole(DEFAULT_ADMIN_ROLE) { _grantRole(TRANSFER_ROLE, a); } function revokeTransfer(address a) external onlyRole(DEFAULT_ADMIN_ROLE) { _revokeRole(TRANSFER_ROLE, a); } // Gate all token movements (mint/burn are not used beyond constructor) function _update(address from, address to, uint256 amount) internal override(ERC20) { if (paused()) revert("paused"); // Mint (from == 0) only happened at constructor; Burn (to == 0) not used later. if (from != address(0)) { require(hasRole(TRANSFER_ROLE, from), "from !transferRole"); } if (to != address(0)) { require(hasRole(TRANSFER_ROLE, to), "to !transferRole"); } super._update(from, to, amount); } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; /* * MarketMaker for wRET @ 32.45 ETH per wRET (fixed, enforced) * - Users buy: send ETH -> get wRET at exact price * - Users sell: send wRET -> get ETH at exact price * - Contract must hold enough wRET & ETH liquidity for operations * - Owner can pause, withdraw ETH, and manage inventory */ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; contract wRETMarketMaker is Ownable2Step, Pausable, ReentrancyGuard { using SafeERC20 for IERC20; IERC20 public immutable WRET; // Hard-enforced price: 32.45 ETH per 1.0 wRET uint256 public constant PRICE_WEI_PER_WRET = 32_450000000000000000; // 32.45 ether event Bought(address indexed buyer, uint256 ethIn, uint256 wretOut); event Sold(address indexed seller, uint256 wretIn, uint256 ethOut); event SweepETH(address indexed to, uint256 amount); event Funded(address indexed from, uint256 wretIn, uint256 ethIn); constructor(address owner_, address wret) { require(owner_ != address(0), "owner=0"); require(wret != address(0), "wret=0"); _transferOwnership(owner_); WRET = IERC20(wret); } receive() external payable {} function pause() external onlyOwner { _pause(); } function unpause() external onlyOwner { _unpause(); } /// @notice Owner can fund the contract with wRET &/or ETH liquidity function fund(uint256 wretAmount) external payable onlyOwner { if (wretAmount > 0) { WRET.safeTransferFrom(msg.sender, address(this), wretAmount); } emit Funded(msg.sender, wretAmount, msg.value); } /// @notice Owner can sweep excess ETH function sweepETH(address payable to, uint256 amount) external onlyOwner { require(to != address(0), "to=0"); (bool ok,) = to.call{value: amount}(""); require(ok, "eth xfer fail"); emit SweepETH(to, amount); } /// @notice Buy wRET by sending ETH at fixed price (32.45 ETH/wRET) function buy() external payable nonReentrant whenNotPaused { require(msg.value > 0, "no ETH"); // wRET out = ETH_in / price uint256 wretOut = (msg.value * 1e18) / PRICE_WEI_PER_WRET; // 18-decimal wRET require(WRET.balanceOf(address(this)) >= wretOut, "insufficient wRET liquidity"); WRET.safeTransfer(msg.sender, wretOut); emit Bought(msg.sender, msg.value, wretOut); } /// @notice Sell wRET and receive ETH at fixed price function sell(uint256 wretAmount) external nonReentrant whenNotPaused { require(wretAmount > 0, "no wRET"); // ETH out = wRET_in * price uint256 ethOut = (wretAmount * PRICE_WEI_PER_WRET) / 1e18; require(address(this).balance >= ethOut, "insufficient ETH liquidity"); WRET.safeTransferFrom(msg.sender, address(this), wretAmount); (bool ok,) = payable(msg.sender).call{value: ethOut}(""); require(ok, "eth xfer fail"); emit Sold(msg.sender, wretAmount, ethOut); } }