SherwoodTiles

0xce7aae491a3801b1e2f88f1f3fadc4aa8a86aa77

Verification
Verified
v0.8.24+commit.e11b9ed9
Type
Contract
14,907 bytes
ABI entries
85
42 read · 21 write
License
none

Contract information

Address
0xce7aae491a3801b1e2f88f1f3fadc4aa8a86aa77
Chain
Robinhood Chain (4663)
Compiler
v0.8.24+commit.e11b9ed9
Optimization
Enabled
Creation tx
0x2cf4d24fee…d780a94653

Token

Not a token

This contract does not expose ERC-20 metadata.

Read contract (42)

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
players(address)uint256, uint256, uint8
sherwoodToken()address
taxRateBps()uint256
tokenURI(uint256)string
totalTileHolders()uint256
treasury()address
treasuryFeesAccumulated()uint256

Events (18)

BaseMetadataURISetDepositAddedDepositWithdrawnFeesClaimedMetadataLockedNativeFeesReceivedOwnershipTransferredParamChangedPausedSetPriceChangedSherwoodTokenSetTaxCollectedTileAbandonedTileBuyoutTileClaimedTileForeclosedTreasuryChangedTreasuryFeesClaimed

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": "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": "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": [
      {
        "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": [
      {
        "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"
  },
  {
    "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 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;

    // ═══════════════════════════════════════════════════════════
    //  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 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;

        _settleFees(prevOwner);
        _settleFees(msg.sender);
        _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;
        }

        // 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
        if (payout > 0) {
            (bool sent,) = payable(prevOwner).call{value: payout}("");
            require(sent, "Payout failed");
        }

        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.
        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();

        (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);
    }

    // ═══════════════════════════════════════════════════════════
    //  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;
        (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);
    }

    // ═══════════════════════════════════════════════════════════
    //  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;
    }

    function transferOwnership(address newOwner) external onlyOwner {
        require(newOwner != address(0), "Zero address");
        emit OwnershipTransferred(owner, newOwner);
        owner = newOwner;
    }

    /// @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 after 90 days of zero activity. Funds go to `treasury`.
    /// @dev Centralization vector inherited from the original (documented in DEPLOY.md).
    function emergencyWithdraw() external onlyOwner {
        require(block.timestamp - lastActivityTimestamp > 90 days, "Too early");
        (bool sent,) = payable(treasury).call{value: address(this).balance}("");
        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;

        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 explorer3237msChain node77ms