SpinOrSpinToken
0x776853604648864acc45fd9658a4afefae6d7c77
Verification
Verified
v0.8.28+commit.7893614a
Type
Contract
6,163 bytes
ABI entries
86
32 read · 15 write
License
mit
Contract information
- Address
- 0x776853604648864acc45fd9658a4afefae6d7c77
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.28+commit.7893614a
- Optimization
- Enabled
- Creator
- 0x7ADe374118…51EF9A4553
- Creation tx
- 0x332c67c98f…f9b3567a6a
Token
- Name
- Spin or Spin
- Symbol
- SOS
- Decimals
- 18
- Holders
- 91
- Market
- View token page →
Read contract (32)
LIQUIDITY_RESERVE_SUPPLY() → uint256
MAX_BULK_SPINS() → uint256
ONE_TOKEN() → uint256
PUBLIC_SPIN_SUPPLY_CAP() → uint256
REWARD_BASE() → uint256
REWARD_BIG_WIN() → uint256
REWARD_DOUBLE_SEVEN() → uint256
REWARD_JACKPOT() → uint256
REWARD_TRIPLE() → uint256
SPIN_QUOTE_TYPEHASH() → bytes32
allowance(address, address) → uint256
balanceOf(address) → uint256
cap() → uint256
decimals() → uint8
eip712Domain() → bytes1, string, string, uint256, address, bytes32, uint256[]
liquidityReserveSupply() → uint256
mintOpen() → bool
mintPrice() → uint256
name() → string
owner() → address
publicSpinMinted() → uint256
publicSpinSupplyCap() → uint256
randomSpinNonce() → uint256
remainingPublicSpinSupply() → uint256
remainingSupply() → uint256
reserveMinted() → bool
reserveTreasury() → address
rewardForTier(uint8) → uint256
symbol() → string
totalSupply() → uint256
trustedSigner() → address
usedNonces(bytes32) → bool
Events (11)
ApprovalEIP712DomainChangedLiquidityReserveMintedMintOpenChangedMintPriceChangedOwnershipTransferredRandomSpinBatchMintedSpinMintedTransferTreasuryWithdrawalTrustedSignerChanged
ABI
[
{
"inputs": [
{
"internalType": "string",
"name": "tokenName",
"type": "string"
},
{
"internalType": "string",
"name": "tokenSymbol",
"type": "string"
},
{
"internalType": "uint256",
"name": "maxSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "initialMintPrice",
"type": "uint256"
},
{
"internalType": "address",
"name": "initialSigner",
"type": "address"
},
{
"internalType": "address",
"name": "initialOwner",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "BadSignature",
"type": "error"
},
{
"inputs": [],
"name": "BadTier",
"type": "error"
},
{
"inputs": [],
"name": "BulkArrayLengthMismatch",
"type": "error"
},
{
"inputs": [],
"name": "DeadlineExpired",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "increasedSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "cap",
"type": "uint256"
}
],
"name": "ERC20ExceededCap",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "allowance",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "needed",
"type": "uint256"
}
],
"name": "ERC20InsufficientAllowance",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"internalType": "uint256",
"name": "balance",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "needed",
"type": "uint256"
}
],
"name": "ERC20InsufficientBalance",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "approver",
"type": "address"
}
],
"name": "ERC20InvalidApprover",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "cap",
"type": "uint256"
}
],
"name": "ERC20InvalidCap",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "receiver",
"type": "address"
}
],
"name": "ERC20InvalidReceiver",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "ERC20InvalidSender",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "ERC20InvalidSpender",
"type": "error"
},
{
"inputs": [],
"name": "InvalidBulkSpinCount",
"type": "error"
},
{
"inputs": [],
"name": "InvalidShortString",
"type": "error"
},
{
"inputs": [],
"name": "InvalidWithdrawAmount",
"type": "error"
},
{
"inputs": [],
"name": "MintClosed",
"type": "error"
},
{
"inputs": [],
"name": "NonceAlreadyUsed",
"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": "PublicSpinSupplyExceeded",
"type": "error"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [],
"name": "ReserveAlreadyMinted",
"type": "error"
},
{
"inputs": [],
"name": "SignedSpinDeprecated",
"type": "error"
},
{
"inputs": [
{
"internalType": "string",
"name": "str",
"type": "string"
}
],
"name": "StringTooLong",
"type": "error"
},
{
"inputs": [],
"name": "WithdrawFailed",
"type": "error"
},
{
"inputs": [],
"name": "WrongMintPrice",
"type": "error"
},
{
"inputs": [],
"name": "ZeroAddress",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [],
"name": "EIP712DomainChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "treasury",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "totalSupplyAfter",
"type": "uint256"
}
],
"name": "LiquidityReserveMinted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bool",
"name": "open",
"type": "bool"
}
],
"name": "MintOpenChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "oldPrice",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "newPrice",
"type": "uint256"
}
],
"name": "MintPriceChanged",
"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": "address",
"name": "minter",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "spinCount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "totalRewardAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "paidAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "totalSupplyAfter",
"type": "uint256"
}
],
"name": "RandomSpinBatchMinted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "minter",
"type": "address"
},
{
"indexed": true,
"internalType": "uint8",
"name": "tier",
"type": "uint8"
},
{
"indexed": false,
"internalType": "uint256",
"name": "rewardAmount",
"type": "uint256"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "nonce",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "uint256",
"name": "paidAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "totalSupplyAfter",
"type": "uint256"
}
],
"name": "SpinMinted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "TreasuryWithdrawal",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "oldSigner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newSigner",
"type": "address"
}
],
"name": "TrustedSignerChanged",
"type": "event"
},
{
"inputs": [],
"name": "LIQUIDITY_RESERVE_SUPPLY",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_BULK_SPINS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "ONE_TOKEN",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "PUBLIC_SPIN_SUPPLY_CAP",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "REWARD_BASE",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "REWARD_BIG_WIN",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "REWARD_DOUBLE_SEVEN",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "REWARD_JACKPOT",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "REWARD_TRIPLE",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "SPIN_QUOTE_TYPEHASH",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint8[]",
"name": "",
"type": "uint8[]"
},
{
"internalType": "bytes32[]",
"name": "",
"type": "bytes32[]"
},
{
"internalType": "uint256[]",
"name": "",
"type": "uint256[]"
},
{
"internalType": "bytes[]",
"name": "",
"type": "bytes[]"
}
],
"name": "bulkSpinMint",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "cap",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "decimals",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "eip712Domain",
"outputs": [
{
"internalType": "bytes1",
"name": "fields",
"type": "bytes1"
},
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "version",
"type": "string"
},
{
"internalType": "uint256",
"name": "chainId",
"type": "uint256"
},
{
"internalType": "address",
"name": "verifyingContract",
"type": "address"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
},
{
"internalType": "uint256[]",
"name": "extensions",
"type": "uint256[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "liquidityReserveSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "mintOpen",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "mintPrice",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "publicSpinMinted",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "publicSpinSupplyCap",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "randomSpinNonce",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "remainingPublicSpinSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "remainingSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "treasury",
"type": "address"
}
],
"name": "reserveMint",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "reserveMinted",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "reserveTreasury",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint8",
"name": "tier",
"type": "uint8"
}
],
"name": "rewardForTier",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "bool",
"name": "open",
"type": "bool"
}
],
"name": "setMintOpen",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "newPrice",
"type": "uint256"
}
],
"name": "setMintPrice",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newSigner",
"type": "address"
}
],
"name": "setTrustedSigner",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"name": "spinMint",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint8",
"name": "count",
"type": "uint8"
}
],
"name": "spinMintRandom",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "trustedSigner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "usedNonces",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "withdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address payable",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "withdrawAmountTo",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address payable",
"name": "to",
"type": "address"
}
],
"name": "withdrawTo",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC20Capped} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
contract SpinOrSpinToken is ERC20, ERC20Capped, Ownable, ReentrancyGuard, EIP712 {
using ECDSA for bytes32;
uint256 public constant ONE_TOKEN = 1 ether;
uint256 public constant REWARD_BASE = 1_000 ether;
uint256 public constant REWARD_DOUBLE_SEVEN = 3_000 ether;
uint256 public constant REWARD_TRIPLE = 5_000 ether;
uint256 public constant REWARD_JACKPOT = 10_000 ether;
uint256 public constant REWARD_BIG_WIN = 20_000 ether;
uint256 public constant LIQUIDITY_RESERVE_SUPPLY = 20_000_000 ether;
uint256 public constant PUBLIC_SPIN_SUPPLY_CAP = 80_000_000 ether;
uint256 public constant MAX_BULK_SPINS = 20;
uint256 private constant ROLL_DENOMINATOR = 1_000_000;
bytes32 public constant SPIN_QUOTE_TYPEHASH = keccak256(
"SpinQuote(address minter,uint8 tier,bytes32 nonce,uint256 deadline)"
);
uint256 public mintPrice;
bool public mintOpen;
address public trustedSigner;
bool public reserveMinted;
address public reserveTreasury;
uint256 public publicSpinMinted;
uint256 public randomSpinNonce;
mapping(bytes32 => bool) public usedNonces;
event MintOpenChanged(bool indexed open);
event MintPriceChanged(uint256 oldPrice, uint256 newPrice);
event TrustedSignerChanged(address indexed oldSigner, address indexed newSigner);
event LiquidityReserveMinted(address indexed treasury, uint256 amount, uint256 totalSupplyAfter);
event SpinMinted(
address indexed minter,
uint8 indexed tier,
uint256 rewardAmount,
bytes32 indexed nonce,
uint256 paidAmount,
uint256 totalSupplyAfter
);
event RandomSpinBatchMinted(
address indexed minter,
uint256 spinCount,
uint256 totalRewardAmount,
uint256 paidAmount,
uint256 totalSupplyAfter
);
event TreasuryWithdrawal(address indexed to, uint256 amount);
error MintClosed();
error WrongMintPrice();
error BadTier();
error DeadlineExpired();
error NonceAlreadyUsed();
error BadSignature();
error ZeroAddress();
error WithdrawFailed();
error InvalidWithdrawAmount();
error ReserveAlreadyMinted();
error PublicSpinSupplyExceeded();
error InvalidBulkSpinCount();
error BulkArrayLengthMismatch();
error SignedSpinDeprecated();
constructor(
string memory tokenName,
string memory tokenSymbol,
uint256 maxSupply,
uint256 initialMintPrice,
address initialSigner,
address initialOwner
)
ERC20(tokenName, tokenSymbol)
ERC20Capped(maxSupply)
Ownable(initialOwner)
EIP712("Spin or Spin", "1")
{
if (initialOwner == address(0) || initialSigner == address(0)) revert ZeroAddress();
mintPrice = initialMintPrice;
trustedSigner = initialSigner;
}
/// @notice Legacy signed spin is intentionally disabled in v14.
/// @dev Signed quotes reveal the reward before the wallet tx is approved. Use spinMintRandom instead.
function spinMint(uint8, bytes32, uint256, bytes calldata)
external
payable
nonReentrant
{
revert SignedSpinDeprecated();
}
/// @notice Legacy signed bulk spin is intentionally disabled in v14.
/// @dev Signed quotes reveal the reward before the wallet tx is approved. Use spinMintRandom instead.
function bulkSpinMint(
uint8[] calldata,
bytes32[] calldata,
uint256[] calldata,
bytes[] calldata
)
external
payable
nonReentrant
{
revert SignedSpinDeprecated();
}
/// @notice One transaction random spin mint. The result is generated inside the transaction,
/// so the website cannot show the reward before the user signs/sends the tx.
function spinMintRandom(uint8 count) external payable nonReentrant {
if (!mintOpen) revert MintClosed();
if (count == 0 || count > MAX_BULK_SPINS) revert InvalidBulkSpinCount();
if (msg.value != mintPrice * uint256(count)) revert WrongMintPrice();
uint256 totalReward;
for (uint256 i = 0; i < count; i++) {
uint256 localNonce = randomSpinNonce++;
bytes32 seed = keccak256(
abi.encodePacked(
block.prevrandao,
blockhash(block.number - 1),
block.timestamp,
msg.sender,
address(this),
totalSupply(),
localNonce,
i
)
);
uint8 tier = _tierFromRoll(uint256(seed) % ROLL_DENOMINATOR);
uint256 reward = rewardForTier(tier);
if (publicSpinMinted + reward > PUBLIC_SPIN_SUPPLY_CAP) revert PublicSpinSupplyExceeded();
bytes32 eventNonce = keccak256(abi.encodePacked("SOS_RANDOM_SPIN", msg.sender, localNonce, i, seed));
publicSpinMinted += reward;
totalReward += reward;
_mint(msg.sender, reward);
emit SpinMinted(msg.sender, tier, reward, eventNonce, mintPrice, totalSupply());
}
emit RandomSpinBatchMinted(msg.sender, count, totalReward, msg.value, totalSupply());
}
function _consumeSpinQuote(
address minter,
uint8 tier,
bytes32 nonce,
uint256 deadline,
bytes calldata signature,
uint256 paidAmount
)
internal
{
if (!mintOpen) revert MintClosed();
if (block.timestamp > deadline) revert DeadlineExpired();
if (usedNonces[nonce]) revert NonceAlreadyUsed();
uint256 reward = rewardForTier(tier);
if (publicSpinMinted + reward > PUBLIC_SPIN_SUPPLY_CAP) revert PublicSpinSupplyExceeded();
bytes32 structHash = keccak256(abi.encode(SPIN_QUOTE_TYPEHASH, minter, tier, nonce, deadline));
bytes32 digest = _hashTypedDataV4(structHash);
address recovered = ECDSA.recover(digest, signature);
if (recovered != trustedSigner) revert BadSignature();
usedNonces[nonce] = true;
publicSpinMinted += reward;
_mint(minter, reward);
emit SpinMinted(minter, tier, reward, nonce, paidAmount, totalSupply());
}
function reserveMint(address treasury) external onlyOwner nonReentrant {
if (treasury == address(0)) revert ZeroAddress();
if (reserveMinted) revert ReserveAlreadyMinted();
reserveMinted = true;
reserveTreasury = treasury;
_mint(treasury, LIQUIDITY_RESERVE_SUPPLY);
emit LiquidityReserveMinted(treasury, LIQUIDITY_RESERVE_SUPPLY, totalSupply());
}
function rewardForTier(uint8 tier) public pure returns (uint256) {
if (tier == 0) return REWARD_BASE;
if (tier == 1) return REWARD_DOUBLE_SEVEN;
if (tier == 2) return REWARD_TRIPLE;
if (tier == 3) return REWARD_JACKPOT;
if (tier == 4) return REWARD_BIG_WIN;
revert BadTier();
}
function _tierFromRoll(uint256 roll) internal pure returns (uint8) {
if (roll < 900_000) return 0;
if (roll < 960_000) return 1;
if (roll < 990_000) return 2;
if (roll < 999_000) return 3;
return 4;
}
function remainingSupply() external view returns (uint256) {
return cap() - totalSupply();
}
function remainingPublicSpinSupply() external view returns (uint256) {
return PUBLIC_SPIN_SUPPLY_CAP - publicSpinMinted;
}
function liquidityReserveSupply() external pure returns (uint256) {
return LIQUIDITY_RESERVE_SUPPLY;
}
function publicSpinSupplyCap() external pure returns (uint256) {
return PUBLIC_SPIN_SUPPLY_CAP;
}
function setMintOpen(bool open) external onlyOwner {
mintOpen = open;
emit MintOpenChanged(open);
}
function setMintPrice(uint256 newPrice) external onlyOwner {
uint256 old = mintPrice;
mintPrice = newPrice;
emit MintPriceChanged(old, newPrice);
}
function setTrustedSigner(address newSigner) external onlyOwner {
if (newSigner == address(0)) revert ZeroAddress();
address old = trustedSigner;
trustedSigner = newSigner;
emit TrustedSignerChanged(old, newSigner);
}
function withdraw() external onlyOwner nonReentrant {
_withdrawAmountTo(owner(), address(this).balance);
}
function withdrawTo(address payable to) external onlyOwner nonReentrant {
if (to == address(0)) revert ZeroAddress();
_withdrawAmountTo(to, address(this).balance);
}
function withdrawAmountTo(address payable to, uint256 amount) external onlyOwner nonReentrant {
if (to == address(0)) revert ZeroAddress();
if (amount == 0 || amount > address(this).balance) revert InvalidWithdrawAmount();
_withdrawAmountTo(to, amount);
}
function _withdrawAmountTo(address to, uint256 amount) internal {
(bool ok,) = payable(to).call{value: amount}("");
if (!ok) revert WithdrawFailed();
emit TreasuryWithdrawal(to, amount);
}
function _update(address from, address to, uint256 value) internal override(ERC20, ERC20Capped) {
super._update(from, to, value);
}
}
Chain explorer6010msChain node81ms