SherwoodTiles
0xdec5327a235d8508a330fb805d8cc5f7722492aa
Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
15,560 bytes
ABI entries
92
44 read · 23 write
License
none
Contract information
- Address
- 0xdec5327a235d8508a330fb805d8cc5f7722492aa
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.24+commit.e11b9ed9
- Optimization
- Enabled
- Creator
- 0x30b0D62A8E…2425D1647F
- Creation tx
- 0x8ed3ded52f…8a86a23923
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (44)
APPR_HOLDER_BPS() → uint256
APPR_TREASURY_BPS() → uint256
BUYOUT_HOLDER_BPS() → uint256
BUYOUT_TREASURY_BPS() → uint256
DECAY_FLOOR_BPS() → uint256
DECAY_PERIOD() → uint256
FEE_HOLDER_BPS() → uint256
FEE_TREASURY_BPS() → uint256
MAX_ADMISSION() → uint256
MAX_APPR_TAX_BPS() → uint256
MAX_BUYOUT_FEE_BPS() → uint256
MAX_DECAY_RATE_BPS() → uint256
MAX_PRICE_MULT() → uint256
MAX_TAX_RATE_BPS() → uint256
MAX_TILES_PER_WALLET() → uint256
MIN_PRICE() → uint256
TAX_HOLDER_BPS() → uint256
TAX_PERIOD() → uint256
TAX_TREASURY_BPS() → uint256
TOTAL_TILES() → uint256
admissionPrice() → uint256
appreciationTaxBps() → uint256
baseMetadataURI() → string
buyoutFeeBps() → uint256
decayRateBps() → uint256
getAllTiles() → address[100], uint256[100], uint256[100]
getEffectivePrice(uint256) → uint256
getPlayer(address) → uint256, uint256, uint8[5]
getTile(uint256) → address, uint256, uint256, uint256, uint256, uint256, uint256
globalRewardPerShare() → uint256
isTileHolder(address) → bool
lastActivityTimestamp() → uint256
metadataLocked() → bool
owner() → address
paused() → bool
pendingOwner() → address
pendingWithdrawals(address) → uint256
players(address) → uint256, uint256, uint8
sherwoodToken() → address
taxRateBps() → uint256
tokenURI(uint256) → string
totalTileHolders() → uint256
treasury() → address
treasuryFeesAccumulated() → uint256
Events (21)
BaseMetadataURISetDepositAddedDepositWithdrawnFeesClaimedMetadataLockedNativeFeesReceivedOwnershipTransferStartedOwnershipTransferredParamChangedPausedSetPaymentsWithdrawnPayoutDeferredPriceChangedSherwoodTokenSetTaxCollectedTileAbandonedTileBuyoutTileClaimedTileForeclosedTreasuryChangedTreasuryFeesClaimed
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "_treasury",
"type": "address"
},
{
"internalType": "address",
"name": "_sherwoodToken",
"type": "address"
},
{
"internalType": "string",
"name": "_baseMetadataURI",
"type": "string"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint8",
"name": "bits",
"type": "uint8"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "SafeCastOverflowedUintDowncast",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "string",
"name": "uri",
"type": "string"
}
],
"name": "BaseMetadataURISet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "tileId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "DepositAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "tileId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "DepositWithdrawn",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "player",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "FeesClaimed",
"type": "event"
},
{
"anonymous": false,
"inputs": [],
"name": "MetadataLocked",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "total",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "toTreasury",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "toHolders",
"type": "uint256"
}
],
"name": "NativeFeesReceived",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferStarted",
"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": "bytes32",
"name": "key",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "ParamChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "bool",
"name": "paused",
"type": "bool"
}
],
"name": "PausedSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "payee",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "PaymentsWithdrawn",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "payee",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "PayoutDeferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "tileId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "oldPrice",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "newPrice",
"type": "uint256"
}
],
"name": "PriceChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SherwoodTokenSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "tileId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "TaxCollected",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "tileId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "TileAbandoned",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "tileId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "prevOwner",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "price",
"type": "uint256"
}
],
"name": "TileBuyout",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "tileId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "price",
"type": "uint256"
}
],
"name": "TileClaimed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "tileId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "prevOwner",
"type": "address"
}
],
"name": "TileForeclosed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousTreasury",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newTreasury",
"type": "address"
}
],
"name": "TreasuryChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "treasury",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "TreasuryFeesClaimed",
"type": "event"
},
{
"inputs": [],
"name": "APPR_HOLDER_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "APPR_TREASURY_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "BUYOUT_HOLDER_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "BUYOUT_TREASURY_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "DECAY_FLOOR_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "DECAY_PERIOD",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "FEE_HOLDER_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "FEE_TREASURY_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_ADMISSION",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_APPR_TAX_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_BUYOUT_FEE_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_DECAY_RATE_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_PRICE_MULT",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_TAX_RATE_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_TILES_PER_WALLET",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MIN_PRICE",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TAX_HOLDER_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TAX_PERIOD",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TAX_TREASURY_BPS",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "TOTAL_TILES",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tileId",
"type": "uint256"
}
],
"name": "abandonTile",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "acceptOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tileId",
"type": "uint256"
}
],
"name": "addDeposit",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "admissionPrice",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "appreciationTaxBps",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "baseMetadataURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "buyoutFeeBps",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tileId",
"type": "uint256"
}
],
"name": "buyoutTile",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "claimFees",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tileId",
"type": "uint256"
},
{
"internalType": "uint80",
"name": "declaredPrice",
"type": "uint80"
}
],
"name": "claimTile",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "claimTreasuryFees",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "decayRateBps",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "emergencyWithdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "getAllTiles",
"outputs": [
{
"internalType": "address[100]",
"name": "owners",
"type": "address[100]"
},
{
"internalType": "uint256[100]",
"name": "prices",
"type": "uint256[100]"
},
{
"internalType": "uint256[100]",
"name": "deposits",
"type": "uint256[100]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tileId",
"type": "uint256"
}
],
"name": "getEffectivePrice",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "addr",
"type": "address"
}
],
"name": "getPlayer",
"outputs": [
{
"internalType": "uint256",
"name": "tileCount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "pendingFees",
"type": "uint256"
},
{
"internalType": "uint8[5]",
"name": "ownedTiles",
"type": "uint8[5]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tileId",
"type": "uint256"
}
],
"name": "getTile",
"outputs": [
{
"internalType": "address",
"name": "tileOwner",
"type": "address"
},
{
"internalType": "uint256",
"name": "price",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "effectivePrice",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deposit",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "taxOwed",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "taxTimestamp",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "buyoutTimestamp",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "globalRewardPerShare",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "addr",
"type": "address"
}
],
"name": "isTileHolder",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "lastActivityTimestamp",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "lockMetadata",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "metadataLocked",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "paused",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pendingOwner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "pendingWithdrawals",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "players",
"outputs": [
{
"internalType": "uint256",
"name": "rewardSnapshot",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "accumulatedFees",
"type": "uint256"
},
{
"internalType": "uint8",
"name": "tileCount",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tileId",
"type": "uint256"
}
],
"name": "pokeTax",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "price",
"type": "uint256"
}
],
"name": "setAdmissionPrice",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "bps",
"type": "uint256"
}
],
"name": "setAppreciationTaxBps",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "uri",
"type": "string"
}
],
"name": "setBaseMetadataURI",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "bps",
"type": "uint256"
}
],
"name": "setBuyoutFeeBps",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "bps",
"type": "uint256"
}
],
"name": "setDecayRateBps",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bool",
"name": "_paused",
"type": "bool"
}
],
"name": "setPaused",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tileId",
"type": "uint256"
},
{
"internalType": "uint80",
"name": "newPrice",
"type": "uint80"
}
],
"name": "setPrice",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_token",
"type": "address"
}
],
"name": "setSherwoodToken",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "bps",
"type": "uint256"
}
],
"name": "setTaxRateBps",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_treasury",
"type": "address"
}
],
"name": "setTreasury",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "sherwoodToken",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "taxRateBps",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tileId",
"type": "uint256"
}
],
"name": "tokenURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalTileHolders",
"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": [],
"name": "treasury",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "treasuryFeesAccumulated",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tileId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "withdrawDeposit",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "withdrawPayments",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
/// @title SherwoodTiles — Harberger Tile Economy for $SHERWOOD
/// @notice 100 tiles (IDs 1..100) with a fixed admission fee, continuous Harberger tax,
/// self-assessed pricing, permissionless takeover/buyout, and native-fee revenue
/// share to tile holders. Forked from the Takeover.fun / PolyClaudeTiles system.
/// @dev Economics are denominated in the chain's NATIVE gas token (ETH on Robinhood Chain,
/// an Ethereum L2). Protocol fees route to `treasury` (constructor / setter, never
/// hardcoded). The $SHERWOOD token address is stored for reference + post-launch wiring
/// via `setSherwoodToken` and is NOT required by the core mechanics.
///
/// Fee routing (protocol share -> treasury, remainder -> tile holders):
/// - admission fee ............ 100% treasury
/// - harberger tax ............ 50% treasury / 50% holders
/// - buyout fee (10%) ......... 90% treasury / 10% holders
/// - appreciation tax (30%) ... 60% treasury / 40% holders
/// - native fee stream ........ 10% treasury / 90% holders (robinfun trading fees)
contract SherwoodTiles is ReentrancyGuard {
using Strings for uint256;
using SafeCast for uint256;
// ═══════════════════════════════════════════════════════════
// CONSTANTS (structural — not tunable)
// ═══════════════════════════════════════════════════════════
uint256 public constant TOTAL_TILES = 100; // IDs 1..100
uint256 public constant MIN_PRICE = 0.0005 ether;
uint256 public constant MAX_TILES_PER_WALLET = 5;
uint256 public constant TAX_PERIOD = 604800; // 1 week in seconds
uint256 public constant DECAY_PERIOD = 1209600; // 2 weeks in seconds
uint256 public constant DECAY_FLOOR_BPS = 1000; // decayed price floors at 10% of original
uint256 public constant MAX_PRICE_MULT = 3; // max 3x self-assessed increase per setPrice
// Fee SPLITS are fixed (structural safety — never mis-set to grief holders/treasury).
uint256 public constant BUYOUT_TREASURY_BPS = 9000; // buyout fee: 90% treasury
uint256 public constant BUYOUT_HOLDER_BPS = 1000; // 10% holders
uint256 public constant FEE_TREASURY_BPS = 1000; // native stream: 10% treasury
uint256 public constant FEE_HOLDER_BPS = 9000; // 90% holders
uint256 public constant TAX_TREASURY_BPS = 5000; // harberger tax: 50% treasury
uint256 public constant TAX_HOLDER_BPS = 5000; // 50% holders
uint256 public constant APPR_TREASURY_BPS = 6000; // appreciation: 60% treasury
uint256 public constant APPR_HOLDER_BPS = 4000; // 40% holders
// Tunable-parameter bounds
uint256 public constant MAX_TAX_RATE_BPS = 2000; // hard cap 20%/week
uint256 public constant MAX_BUYOUT_FEE_BPS = 3000; // hard cap 30%
uint256 public constant MAX_APPR_TAX_BPS = 5000; // hard cap 50%
uint256 public constant MAX_DECAY_RATE_BPS = 5000; // hard cap 50%/period
uint256 public constant MAX_ADMISSION = 1 ether;
uint256 private constant PRECISION = 1e18;
uint256 private constant BPS = 10000;
// ═══════════════════════════════════════════════════════════
// TUNABLE PARAMETERS (owner-settable, bounded; original values as defaults)
// ═══════════════════════════════════════════════════════════
uint256 public admissionPrice = 0.005 ether; // fixed admission → treasury
uint256 public taxRateBps = 500; // 5% per TAX_PERIOD
uint256 public buyoutFeeBps = 1000; // 10% takeover premium
uint256 public appreciationTaxBps = 3000; // 30% on self-assessed increases
uint256 public decayRateBps = 2000; // 20% per DECAY_PERIOD
// ═══════════════════════════════════════════════════════════
// ROLES / CONFIG
// ═══════════════════════════════════════════════════════════
address public owner; // admin (was `authority`)
address public pendingOwner; // two-step ownership handoff target (must acceptOwnership)
address public treasury; // protocol fee recipient (was hardcoded `devWallet`)
address public sherwoodToken; // $SHERWOOD CA — reference/wiring only, never hardcoded
string public baseMetadataURI; // tokenURI = baseMetadataURI + tileId + ".json"
bool public metadataLocked; // once true, baseMetadataURI is immutable
bool public paused;
uint256 public lastActivityTimestamp;
// Fee accumulation
uint256 public globalRewardPerShare;
uint256 public totalTileHolders;
uint256 public treasuryFeesAccumulated;
struct TileData {
address owner;
uint80 price; // self-assessed price
uint96 deposit; // tax reserve
uint40 taxTimestamp; // last tax collection
uint40 buyoutTimestamp; // last buyout (decay anchor)
uint80 originalPrice; // decay floor reference
}
struct PlayerState {
uint256 rewardSnapshot; // globalRewardPerShare at last settlement
uint256 accumulatedFees; // unclaimed holder fees
uint8 tileCount; // tiles owned
uint8[5] ownedTiles; // owned tile IDs (1..100); 0 = empty slot
}
TileData[100] private _tiles; // index i => tileId (i+1)
mapping(address => PlayerState) public players;
// Pull-payment escrow: buyout payouts that fail to push (e.g. a prev owner whose
// receive() reverts) are credited here so a griefing recipient can never block a
// takeover. The recipient withdraws separately via withdrawPayments().
mapping(address => uint256) public pendingWithdrawals;
// ═══════════════════════════════════════════════════════════
// EVENTS
// ═══════════════════════════════════════════════════════════
event TileClaimed(uint256 indexed tileId, address indexed owner, uint256 price);
event TileBuyout(uint256 indexed tileId, address indexed newOwner, address indexed prevOwner, uint256 price);
event TileAbandoned(uint256 indexed tileId, address indexed owner);
event PriceChanged(uint256 indexed tileId, uint256 oldPrice, uint256 newPrice);
event TaxCollected(uint256 indexed tileId, uint256 amount);
event TileForeclosed(uint256 indexed tileId, address indexed prevOwner);
event FeesClaimed(address indexed player, uint256 amount);
event TreasuryFeesClaimed(address indexed treasury, uint256 amount);
event NativeFeesReceived(uint256 total, uint256 toTreasury, uint256 toHolders);
event DepositAdded(uint256 indexed tileId, uint256 amount);
event DepositWithdrawn(uint256 indexed tileId, uint256 amount);
event PayoutDeferred(address indexed payee, uint256 amount);
event PaymentsWithdrawn(address indexed payee, uint256 amount);
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
event TreasuryChanged(address indexed previousTreasury, address indexed newTreasury);
event SherwoodTokenSet(address indexed token);
event BaseMetadataURISet(string uri);
event MetadataLocked();
event ParamChanged(bytes32 indexed key, uint256 value);
event PausedSet(bool paused);
// ═══════════════════════════════════════════════════════════
// MODIFIERS
// ═══════════════════════════════════════════════════════════
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
modifier whenNotPaused() {
require(!paused, "Paused");
_;
}
/// @dev Validates a 1-based tile ID and returns the storage slot.
function _tile(uint256 tileId) private view returns (TileData storage) {
require(tileId >= 1 && tileId <= TOTAL_TILES, "Invalid tile");
return _tiles[tileId - 1];
}
// ═══════════════════════════════════════════════════════════
// CONSTRUCTOR
// ═══════════════════════════════════════════════════════════
/// @param _treasury protocol fee recipient (TREASURY_ADDRESS)
/// @param _sherwoodToken $SHERWOOD CA if already launched, else address(0) (wire later)
/// @param _baseMetadataURI base URI for tile metadata (e.g. "ipfs://<cid>/" or "https://.../")
constructor(address _treasury, address _sherwoodToken, string memory _baseMetadataURI) {
require(_treasury != address(0), "Zero treasury");
owner = msg.sender;
treasury = _treasury;
sherwoodToken = _sherwoodToken;
baseMetadataURI = _baseMetadataURI;
lastActivityTimestamp = block.timestamp;
emit OwnershipTransferred(address(0), msg.sender);
emit TreasuryChanged(address(0), _treasury);
if (_sherwoodToken != address(0)) emit SherwoodTokenSet(_sherwoodToken);
}
// ═══════════════════════════════════════════════════════════
// RECEIVE — native fee stream (robinfun trading fees) enters here
// ═══════════════════════════════════════════════════════════
/// @notice Receives native token (trading-fee stream). Splits 10% treasury / 90% holders.
receive() external payable {
if (msg.value == 0) return;
lastActivityTimestamp = block.timestamp;
uint256 toTreasury = (msg.value * FEE_TREASURY_BPS) / BPS;
uint256 toHolders = msg.value - toTreasury;
treasuryFeesAccumulated += toTreasury;
if (totalTileHolders > 0 && toHolders > 0) {
globalRewardPerShare += (toHolders * PRECISION) / totalTileHolders;
} else {
treasuryFeesAccumulated += toHolders; // no holders yet → all to treasury
}
emit NativeFeesReceived(msg.value, toTreasury, toHolders);
}
// ═══════════════════════════════════════════════════════════
// TILE OPERATIONS
// ═══════════════════════════════════════════════════════════
/// @notice Claim an empty tile. Fixed admission fee → 100% treasury; excess → tax deposit.
function claimTile(uint256 tileId, uint80 declaredPrice) external payable nonReentrant whenNotPaused {
TileData storage tile = _tile(tileId);
require(tile.owner == address(0), "Tile taken");
require(declaredPrice >= MIN_PRICE, "Price too low");
require(msg.value >= admissionPrice, "Underpaid admission");
PlayerState storage player = players[msg.sender];
require(player.tileCount < MAX_TILES_PER_WALLET, "Max 5 tiles");
_settleFees(msg.sender);
uint256 fee = admissionPrice;
treasuryFeesAccumulated += fee; // pull-payment (no external call in hot path)
uint256 depositAmount = msg.value - fee;
tile.owner = msg.sender;
tile.price = declaredPrice;
tile.deposit = depositAmount.toUint96();
tile.taxTimestamp = block.timestamp.toUint40();
tile.buyoutTimestamp = block.timestamp.toUint40();
tile.originalPrice = declaredPrice;
player.ownedTiles[player.tileCount] = tileId.toUint8();
player.tileCount++;
totalTileHolders++;
lastActivityTimestamp = block.timestamp;
emit TileClaimed(tileId, msg.sender, declaredPrice);
}
/// @notice Take over any tile at its current effective price + buyout fee. Anyone, anytime.
function buyoutTile(uint256 tileId) external payable nonReentrant whenNotPaused {
TileData storage tile = _tile(tileId);
require(tile.owner != address(0), "Tile empty");
require(tile.owner != msg.sender, "Own tile");
PlayerState storage buyer = players[msg.sender];
require(buyer.tileCount < MAX_TILES_PER_WALLET, "Max 5 tiles");
_collectTax(tileId);
require(tile.owner != address(0), "Tile foreclosed"); // may have foreclosed during tax
uint256 effectivePrice = _getEffectivePrice(tileId);
uint256 buyoutFee = (effectivePrice * buyoutFeeBps) / BPS;
uint256 totalCost = effectivePrice + buyoutFee;
require(msg.value >= totalCost, "Insufficient ETH");
address prevOwner = tile.owner;
uint256 prevDeposit = tile.deposit;
// Settle + remove the outgoing owner BEFORE the holder-reward bump so the
// fee they are paying isn't retroactively credited to the tile leaving them.
_settleFees(prevOwner);
_removeTileFromPlayer(prevOwner, tileId);
uint256 payout = effectivePrice + prevDeposit;
// Buyout fee split
uint256 treasuryShare = (buyoutFee * BUYOUT_TREASURY_BPS) / BPS;
uint256 holderShare = buyoutFee - treasuryShare;
treasuryFeesAccumulated += treasuryShare;
if (totalTileHolders > 0 && holderShare > 0) {
globalRewardPerShare += (holderShare * PRECISION) / totalTileHolders;
} else {
treasuryFeesAccumulated += holderShare;
}
// Settle the buyer AFTER the bump so the tile they are acquiring doesn't grab a
// slice of the buyout fee they themselves just paid (self-rebate).
_settleFees(msg.sender);
// New owner, reset decay anchor
tile.owner = msg.sender;
tile.price = effectivePrice.toUint80();
tile.taxTimestamp = block.timestamp.toUint40();
tile.buyoutTimestamp = block.timestamp.toUint40();
tile.originalPrice = effectivePrice.toUint80();
tile.deposit = (msg.value - totalCost).toUint96(); // excess → new deposit
buyer.ownedTiles[buyer.tileCount] = tileId.toUint8();
buyer.tileCount++;
// totalTileHolders unchanged (1 out, 1 in)
// CEI: external transfer last, guarded by nonReentrant. Try to push the payout,
// but a prev owner whose receive() reverts must NOT be able to block the
// takeover — fall back to pull-payment escrow instead of reverting.
if (payout > 0) {
(bool sent,) = payable(prevOwner).call{value: payout}("");
if (!sent) {
pendingWithdrawals[prevOwner] += payout;
emit PayoutDeferred(prevOwner, payout);
}
}
lastActivityTimestamp = block.timestamp;
emit TileBuyout(tileId, msg.sender, prevOwner, effectivePrice);
}
/// @notice Abandon a tile and recover remaining deposit (after settling tax).
function abandonTile(uint256 tileId) external nonReentrant {
TileData storage tile = _tile(tileId);
require(tile.owner == msg.sender, "Not owner");
_collectTax(tileId);
if (tile.owner == address(0)) return; // foreclosed during tax; nothing to refund
_settleFees(msg.sender);
uint256 refund = tile.deposit;
_removeTileFromPlayer(msg.sender, tileId);
_clearTile(tile);
totalTileHolders--;
if (refund > 0) {
(bool sent,) = payable(msg.sender).call{value: refund}("");
require(sent, "Refund failed");
}
lastActivityTimestamp = block.timestamp;
emit TileAbandoned(tileId, msg.sender);
}
/// @notice Set a new self-assessed price. Appreciation tax applies on increases.
function setPrice(uint256 tileId, uint80 newPrice) external payable nonReentrant whenNotPaused {
TileData storage tile = _tile(tileId);
require(tile.owner == msg.sender, "Not owner");
require(newPrice >= MIN_PRICE, "Price too low");
_collectTax(tileId);
require(tile.owner == msg.sender, "Foreclosed");
uint256 effectivePrice = _getEffectivePrice(tileId);
require(newPrice <= effectivePrice * MAX_PRICE_MULT, "Max 3x increase");
uint256 oldPrice = tile.price;
if (newPrice > effectivePrice) {
uint256 increase = newPrice - effectivePrice;
uint256 tax = (increase * appreciationTaxBps) / BPS;
uint256 treasuryShare = (tax * APPR_TREASURY_BPS) / BPS;
uint256 holderShare = tax - treasuryShare;
treasuryFeesAccumulated += treasuryShare;
if (totalTileHolders > 0 && holderShare > 0) {
globalRewardPerShare += (holderShare * PRECISION) / totalTileHolders;
} else {
treasuryFeesAccumulated += holderShare;
}
uint256 available = uint256(tile.deposit) + msg.value;
require(available >= tax, "Insufficient for appreciation tax");
tile.deposit = (available - tax).toUint96();
} else {
tile.deposit += msg.value.toUint96();
}
tile.price = newPrice;
// decay anchor (buyoutTimestamp/originalPrice) intentionally NOT reset here — parity
// with the original: an untraded tile keeps decaying even if re-priced.
lastActivityTimestamp = block.timestamp;
emit PriceChanged(tileId, oldPrice, newPrice);
}
/// @notice Add native token to a tile's tax deposit.
function addDeposit(uint256 tileId) external payable nonReentrant {
TileData storage tile = _tile(tileId);
require(tile.owner == msg.sender, "Not owner");
require(msg.value > 0, "No ETH");
tile.deposit += msg.value.toUint96();
lastActivityTimestamp = block.timestamp;
emit DepositAdded(tileId, msg.value);
}
/// @notice Withdraw from a tile's deposit (after settling tax).
function withdrawDeposit(uint256 tileId, uint256 amount) external nonReentrant {
TileData storage tile = _tile(tileId);
require(tile.owner == msg.sender, "Not owner");
_collectTax(tileId);
require(tile.owner == msg.sender, "Foreclosed");
require(amount <= tile.deposit, "Exceeds deposit");
tile.deposit -= amount.toUint96();
lastActivityTimestamp = block.timestamp;
(bool sent,) = payable(msg.sender).call{value: amount}("");
require(sent, "Withdraw failed");
emit DepositWithdrawn(tileId, amount);
}
/// @notice Permissionless tax collection / foreclosure trigger for any tile.
function pokeTax(uint256 tileId) external {
TileData storage tile = _tile(tileId);
require(tile.owner != address(0), "Empty tile");
_collectTax(tileId);
lastActivityTimestamp = block.timestamp;
}
// ═══════════════════════════════════════════════════════════
// FEE CLAIMING
// ═══════════════════════════════════════════════════════════
/// @notice Claim accumulated holder fees.
function claimFees() external nonReentrant {
_settleFees(msg.sender);
PlayerState storage player = players[msg.sender];
uint256 amount = player.accumulatedFees;
require(amount > 0, "No fees");
player.accumulatedFees = 0;
lastActivityTimestamp = block.timestamp;
(bool sent,) = payable(msg.sender).call{value: amount}("");
require(sent, "Claim failed");
emit FeesClaimed(msg.sender, amount);
}
/// @notice Send accumulated protocol fees to the treasury. Permissionless trigger,
/// funds always go to `treasury` (not the caller).
function claimTreasuryFees() external nonReentrant {
uint256 amount = treasuryFeesAccumulated;
require(amount > 0, "No fees");
treasuryFeesAccumulated = 0;
(bool sent,) = payable(treasury).call{value: amount}("");
require(sent, "Claim failed");
emit TreasuryFeesClaimed(treasury, amount);
}
/// @notice Withdraw a buyout payout that could not be pushed (escrowed pull-payment).
function withdrawPayments() external nonReentrant {
uint256 amount = pendingWithdrawals[msg.sender];
require(amount > 0, "Nothing to withdraw");
pendingWithdrawals[msg.sender] = 0;
(bool sent,) = payable(msg.sender).call{value: amount}("");
require(sent, "Withdraw failed");
emit PaymentsWithdrawn(msg.sender, amount);
}
// ═══════════════════════════════════════════════════════════
// VIEWS
// ═══════════════════════════════════════════════════════════
function getEffectivePrice(uint256 tileId) external view returns (uint256) {
_tile(tileId); // bounds check
return _getEffectivePrice(tileId);
}
function getTile(uint256 tileId)
external
view
returns (
address tileOwner,
uint256 price,
uint256 effectivePrice,
uint256 deposit,
uint256 taxOwed,
uint256 taxTimestamp,
uint256 buyoutTimestamp
)
{
TileData storage tile = _tile(tileId);
tileOwner = tile.owner;
price = tile.price;
effectivePrice = _getEffectivePrice(tileId);
deposit = tile.deposit;
taxOwed = _calculateTax(tileId);
taxTimestamp = tile.taxTimestamp;
buyoutTimestamp = tile.buyoutTimestamp;
}
function getPlayer(address addr)
external
view
returns (uint256 tileCount, uint256 pendingFees, uint8[5] memory ownedTiles)
{
PlayerState storage player = players[addr];
tileCount = player.tileCount;
ownedTiles = player.ownedTiles;
pendingFees = player.accumulatedFees;
if (player.tileCount > 0) {
pendingFees += ((globalRewardPerShare - player.rewardSnapshot) * player.tileCount) / PRECISION;
}
}
/// @notice True if `addr` owns at least one tile.
function isTileHolder(address addr) external view returns (bool) {
return players[addr].tileCount > 0;
}
/// @notice Snapshot of all 100 tiles. Array index i corresponds to tileId (i + 1).
function getAllTiles()
external
view
returns (address[100] memory owners, uint256[100] memory prices, uint256[100] memory deposits)
{
for (uint256 i = 0; i < TOTAL_TILES; i++) {
TileData storage tile = _tiles[i];
owners[i] = tile.owner;
prices[i] = _getEffectivePrice(i + 1);
deposits[i] = tile.deposit;
}
}
/// @notice Metadata pointer for a tile: baseMetadataURI + tileId + ".json".
function tokenURI(uint256 tileId) external view returns (string memory) {
_tile(tileId); // bounds check
return string(abi.encodePacked(baseMetadataURI, tileId.toString(), ".json"));
}
// ═══════════════════════════════════════════════════════════
// ADMIN
// ═══════════════════════════════════════════════════════════
function setPaused(bool _paused) external onlyOwner {
paused = _paused;
emit PausedSet(_paused);
}
function setTreasury(address _treasury) external onlyOwner {
require(_treasury != address(0), "Zero address");
emit TreasuryChanged(treasury, _treasury);
treasury = _treasury;
}
/// @notice Begin a two-step ownership handoff. The new owner must call acceptOwnership()
/// to take control, so a mistyped-but-valid address can never brick the admin.
/// Pass address(0) to cancel a pending transfer.
function transferOwnership(address newOwner) external onlyOwner {
pendingOwner = newOwner;
emit OwnershipTransferStarted(owner, newOwner);
}
/// @notice Complete a two-step ownership handoff. Only the pending owner may call.
function acceptOwnership() external {
require(msg.sender == pendingOwner, "Not pending owner");
emit OwnershipTransferred(owner, pendingOwner);
owner = pendingOwner;
pendingOwner = address(0);
}
/// @notice Wire the $SHERWOOD token address post-launch (never hardcoded).
function setSherwoodToken(address _token) external onlyOwner {
sherwoodToken = _token;
emit SherwoodTokenSet(_token);
}
function setBaseMetadataURI(string calldata uri) external onlyOwner {
require(!metadataLocked, "Metadata locked");
baseMetadataURI = uri;
emit BaseMetadataURISet(uri);
}
/// @notice Permanently freeze the metadata pointer (makes it immutable).
function lockMetadata() external onlyOwner {
metadataLocked = true;
emit MetadataLocked();
}
// ── Tunable economic params (bounded) ──
function setTaxRateBps(uint256 bps) external onlyOwner {
require(bps <= MAX_TAX_RATE_BPS, "Tax too high");
taxRateBps = bps;
emit ParamChanged("taxRateBps", bps);
}
function setAdmissionPrice(uint256 price) external onlyOwner {
require(price >= MIN_PRICE && price <= MAX_ADMISSION, "Out of bounds");
admissionPrice = price;
emit ParamChanged("admissionPrice", price);
}
function setBuyoutFeeBps(uint256 bps) external onlyOwner {
require(bps <= MAX_BUYOUT_FEE_BPS, "Fee too high");
buyoutFeeBps = bps;
emit ParamChanged("buyoutFeeBps", bps);
}
function setAppreciationTaxBps(uint256 bps) external onlyOwner {
require(bps <= MAX_APPR_TAX_BPS, "Tax too high");
appreciationTaxBps = bps;
emit ParamChanged("appreciationTaxBps", bps);
}
function setDecayRateBps(uint256 bps) external onlyOwner {
require(bps <= MAX_DECAY_RATE_BPS, "Decay too high");
decayRateBps = bps;
emit ParamChanged("decayRateBps", bps);
}
/// @notice Emergency sweep of accrued PROTOCOL fees to `treasury` after 90 days of zero
/// activity. Bounded to `treasuryFeesAccumulated` only — it can NEVER touch player
/// tax deposits or unclaimed holder fees, which stay withdrawable by their owners
/// (abandon/withdrawDeposit/claimFees) regardless of contract activity.
function emergencyWithdraw() external onlyOwner {
require(block.timestamp - lastActivityTimestamp > 90 days, "Too early");
uint256 amount = treasuryFeesAccumulated;
require(amount > 0, "No fees");
treasuryFeesAccumulated = 0;
(bool sent,) = payable(treasury).call{value: amount}("");
require(sent, "Failed");
}
// ═══════════════════════════════════════════════════════════
// INTERNAL
// ═══════════════════════════════════════════════════════════
function _getEffectivePrice(uint256 tileId) internal view returns (uint256) {
TileData storage tile = _tiles[tileId - 1];
if (tile.owner == address(0)) return 0;
uint256 elapsed = block.timestamp - tile.buyoutTimestamp;
if (elapsed < DECAY_PERIOD) return tile.price;
uint256 periods = elapsed / DECAY_PERIOD;
uint256 price = tile.price;
uint256 floor = (uint256(tile.originalPrice) * DECAY_FLOOR_BPS) / BPS;
if (floor < MIN_PRICE) floor = MIN_PRICE;
// The decay floor references the ORIGINAL (anchor) price, but setPrice can lower the
// self-assessed price below that anchor without resetting it. Never let the floor
// raise the effective price ABOVE the owner's current self-assessment — that would
// invert the Harberger invariant (you're always for sale at YOUR price) and overtax.
if (floor > price) floor = price;
for (uint256 i = 0; i < periods && i < 20; i++) {
price = price - (price * decayRateBps) / BPS;
if (price <= floor) return floor;
}
return price < MIN_PRICE ? MIN_PRICE : price;
}
function _calculateTax(uint256 tileId) internal view returns (uint256) {
TileData storage tile = _tiles[tileId - 1];
if (tile.owner == address(0)) return 0;
uint256 elapsed = block.timestamp - tile.taxTimestamp;
if (elapsed == 0) return 0;
uint256 effectivePrice = _getEffectivePrice(tileId);
return (effectivePrice * taxRateBps * elapsed) / (BPS * TAX_PERIOD);
}
function _collectTax(uint256 tileId) internal {
TileData storage tile = _tiles[tileId - 1];
if (tile.owner == address(0)) return;
uint256 taxOwed = _calculateTax(tileId);
if (taxOwed == 0) {
tile.taxTimestamp = block.timestamp.toUint40();
return;
}
uint256 deposit = tile.deposit;
if (deposit >= taxOwed) {
tile.deposit = (deposit - taxOwed).toUint96();
tile.taxTimestamp = block.timestamp.toUint40();
_splitTax(taxOwed);
emit TaxCollected(tileId, taxOwed);
} else {
// Insolvent → foreclosure. Remaining deposit is taken as partial tax.
uint256 partialTax = deposit;
tile.deposit = 0;
if (partialTax > 0) _splitTax(partialTax);
address prevOwner = tile.owner;
_settleFees(prevOwner);
_removeTileFromPlayer(prevOwner, tileId);
_clearTile(tile);
totalTileHolders--;
emit TileForeclosed(tileId, prevOwner);
}
}
function _splitTax(uint256 amount) private {
uint256 treasuryShare = (amount * TAX_TREASURY_BPS) / BPS;
uint256 holderShare = amount - treasuryShare;
treasuryFeesAccumulated += treasuryShare;
if (totalTileHolders > 0 && holderShare > 0) {
globalRewardPerShare += (holderShare * PRECISION) / totalTileHolders;
} else {
treasuryFeesAccumulated += holderShare;
}
}
function _settleFees(address addr) internal {
PlayerState storage player = players[addr];
if (player.tileCount > 0) {
uint256 pending = ((globalRewardPerShare - player.rewardSnapshot) * player.tileCount) / PRECISION;
if (pending > 0) player.accumulatedFees += pending;
}
player.rewardSnapshot = globalRewardPerShare;
}
function _removeTileFromPlayer(address addr, uint256 tileId) internal {
PlayerState storage player = players[addr];
uint8 count = player.tileCount;
for (uint8 i = 0; i < count; i++) {
if (player.ownedTiles[i] == tileId.toUint8()) {
player.ownedTiles[i] = player.ownedTiles[count - 1];
player.ownedTiles[count - 1] = 0;
player.tileCount--;
return;
}
}
}
function _clearTile(TileData storage tile) private {
tile.owner = address(0);
tile.price = 0;
tile.deposit = 0;
tile.taxTimestamp = 0;
tile.buyoutTimestamp = 0;
tile.originalPrice = 0;
}
}
Chain explorer3942msChain node83ms