SteelMineV2
0xb089d11432f219495a4278acd6446b7faefa2ba6
Verification
Verified
v0.8.33+commit.64118f21
Type
Contract
16,145 bytes
ABI entries
106
56 read · 22 write
License
none
Contract information
- Address
- 0xb089d11432f219495a4278acd6446b7faefa2ba6
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.33+commit.64118f21
- Optimization
- Enabled
- Creator
- 0x30b807ca11…D08dcAA382
- Creation tx
- 0xf685ceb4fc…5a08416944
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (56)
BPS() → uint256
SQUARES() → uint256
autoBalance(address) → uint256
autoCount() → uint256
autoFee() → uint256
autoInfo(address) → uint256, uint256, uint256, bool, uint256, bool
autoList(uint256) → address
autoReady(address) → bool
autoReadyList() → address[]
autoSquares(address) → uint8[]
betOf(uint256, uint256, address) → uint256
bettingDuration() → uint256
bettingEndsAt(uint256) → uint256
bettingLeft() → uint256
carryEth() → uint256
carrySteel() → uint256
currentRound() → uint256
devAddr() → address
devBps() → uint256
drand() → address
drandGenesis() → uint256
drandPeriod() → uint256
drandRoundFor(uint256) → uint64
drandSafety() → uint64
emittedTotal() → uint256
genesis() → uint256
getRound(uint256) → tuple
globalIndex() → uint256
jackpotFeeBps() → uint256
jackpotOdds() → uint256
keeperFeeBps() → uint256
minCommit() → uint256
miners(address) → uint256, uint256, uint256
miningCap() → uint256
motherlodeBps() → uint256
motherlodeEth() → uint256
motherlodeSteel() → uint256
owner() → address
pendingEth(address) → uint256
protocolFeeBps() → uint256
refinable(address) → uint256, uint256, uint256
refiningFeeBps() → uint256
roundDuration() → uint256
roundEndsAt(uint256) → uint256
roundShare(uint256, address) → uint256, uint256
rounds(uint256) → uint128, uint128, uint128, uint128, uint64, uint8, bool, bool, bool, bool, address
seeded() → bool
squarePot(uint256, uint256) → uint256
steel() → address
steelPerRound() → uint256
timeLeft() → uint256
totalUnrefined() → uint256
userRoundTotal(uint256, address) → uint256
veSteel() → address
winnerAt(uint256, uint256) → address
winnersCount(uint256) → uint256
Events (11)
AutoCancelledAutoExecutedAutoSubscribedAutoTopUpClaimedCommittedEthClaimedOwnershipTransferredParamsChangedRefinedResolved
ABI
[
{
"inputs": [
{
"internalType": "contract ISteelToken",
"name": "_steel",
"type": "address"
},
{
"internalType": "contract IDrandOracle",
"name": "_drand",
"type": "address"
},
{
"internalType": "address",
"name": "_devAddr",
"type": "address"
},
{
"internalType": "uint256",
"name": "_squares",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_miningCap",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_roundDuration",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_drandGenesis",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_drandPeriod",
"type": "uint256"
},
{
"internalType": "uint64",
"name": "_drandSafety",
"type": "uint64"
},
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "AlreadyClaimed",
"type": "error"
},
{
"inputs": [],
"name": "AlreadyResolved",
"type": "error"
},
{
"inputs": [],
"name": "BadParam",
"type": "error"
},
{
"inputs": [],
"name": "BadSquare",
"type": "error"
},
{
"inputs": [],
"name": "BettingClosed",
"type": "error"
},
{
"inputs": [],
"name": "NoSquares",
"type": "error"
},
{
"inputs": [],
"name": "NoWinner",
"type": "error"
},
{
"inputs": [],
"name": "NotReady",
"type": "error"
},
{
"inputs": [],
"name": "NotResolved",
"type": "error"
},
{
"inputs": [],
"name": "NotStarted",
"type": "error"
},
{
"inputs": [],
"name": "NothingToClaim",
"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": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [],
"name": "RoundOpen",
"type": "error"
},
{
"inputs": [],
"name": "TooSmall",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "refunded",
"type": "uint256"
}
],
"name": "AutoCancelled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "round",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "spent",
"type": "uint256"
}
],
"name": "AutoExecuted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amountPerSquare",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "rounds",
"type": "uint256"
}
],
"name": "AutoSubscribed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "AutoTopUp",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "roundId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ethWon",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "steelUnrefined",
"type": "uint256"
}
],
"name": "Claimed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "roundId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint8[]",
"name": "squares",
"type": "uint8[]"
}
],
"name": "Committed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "EthClaimed",
"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": [],
"name": "ParamsChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "steelOut",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "fee",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "ethOut",
"type": "uint256"
}
],
"name": "Refined",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "roundId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint8",
"name": "winner",
"type": "uint8"
},
{
"indexed": false,
"internalType": "bool",
"name": "hasWinner",
"type": "bool"
},
{
"indexed": false,
"internalType": "bool",
"name": "solo",
"type": "bool"
},
{
"indexed": false,
"internalType": "bool",
"name": "jackpot",
"type": "bool"
},
{
"indexed": false,
"internalType": "uint256",
"name": "pot",
"type": "uint256"
}
],
"name": "Resolved",
"type": "event"
},
{
"inputs": [],
"name": "BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "SQUARES",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "autoBalance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "autoCancel",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "autoCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
}
],
"name": "autoExecute",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "users",
"type": "address[]"
}
],
"name": "autoExecuteMany",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "autoFee",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "u",
"type": "address"
}
],
"name": "autoInfo",
"outputs": [
{
"internalType": "uint256",
"name": "amountPerSquare",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "roundsLeft",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "lastRound",
"type": "uint256"
},
{
"internalType": "bool",
"name": "active",
"type": "bool"
},
{
"internalType": "uint256",
"name": "balance",
"type": "uint256"
},
{
"internalType": "bool",
"name": "reload",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "autoList",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "u",
"type": "address"
}
],
"name": "autoReady",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "autoReadyList",
"outputs": [
{
"internalType": "address[]",
"name": "ready",
"type": "address[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "u",
"type": "address"
}
],
"name": "autoSquares",
"outputs": [
{
"internalType": "uint8[]",
"name": "",
"type": "uint8[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint8[]",
"name": "squares",
"type": "uint8[]"
},
{
"internalType": "uint256",
"name": "amountPerSquare",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "roundsCount",
"type": "uint256"
},
{
"internalType": "bool",
"name": "reload",
"type": "bool"
}
],
"name": "autoSubscribe",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "autoTopUp",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "sq",
"type": "uint256"
},
{
"internalType": "address",
"name": "u",
"type": "address"
}
],
"name": "betOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "bettingDuration",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "bettingEndsAt",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "bettingLeft",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "carryEth",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "carrySteel",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "claimAll",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "claimEth",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint8[]",
"name": "squares",
"type": "uint8[]"
}
],
"name": "commit",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "bettor",
"type": "address"
},
{
"internalType": "uint8[]",
"name": "squares",
"type": "uint8[]"
}
],
"name": "commitFor",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "currentRound",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "devAddr",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "devBps",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "drand",
"outputs": [
{
"internalType": "contract IDrandOracle",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "drandGenesis",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "drandPeriod",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "drandRoundFor",
"outputs": [
{
"internalType": "uint64",
"name": "",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "drandSafety",
"outputs": [
{
"internalType": "uint64",
"name": "",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "emittedTotal",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "finalizeSeed",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "genesis",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "getRound",
"outputs": [
{
"components": [
{
"internalType": "uint128",
"name": "totalPot",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "winnersPool",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "winnerPot",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "steelForWinners",
"type": "uint128"
},
{
"internalType": "uint64",
"name": "drandRound",
"type": "uint64"
},
{
"internalType": "uint8",
"name": "winner",
"type": "uint8"
},
{
"internalType": "bool",
"name": "resolved",
"type": "bool"
},
{
"internalType": "bool",
"name": "hasWinner",
"type": "bool"
},
{
"internalType": "bool",
"name": "solo",
"type": "bool"
},
{
"internalType": "bool",
"name": "jackpotHit",
"type": "bool"
},
{
"internalType": "address",
"name": "soloWinner",
"type": "address"
}
],
"internalType": "struct SteelMineV2.Round",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "globalIndex",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "jackpotFeeBps",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "jackpotOdds",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "keeperFeeBps",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "minCommit",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "miners",
"outputs": [
{
"internalType": "uint256",
"name": "unrefined",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "indexSnapshot",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "refinedAccrued",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "miningCap",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "motherlodeBps",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "motherlodeEth",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "motherlodeSteel",
"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": "address",
"name": "",
"type": "address"
}
],
"name": "pendingEth",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "protocolFeeBps",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "u",
"type": "address"
}
],
"name": "refinable",
"outputs": [
{
"internalType": "uint256",
"name": "gross",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "fee",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "net",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "refiningFeeBps",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "rid",
"type": "uint256"
}
],
"name": "resolveRound",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "roundDuration",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "roundEndsAt",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"internalType": "address",
"name": "u",
"type": "address"
}
],
"name": "roundShare",
"outputs": [
{
"internalType": "uint256",
"name": "ethWon",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "steelWon",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "rounds",
"outputs": [
{
"internalType": "uint128",
"name": "totalPot",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "winnersPool",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "winnerPot",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "steelForWinners",
"type": "uint128"
},
{
"internalType": "uint64",
"name": "drandRound",
"type": "uint64"
},
{
"internalType": "uint8",
"name": "winner",
"type": "uint8"
},
{
"internalType": "bool",
"name": "resolved",
"type": "bool"
},
{
"internalType": "bool",
"name": "hasWinner",
"type": "bool"
},
{
"internalType": "bool",
"name": "solo",
"type": "bool"
},
{
"internalType": "bool",
"name": "jackpotHit",
"type": "bool"
},
{
"internalType": "address",
"name": "soloWinner",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "steel",
"type": "uint256"
}
],
"name": "seedMotherlode",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "users",
"type": "address[]"
},
{
"internalType": "uint256[]",
"name": "amounts",
"type": "uint256[]"
}
],
"name": "seedUnrefined",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "seeded",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "f",
"type": "uint256"
}
],
"name": "setAutoFee",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_bettingDuration",
"type": "uint256"
}
],
"name": "setBettingWindow",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "d",
"type": "address"
}
],
"name": "setDevAddr",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract IDrandOracle",
"name": "d",
"type": "address"
}
],
"name": "setDrand",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_protocolFeeBps",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_jackpotFeeBps",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_steelPerRound",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_devBps",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_motherlodeBps",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_jackpotOdds",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_refiningFeeBps",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_keeperFeeBps",
"type": "uint256"
}
],
"name": "setEconomics",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "v",
"type": "uint256"
}
],
"name": "setMinCommit",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract IVeSteelV2",
"name": "v",
"type": "address"
}
],
"name": "setVeSteel",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "squarePot",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "steel",
"outputs": [
{
"internalType": "contract ISteelToken",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "steelPerRound",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "timeLeft",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalUnrefined",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"internalType": "address",
"name": "u",
"type": "address"
}
],
"name": "userRoundTotal",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "veSteel",
"outputs": [
{
"internalType": "contract IVeSteelV2",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "i",
"type": "uint256"
}
],
"name": "winnerAt",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
}
],
"name": "winnersCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IDrandOracle} from "./interfaces/IDrandOracle.sol";
interface ISteelToken {
function mint(address to, uint256 amount) external;
}
interface IVeSteelV2 {
function notifyReward() external payable; // 8% ETH -> stakers
function creditDevLock(uint256 amount) external; // 8% STEEL -> dev permanent lock
}
/// @title SteelMineV2 — slvr.fun-style round mining lottery (full economics).
/// @notice 25-square grid, 60s rounds, drand winner. Per round:
/// ETH (10% fee of pot): 90% winners · 8% veSTEEL stakers · 2% motherlode.
/// STEEL (mint 1.12): 1.0 winners · 0.08 dev(veSTEEL lock) · 0.04 motherlode.
/// Motherlode (ETH+STEEL) pays out on a 1/625 roll to that round's winners.
/// ETH ALWAYS splits pro-rata by bet. Single-miner round (50%) only concentrates
/// that round's STEEL emission to ONE bet-weighted winner (the mining jackpot).
/// Winnings are UNREFINED STEEL; refining costs 10%, redistributed pro-rata to
/// the other unrefined holders (claim later, earn more).
contract SteelMineV2 is Ownable, ReentrancyGuard {
uint256 public constant BPS = 10_000;
uint256 internal constant WAD = 1e18;
// ── immutables ──
ISteelToken public immutable steel;
uint256 public immutable SQUARES;
uint256 public immutable miningCap;
uint256 public immutable genesis;
uint256 public immutable roundDuration;
uint256 public immutable drandGenesis;
uint256 public immutable drandPeriod;
uint64 public immutable drandSafety;
// ── wiring ──
IVeSteelV2 public veSteel;
IDrandOracle public drand;
address public devAddr;
// ── tunable ──
uint256 public protocolFeeBps = 1000; // 10% of pot
uint256 public jackpotFeeBps = 200; // 2% of pot -> motherlode ETH
uint256 public keeperFeeBps = 200; // 2% of pot -> devAddr, self-funds keeper gas (from the 10% tax)
uint256 public steelPerRound = 1e18; // base winner STEEL / round
uint256 public devBps = 800; // +8% of base -> dev lock
uint256 public motherlodeBps = 1000; // +10% of base -> motherlode STEEL (0.1/round)
uint256 public jackpotOdds = 625; // 1/625 payout roll
uint256 public refiningFeeBps = 1000; // 10% on refine
uint256 public minCommit = 1e14; // 0.0001 ETH / square
uint256 public bettingDuration; // betting closes this many secs into a round (reveal = rest)
// ── round state ──
struct Round {
uint128 totalPot;
uint128 winnersPool; // ETH to winners (90% + carry, + motherlode on hit)
uint128 winnerPot; // ETH bet on winning square
uint128 steelForWinners; // unrefined STEEL for winners (+ motherlode on hit)
uint64 drandRound;
uint8 winner;
bool resolved;
bool hasWinner;
bool solo; // single-miner round
bool jackpotHit;
address soloWinner;
}
mapping(uint256 => Round) public rounds;
mapping(uint256 => mapping(uint256 => uint256)) public squarePot; // round => sq => ETH
mapping(bytes32 => uint256) internal _bet; // (round,sq,user) => ETH
mapping(bytes32 => uint256) internal _userRound; // (round,user) => ETH
mapping(bytes32 => bool) internal _isBettor; // (round,sq,user)
mapping(uint256 => mapping(uint256 => address[])) internal _bettors; // round => sq => users
mapping(bytes32 => bool) internal _claimedRound; // (round,user) => claimed
uint256 public emittedTotal; // STEEL reserved against cap (1.12x basis)
uint256 public motherlodeEth;
uint256 public motherlodeSteel;
uint256 public carryEth; // no-winner ETH rolled forward
uint256 public carrySteel;
// ── payouts + refining ──
mapping(address => uint256) public pendingEth; // withdrawable ETH winnings
// ORE-style refining accumulator
struct Miner {
uint256 unrefined; // unminted STEEL
uint256 indexSnapshot;
uint256 refinedAccrued; // dividends earned from others' refine fees
}
mapping(address => Miner) public miners;
uint256 public globalIndex; // 1e18-scaled dividend index
uint256 public totalUnrefined;
// ── events ──
event Committed(uint256 indexed roundId, address indexed user, uint256 amount, uint8[] squares);
event Resolved(uint256 indexed roundId, uint8 winner, bool hasWinner, bool solo, bool jackpot, uint256 pot);
event Claimed(uint256 indexed roundId, address indexed user, uint256 ethWon, uint256 steelUnrefined);
event Refined(address indexed user, uint256 steelOut, uint256 fee, uint256 ethOut);
event EthClaimed(address indexed user, uint256 amount);
event ParamsChanged();
error BadSquare();
error NoSquares();
error TooSmall();
error NotStarted();
error RoundOpen();
error AlreadyResolved();
error NotResolved();
error NotReady();
error NoWinner();
error NothingToClaim();
error AlreadyClaimed();
error BettingClosed();
error BadParam();
constructor(
ISteelToken _steel,
IDrandOracle _drand,
address _devAddr,
uint256 _squares,
uint256 _miningCap,
uint256 _roundDuration,
uint256 _drandGenesis,
uint256 _drandPeriod,
uint64 _drandSafety,
address _owner
) Ownable(_owner) {
if (_squares == 0 || _squares > 256 || _devAddr == address(0)) revert BadParam();
if (_roundDuration < 10 || _drandPeriod == 0) revert BadParam();
steel = _steel;
drand = _drand;
devAddr = _devAddr;
SQUARES = _squares;
miningCap = _miningCap;
roundDuration = _roundDuration;
genesis = block.timestamp;
drandGenesis = _drandGenesis;
drandPeriod = _drandPeriod;
drandSafety = _drandSafety;
bettingDuration = _roundDuration; // default: betting open whole round; tune via setBettingWindow
}
// ── views ──
function currentRound() public view returns (uint256) {
if (block.timestamp < genesis) return 0;
return (block.timestamp - genesis) / roundDuration;
}
function roundEndsAt(uint256 id) public view returns (uint256) {
return genesis + (id + 1) * roundDuration;
}
function timeLeft() external view returns (uint256) {
uint256 e = roundEndsAt(currentRound());
return block.timestamp >= e ? 0 : e - block.timestamp;
}
function bettingEndsAt(uint256 id) public view returns (uint256) {
return genesis + id * roundDuration + bettingDuration;
}
/// @notice Secs until betting closes for the current round (0 = in reveal phase).
function bettingLeft() external view returns (uint256) {
uint256 e = bettingEndsAt(currentRound());
return block.timestamp >= e ? 0 : e - block.timestamp;
}
function drandRoundFor(uint256 id) public view returns (uint64) {
// beacon finalizes just after BETTING closes (unknowable while betting is
// open) so the winner can be resolved during the reveal window.
uint256 e = bettingEndsAt(id);
if (e <= drandGenesis) return drandSafety;
return uint64((e - drandGenesis) / drandPeriod) + drandSafety;
}
function getRound(uint256 id) external view returns (Round memory) {
return rounds[id];
}
/// @notice Number of miners who bet on a resolved round's winning square.
function winnersCount(uint256 id) external view returns (uint256) {
Round storage r = rounds[id];
if (!r.resolved || !r.hasWinner) return 0;
return _bettors[id][r.winner].length;
}
/// @notice The i-th miner on a resolved round's winning square.
function winnerAt(uint256 id, uint256 i) external view returns (address) {
return _bettors[id][rounds[id].winner][i];
}
function betOf(uint256 id, uint256 sq, address u) external view returns (uint256) {
return _bet[keccak256(abi.encode(id, sq, u))];
}
function userRoundTotal(uint256 id, address u) external view returns (uint256) {
return _userRound[keccak256(abi.encode(id, u))];
}
/// @notice Refinable STEEL (unrefined + accrued dividends) and its 10% fee.
function refinable(address u) public view returns (uint256 gross, uint256 fee, uint256 net) {
Miner memory m = miners[u];
uint256 accrued = m.refinedAccrued + (m.unrefined * (globalIndex - m.indexSnapshot)) / WAD;
fee = (m.unrefined * refiningFeeBps) / BPS;
gross = m.unrefined + accrued;
net = gross - fee;
}
/// @notice A user's share of a resolved round (informational). Winnings are
/// auto-settled into `pendingEth` / unrefined at resolve, so this is a
/// historical breakdown, not an outstanding claim.
function roundShare(uint256 id, address u) external view returns (uint256 ethWon, uint256 steelWon) {
Round storage r = rounds[id];
if (!r.resolved || !r.hasWinner) return (0, 0);
uint256 b = _bet[keccak256(abi.encode(id, uint256(r.winner), u))];
if (b == 0 || r.winnerPot == 0) return (0, 0);
ethWon = (uint256(r.winnersPool) * b) / r.winnerPot;
if (r.solo) steelWon = u == r.soloWinner ? r.steelForWinners : 0;
else steelWon = (uint256(r.steelForWinners) * b) / r.winnerPot;
}
// ── play ──
function commit(uint8[] calldata squares) external payable nonReentrant {
_commit(msg.sender, msg.value, squares);
}
/// @notice Commit ETH attributed to `bettor` — winnings + STEEL auto-settle to them.
/// Whoever calls funds the bet; used by the auto-deploy keeper.
function commitFor(address bettor, uint8[] calldata squares) external payable nonReentrant {
_commit(bettor, msg.value, squares);
}
function _commit(address bettor, uint256 value, uint8[] memory squares) internal {
if (block.timestamp < genesis) revert NotStarted();
uint256 n = squares.length;
if (n == 0) revert NoSquares();
uint256 per = value / n;
if (per < minCommit) revert TooSmall();
uint256 rid = currentRound();
if (block.timestamp >= genesis + rid * roundDuration + bettingDuration) revert BettingClosed();
uint256 used;
for (uint256 i; i < n; i++) {
uint8 sq = squares[i];
if (sq >= SQUARES) revert BadSquare();
bytes32 bk = keccak256(abi.encode(rid, uint256(sq), bettor));
if (_bet[bk] == 0 && !_isBettor[bk]) {
_isBettor[bk] = true;
_bettors[rid][sq].push(bettor);
}
_bet[bk] += per;
squarePot[rid][sq] += per;
used += per;
}
uint256 rem = value - used;
if (rem > 0) {
uint8 f = squares[0];
_bet[keccak256(abi.encode(rid, uint256(f), bettor))] += rem;
squarePot[rid][f] += rem;
}
rounds[rid].totalPot += uint128(value);
_userRound[keccak256(abi.encode(rid, bettor))] += value;
emit Committed(rid, bettor, value, squares);
}
// ── auto-deploy (keeper commits for you each round from an escrow balance) ──
struct AutoSub {
uint128 amountPerSquare;
uint64 roundsLeft; // ignored when reload = true
uint64 lastRound; // last round executed (or subscribe round) — one commit per round
bool active;
bool reload; // run indefinitely while the escrow can cover a round
uint8[] squares;
}
mapping(address => AutoSub) internal _autoSub;
mapping(address => uint256) public autoBalance;
address[] public autoList;
mapping(address => bool) internal _autoListed;
uint256 public autoFee; // ETH per-square per round -> devAddr (keeper gas). default 0
event AutoSubscribed(address indexed user, uint256 amountPerSquare, uint256 rounds);
event AutoTopUp(address indexed user, uint256 amount);
event AutoCancelled(address indexed user, uint256 refunded);
event AutoExecuted(address indexed user, uint256 indexed round, uint256 spent);
/// @notice Escrow ETH and auto-commit `squares` at `amountPerSquare` for `roundsCount`
/// future rounds. The keeper fires each round; winnings auto-settle to you.
/// Starts next round. Must fully fund the plan (perRound * rounds).
function autoSubscribe(uint8[] calldata squares, uint256 amountPerSquare, uint256 roundsCount, bool reload)
external
payable
nonReentrant
{
uint256 n = squares.length;
if (n == 0 || n > SQUARES) revert NoSquares();
if (amountPerSquare < minCommit) revert TooSmall();
for (uint256 i; i < n; i++) if (squares[i] >= SQUARES) revert BadSquare();
autoBalance[msg.sender] += msg.value;
uint256 perRound = (amountPerSquare + autoFee) * n; // fee is per-square (keeper gas scales with tiles)
// reload: fund at least one round and keep going; fixed: fully fund the plan
if (reload) {
if (autoBalance[msg.sender] < perRound) revert TooSmall();
} else {
if (roundsCount == 0) revert BadParam();
if (autoBalance[msg.sender] < perRound * roundsCount) revert TooSmall();
}
AutoSub storage s = _autoSub[msg.sender];
delete s.squares;
for (uint256 i; i < n; i++) s.squares.push(squares[i]);
s.amountPerSquare = uint128(amountPerSquare);
s.roundsLeft = reload ? 0 : uint64(roundsCount);
s.reload = reload;
s.active = true;
s.lastRound = uint64(currentRound()); // skip the in-progress round, start next
if (!_autoListed[msg.sender]) {
_autoListed[msg.sender] = true;
autoList.push(msg.sender);
}
emit AutoSubscribed(msg.sender, amountPerSquare, roundsCount);
}
function autoTopUp() external payable nonReentrant {
autoBalance[msg.sender] += msg.value;
emit AutoTopUp(msg.sender, msg.value);
}
function autoCancel() external nonReentrant {
_autoSub[msg.sender].active = false;
uint256 bal = autoBalance[msg.sender];
if (bal > 0) {
autoBalance[msg.sender] = 0;
(bool ok,) = payable(msg.sender).call{value: bal}("");
require(ok, "ETH fail");
}
emit AutoCancelled(msg.sender, bal);
}
/// @notice Keeper entrypoint: fire every ready subscriber for the current round.
function autoExecuteMany(address[] calldata users) external nonReentrant {
uint256 rid = currentRound();
if (block.timestamp >= genesis + rid * roundDuration + bettingDuration) return; // betting closed
for (uint256 i; i < users.length; i++) _autoExec(users[i], rid);
}
function autoExecute(address user) external nonReentrant {
uint256 rid = currentRound();
if (block.timestamp >= genesis + rid * roundDuration + bettingDuration) revert BettingClosed();
_autoExec(user, rid);
}
function _autoExec(address user, uint256 rid) internal {
AutoSub storage s = _autoSub[user];
if (!s.active) return;
if (!s.reload && s.roundsLeft == 0) return;
if (uint64(rid) == s.lastRound) return; // already done this round
uint256 n = s.squares.length;
uint256 cost = uint256(s.amountPerSquare) * n;
uint256 feeTotal = autoFee * n; // per-square keeper gas
uint256 total = cost + feeTotal;
if (autoBalance[user] < total) {
s.active = false; // out of funds -> stop
return;
}
autoBalance[user] -= total;
if (feeTotal > 0) pendingEth[devAddr] += feeTotal; // keeper/dev compensation
s.lastRound = uint64(rid);
if (!s.reload) {
s.roundsLeft -= 1;
if (s.roundsLeft == 0) s.active = false;
}
_commit(user, cost, s.squares);
emit AutoExecuted(user, rid, cost);
}
// auto views
function autoInfo(address u)
external
view
returns (uint256 amountPerSquare, uint256 roundsLeft, uint256 lastRound, bool active, uint256 balance, bool reload)
{
AutoSub storage s = _autoSub[u];
return (s.amountPerSquare, s.roundsLeft, s.lastRound, s.active, autoBalance[u], s.reload);
}
function autoSquares(address u) external view returns (uint8[] memory) {
return _autoSub[u].squares;
}
function autoCount() external view returns (uint256) {
return autoList.length;
}
function autoReady(address u) public view returns (bool) {
AutoSub storage s = _autoSub[u];
if (!s.active) return false;
if (!s.reload && s.roundsLeft == 0) return false;
uint256 rid = currentRound();
if (uint64(rid) == s.lastRound) return false;
if (block.timestamp >= genesis + rid * roundDuration + bettingDuration) return false;
return autoBalance[u] >= (uint256(s.amountPerSquare) + autoFee) * s.squares.length;
}
/// @notice Users the keeper should fire this round (for off-chain iteration).
function autoReadyList() external view returns (address[] memory ready) {
uint256 len = autoList.length;
address[] memory tmp = new address[](len);
uint256 c;
for (uint256 i; i < len; i++) {
if (autoReady(autoList[i])) tmp[c++] = autoList[i];
}
ready = new address[](c);
for (uint256 i; i < c; i++) ready[i] = tmp[i];
}
function resolveRound(uint256 rid) external nonReentrant {
Round storage r = rounds[rid];
if (r.resolved) revert AlreadyResolved();
if (block.timestamp < bettingEndsAt(rid)) revert RoundOpen(); // resolvable once betting closes
uint64 dr = drandRoundFor(rid);
bytes32 rand = drand.randomness(dr);
if (rand == bytes32(0)) revert NotReady();
r.resolved = true;
r.drandRound = dr;
uint256 pot = r.totalPot;
uint8 win = uint8(uint256(rand) % SQUARES);
r.winner = win;
uint256 winnerPot = squarePot[rid][win];
r.winnerPot = uint128(winnerPot);
// ── STEEL emission (reserve 1.12x against cap) ──
(uint256 winnerSteel, uint256 devSteel, uint256 mlodeSteel) = _emit();
if (pot == 0) {
// nothing wagered — carry STEEL forward, no fees
carrySteel += winnerSteel + mlodeSteel;
if (devSteel > 0) _mintDev(devSteel);
emit Resolved(rid, win, false, false, false, 0);
return;
}
// ── ETH fee split (all carved from the protocol fee; winners always get pot-fee) ──
uint256 fee = (pot * protocolFeeBps) / BPS;
uint256 jackEth = (pot * jackpotFeeBps) / BPS;
uint256 keeperEth = (pot * keeperFeeBps) / BPS;
uint256 stakerEth = fee - jackEth - keeperEth; // remainder of the fee -> stakers
uint256 winnersEth = pot - fee;
if (devSteel > 0) _mintDev(devSteel); // credit dev weight first so it shares this round's fee
if (stakerEth > 0 && address(veSteel) != address(0)) veSteel.notifyReward{value: stakerEth}();
else motherlodeEth += stakerEth; // no stakers/dev -> park in motherlode
if (keeperEth > 0) pendingEth[devAddr] += keeperEth; // self-funds keeper gas from the tax
motherlodeEth += jackEth;
motherlodeSteel += mlodeSteel;
if (winnerPot == 0) {
// no winner: roll ETH + winner STEEL into the carry pools
carryEth += winnersEth;
carrySteel += winnerSteel;
emit Resolved(rid, win, false, false, false, pot);
return;
}
r.hasWinner = true;
uint256 poolEth = winnersEth + carryEth;
uint256 poolSteel = winnerSteel + carrySteel;
carryEth = 0;
carrySteel = 0;
// ── jackpot roll (1/625) ──
if (uint256(keccak256(abi.encode(rand, "jackpot"))) % jackpotOdds == 0) {
r.jackpotHit = true;
poolEth += motherlodeEth;
poolSteel += motherlodeSteel;
emittedTotal += motherlodeSteel; // reserve the motherlode STEEL now
motherlodeEth = 0;
motherlodeSteel = 0;
}
// ── single-miner roll (50%) — never on a jackpot round, so the motherlode
// (STEEL + ETH) always pays out pro-rata to ALL of that round's winners ──
if (!r.jackpotHit && uint256(keccak256(abi.encode(rand, "solo"))) % 2 == 0) {
r.solo = true;
r.soloWinner = _pickSolo(rid, win, winnerPot, uint256(keccak256(abi.encode(rand, "pick"))));
}
r.winnersPool = uint128(poolEth);
r.steelForWinners = uint128(poolSteel);
// ── auto-settle: credit winners now so claiming is always one tx ──
_settleWinners(rid, win, poolEth, poolSteel, r.solo, r.soloWinner, winnerPot);
emit Resolved(rid, win, true, r.solo, r.jackpotHit, pot);
}
/// @dev Push each winner's ETH into `pendingEth` and STEEL into their unrefined
/// balance at resolve time. Winning-square bettors are unique in `_bettors`
/// (deduped on commit), so a single pass credits everyone with no double-count.
/// ETH always splits pro-rata by bet — never winner-take-all — so no player
/// can extract another's ETH through solo variance. The STEEL mining reward
/// keeps the lottery: on a solo round the whole emission goes to ONE
/// bet-weighted winner; otherwise STEEL splits pro-rata too.
function _settleWinners(
uint256 rid,
uint8 win,
uint256 poolEth,
uint256 poolSteel,
bool solo,
address soloW,
uint256 winnerPot
) internal {
if (winnerPot == 0) return;
// STEEL jackpot: solo round hands the entire emission to the single winner.
if (solo && poolSteel > 0) _accrueUnrefined(soloW, poolSteel);
address[] storage bs = _bettors[rid][win];
uint256 len = bs.length;
for (uint256 i; i < len; i++) {
address u = bs[i];
uint256 b = _bet[keccak256(abi.encode(rid, uint256(win), u))];
if (b == 0) continue;
uint256 e = (poolEth * b) / winnerPot; // ETH: always pro-rata
if (e > 0) _creditWinnerEth(u, e);
if (!solo && poolSteel > 0) {
uint256 s = (poolSteel * b) / winnerPot; // STEEL: pro-rata on non-solo rounds
if (s > 0) _accrueUnrefined(u, s);
}
}
}
/// @dev ETH winnings only compound back into the escrow when auto-RELOAD is on,
/// so the loop self-funds. Without reload (fixed N rounds) winnings settle to
/// the claimable balance each round, exactly like a manual bettor — never
/// trapped in escrow. Non-subscribers always land in the claimable balance.
function _creditWinnerEth(address u, uint256 amt) internal {
AutoSub storage s = _autoSub[u];
if (s.active && s.reload) autoBalance[u] += amt;
else pendingEth[u] += amt;
}
/// @dev size + reserve the base emission (winner 100% / dev 8% / motherlode 4%).
function _emit() internal returns (uint256 winnerSteel, uint256 devSteel, uint256 mlodeSteel) {
uint256 mult = BPS + devBps + motherlodeBps; // 11200
uint256 remaining = miningCap - emittedTotal;
uint256 maxBudget = (remaining * BPS) / mult;
uint256 budget = steelPerRound < maxBudget ? steelPerRound : maxBudget;
if (budget == 0) return (0, 0, 0);
winnerSteel = budget;
devSteel = (budget * devBps) / BPS;
mlodeSteel = (budget * motherlodeBps) / BPS;
// reserve winner + dev now; motherlode reserved when it pays out
emittedTotal += winnerSteel + devSteel;
}
function _mintDev(uint256 amt) internal {
if (address(veSteel) != address(0)) {
steel.mint(address(veSteel), amt);
veSteel.creditDevLock(amt);
} else {
steel.mint(devAddr, amt);
}
}
/// @dev weighted-by-bet pick among the winning square's bettors.
function _pickSolo(uint256 rid, uint256 sq, uint256 winnerPot, uint256 seed)
internal
view
returns (address)
{
uint256 target = seed % winnerPot;
address[] storage bs = _bettors[rid][sq];
uint256 acc;
for (uint256 i; i < bs.length; i++) {
acc += _bet[keccak256(abi.encode(rid, sq, bs[i]))];
if (target < acc) return bs[i];
}
return bs.length > 0 ? bs[bs.length - 1] : address(0);
}
// ── claim / refine (winnings are auto-settled at resolve; just withdraw) ──
/// @notice Withdraw ETH winnings only (STEEL stays unrefined, earning dividends).
function claimEth() external nonReentrant {
uint256 amt = pendingEth[msg.sender];
if (amt == 0) revert NothingToClaim();
pendingEth[msg.sender] = 0;
(bool ok,) = payable(msg.sender).call{value: amt}("");
require(ok, "ETH fail");
emit EthClaimed(msg.sender, amt);
}
/// @notice Refine STEEL (mint it) — 10% fee shared to other unrefined holders —
/// and withdraw ETH winnings in the same call.
function claimAll() external nonReentrant {
_checkpoint(msg.sender);
Miner storage m = miners[msg.sender];
uint256 unref = m.unrefined;
uint256 accrued = m.refinedAccrued;
uint256 gross = unref + accrued;
if (gross == 0 && pendingEth[msg.sender] == 0) revert NothingToClaim();
uint256 fee = (unref * refiningFeeBps) / BPS;
uint256 out = gross - fee;
m.unrefined = 0;
m.refinedAccrued = 0;
totalUnrefined -= unref;
if (fee > 0 && totalUnrefined > 0) globalIndex += (fee * WAD) / totalUnrefined;
// if no one left to receive, fee stays unminted (reduces circulating; safe)
if (out > 0) steel.mint(msg.sender, out);
uint256 eth = pendingEth[msg.sender];
if (eth > 0) {
pendingEth[msg.sender] = 0;
(bool ok,) = payable(msg.sender).call{value: eth}("");
require(ok, "ETH fail");
}
emit Refined(msg.sender, out, fee, eth);
}
function _accrueUnrefined(address u, uint256 amt) internal {
_checkpoint(u);
miners[u].unrefined += amt;
totalUnrefined += amt;
}
function _checkpoint(address u) internal {
Miner storage m = miners[u];
if (m.unrefined > 0) {
m.refinedAccrued += (m.unrefined * (globalIndex - m.indexSnapshot)) / WAD;
}
m.indexSnapshot = globalIndex;
}
// ── admin ──
function setVeSteel(IVeSteelV2 v) external onlyOwner {
veSteel = v;
}
function setDrand(IDrandOracle d) external onlyOwner {
drand = d;
}
function setDevAddr(address d) external onlyOwner {
if (d == address(0)) revert BadParam();
devAddr = d;
}
function setAutoFee(uint256 f) external onlyOwner {
if (f > 0.01 ether) revert BadParam();
autoFee = f;
}
// ── one-time migration seed (carry state from a previous mine) ──
bool public seeded;
/// @notice Carry each holder's claimable STEEL over as fresh unrefined balance.
/// Reserved against the cap so refining mints correctly. Dev-only, one-shot.
function seedUnrefined(address[] calldata users, uint256[] calldata amounts) external onlyOwner {
if (seeded) revert BadParam();
if (users.length != amounts.length) revert BadParam();
for (uint256 i; i < users.length; i++) {
uint256 amt = amounts[i];
if (amt == 0) continue;
_accrueUnrefined(users[i], amt);
emittedTotal += amt; // it was already emitted on the old mine
}
}
/// @notice Carry the motherlode over (ETH sent with the call, STEEL as param).
function seedMotherlode(uint256 steel) external payable onlyOwner {
if (seeded) revert BadParam();
motherlodeEth += msg.value;
motherlodeSteel += steel;
}
function finalizeSeed() external onlyOwner {
seeded = true;
}
function setEconomics(
uint256 _protocolFeeBps,
uint256 _jackpotFeeBps,
uint256 _steelPerRound,
uint256 _devBps,
uint256 _motherlodeBps,
uint256 _jackpotOdds,
uint256 _refiningFeeBps,
uint256 _keeperFeeBps
) external onlyOwner {
if (_protocolFeeBps > 2000 || _jackpotFeeBps + _keeperFeeBps > _protocolFeeBps) revert BadParam();
if (_steelPerRound > 1000e18 || _devBps > 2000 || _motherlodeBps > 2000) revert BadParam();
if (_jackpotOdds == 0 || _refiningFeeBps > 2000) revert BadParam();
protocolFeeBps = _protocolFeeBps;
jackpotFeeBps = _jackpotFeeBps;
steelPerRound = _steelPerRound;
devBps = _devBps;
motherlodeBps = _motherlodeBps;
jackpotOdds = _jackpotOdds;
refiningFeeBps = _refiningFeeBps;
keeperFeeBps = _keeperFeeBps;
emit ParamsChanged();
}
function setMinCommit(uint256 v) external onlyOwner {
if (v > 1 ether) revert BadParam();
minCommit = v;
emit ParamsChanged();
}
/// @notice Betting closes this many secs into a round; the rest is the reveal phase.
function setBettingWindow(uint256 _bettingDuration) external onlyOwner {
if (_bettingDuration < 10 || _bettingDuration > roundDuration) revert BadParam();
bettingDuration = _bettingDuration;
emit ParamsChanged();
}
}
Chain explorer1633msChain node89ms