KeysOfSherwood
0x02312c3bff61d8d36c6355df1196efbd1a58ed1b
Verification
Verified
v0.8.34+commit.80d5c536
Type
Contract
18,126 bytes
ABI entries
53
20 read · 12 write
License
none
Contract information
- Address
- 0x02312c3bff61d8d36c6355df1196efbd1a58ed1b
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.34+commit.80d5c536
- Optimization
- Disabled
- Creator
- 0x6b640A7028…a4f450AF60
- Creation tx
- 0x8a2012a40b…83643a9ddc
Token
- Name
- Keys of Sherwood
- Symbol
- KEYS
- Decimals
- —
- Holders
- 3,064
- Market
- View token page →
Read contract (20)
BRONZE_END() → uint256
BRONZE_START() → uint256
GOLD_END() → uint256
GOLD_START() → uint256
MAX_SUPPLY() → uint256
SILVER_END() → uint256
SILVER_START() → uint256
balanceOf(address) → uint256
getApproved(uint256) → address
imagesCID() → string
isApprovedForAll(address, address) → bool
name() → string
owner() → address
ownerOf(uint256) → address
royaltyInfo(uint256, uint256) → address, uint256
supportsInterface(bytes4) → bool
symbol() → string
tierName(uint256) → string
tierOf(uint256) → uint8
tokenURI(uint256) → string
Events (6)
AirdroppedApprovalApprovalForAllBatchMintedOwnershipTransferredTransfer
ABI
[
{
"inputs": [
{
"internalType": "string",
"name": "_imagesCID",
"type": "string"
},
{
"internalType": "address",
"name": "royaltyReceiver",
"type": "address"
},
{
"internalType": "uint96",
"name": "royaltyFeeBps",
"type": "uint96"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "numerator",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "denominator",
"type": "uint256"
}
],
"name": "ERC2981InvalidDefaultRoyalty",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "receiver",
"type": "address"
}
],
"name": "ERC2981InvalidDefaultRoyaltyReceiver",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "numerator",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "denominator",
"type": "uint256"
}
],
"name": "ERC2981InvalidTokenRoyalty",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "address",
"name": "receiver",
"type": "address"
}
],
"name": "ERC2981InvalidTokenRoyaltyReceiver",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "ERC721IncorrectOwner",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "ERC721InsufficientApproval",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "approver",
"type": "address"
}
],
"name": "ERC721InvalidApprover",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "operator",
"type": "address"
}
],
"name": "ERC721InvalidOperator",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "ERC721InvalidOwner",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "receiver",
"type": "address"
}
],
"name": "ERC721InvalidReceiver",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "ERC721InvalidSender",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "ERC721NonexistentToken",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "OwnableInvalidOwner",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "OwnableUnauthorizedAccount",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "Airdropped",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "approved",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "ApprovalForAll",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "startId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "count",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "BatchMinted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [],
"name": "BRONZE_END",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "BRONZE_START",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "GOLD_END",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "GOLD_START",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MAX_SUPPLY",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "SILVER_END",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "SILVER_START",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "approve",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "recipients",
"type": "address[]"
},
{
"internalType": "uint256[]",
"name": "tokenIds",
"type": "uint256[]"
}
],
"name": "batchAirdrop",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "startId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "count",
"type": "uint256"
}
],
"name": "batchMintToSelf",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "getApproved",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "imagesCID",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "operator",
"type": "address"
}
],
"name": "isApprovedForAll",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "mint",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "ownerOf",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "salePrice",
"type": "uint256"
}
],
"name": "royaltyInfo",
"outputs": [
{
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "safeTransferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "safeTransferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "operator",
"type": "address"
},
{
"internalType": "bool",
"name": "approved",
"type": "bool"
}
],
"name": "setApprovalForAll",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"internalType": "uint96",
"name": "feeBps",
"type": "uint96"
}
],
"name": "setDefaultRoyalty",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "newCID",
"type": "string"
}
],
"name": "setImagesCID",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes4",
"name": "interfaceId",
"type": "bytes4"
}
],
"name": "supportsInterface",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "tierName",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "tierOf",
"outputs": [
{
"internalType": "enum KeysOfSherwood.Tier",
"name": "",
"type": "uint8"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "tokenURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]Source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/common/ERC2981.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Base64.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
/// @title Keys of Sherwood
/// @notice 3,333 transferable membership keys (Bronze / Silver / Gold),
/// minted by the owner and distributed via manual airdrop.
contract KeysOfSherwood is ERC721, ERC2981, Ownable {
// ---------------------------------------------------------------
// Supply configuration
// ---------------------------------------------------------------
uint256 public constant MAX_SUPPLY = 3333;
// TokenId ranges (inclusive), 1-indexed
uint256 public constant BRONZE_START = 1;
uint256 public constant BRONZE_END = 2333; // 2,333 keys (70%)
uint256 public constant SILVER_START = 2334;
uint256 public constant SILVER_END = 3000; // 667 keys (20%)
uint256 public constant GOLD_START = 3001;
uint256 public constant GOLD_END = 3333; // 333 keys (10%)
enum Tier { Bronze, Silver, Gold }
// IPFS CID of the folder containing bronze_key.jpg / silver_key.jpg / gold_key.jpg
// e.g. "bafybeifs5qyqstujtdoxy2rilhk2beqwowfrtm7orjcnkejsjwkexxmkxq"
string public imagesCID;
// Tracks which tokenIds have already been minted, to prevent double mint
mapping(uint256 => bool) private _minted;
event BatchMinted(uint256 startId, uint256 count, address indexed to);
event Airdropped(uint256 indexed tokenId, address indexed to);
constructor(string memory _imagesCID, address royaltyReceiver, uint96 royaltyFeeBps)
ERC721("Keys of Sherwood", "KEYS")
Ownable(msg.sender)
{
imagesCID = _imagesCID;
// e.g. royaltyFeeBps = 500 -> 5% secondary royalties
_setDefaultRoyalty(royaltyReceiver, royaltyFeeBps);
}
function setImagesCID(string calldata newCID) external onlyOwner {
imagesCID = newCID;
}
// ---------------------------------------------------------------
// Tier lookup
// ---------------------------------------------------------------
function tierOf(uint256 tokenId) public pure returns (Tier) {
require(tokenId >= BRONZE_START && tokenId <= GOLD_END, "Invalid tokenId");
if (tokenId <= BRONZE_END) return Tier.Bronze;
if (tokenId <= SILVER_END) return Tier.Silver;
return Tier.Gold;
}
function tierName(uint256 tokenId) external pure returns (string memory) {
Tier t = tierOf(tokenId);
if (t == Tier.Bronze) return "Bronze Key";
if (t == Tier.Silver) return "Silver Key";
return "Gold Key";
}
// ---------------------------------------------------------------
// Minting (owner-only, mints straight to your own wallet first)
// ---------------------------------------------------------------
/// @notice Mint a single specific tokenId to an address (usually yourself).
function mint(address to, uint256 tokenId) public onlyOwner {
require(tokenId >= BRONZE_START && tokenId <= GOLD_END, "tokenId out of range");
require(!_minted[tokenId], "Already minted");
_minted[tokenId] = true;
_safeMint(to, tokenId);
}
/// @notice Mint a contiguous range of tokenIds to a single address (e.g. yourself).
/// @dev Use this to pre-mint all 3,333 keys to your own wallet before airdropping.
function batchMintToSelf(uint256 startId, uint256 count) external onlyOwner {
require(startId >= BRONZE_START, "start below range");
require(startId + count - 1 <= GOLD_END, "exceeds max supply");
for (uint256 i = 0; i < count; i++) {
uint256 tokenId = startId + i;
require(!_minted[tokenId], "Already minted");
_minted[tokenId] = true;
_safeMint(msg.sender, tokenId);
}
emit BatchMinted(startId, count, msg.sender);
}
// ---------------------------------------------------------------
// Airdrop helper (owner sends already-minted keys out to holders)
// ---------------------------------------------------------------
/// @notice Send previously minted tokenIds (currently owned by you) to recipients.
/// @dev recipients[i] receives tokenIds[i]. Arrays must be the same length.
/// Call this after batchMintToSelf, using your wallet-to-tier CSV.
function batchAirdrop(address[] calldata recipients, uint256[] calldata tokenIds)
external
onlyOwner
{
require(recipients.length == tokenIds.length, "Length mismatch");
for (uint256 i = 0; i < recipients.length; i++) {
safeTransferFrom(msg.sender, recipients[i], tokenIds[i]);
emit Airdropped(tokenIds[i], recipients[i]);
}
}
// ---------------------------------------------------------------
// Metadata (generated fully on-chain — no JSON files needed anywhere)
// ---------------------------------------------------------------
function _tierInfo(Tier t)
internal
pure
returns (
string memory name_,
string memory metal,
string memory gem,
string memory imageFile,
string memory description,
uint256 tierSupply
)
{
if (t == Tier.Bronze) {
return (
"Bronze Key",
"Bronze",
"Smoky Topaz",
"bronze_key.jpg",
"A Bronze Key of Sherwood - your first step into the band. Forged in the outlaw's hideout, it unlocks lifetime allowlist access, a share of secondary royalties, and priority on every future Sherwood airdrop.",
BRONZE_END - BRONZE_START + 1
);
} else if (t == Tier.Silver) {
return (
"Silver Key",
"Silver",
"Pale Sapphire",
"silver_key.jpg",
"A Silver Key of Sherwood - proof you've fought alongside the band, not just watched from the treeline. Unlocks lifetime allowlist access, a share of secondary royalties, and priority on every future Sherwood airdrop.",
SILVER_END - SILVER_START + 1
);
} else {
return (
"Gold Key",
"Gold",
"Emerald",
"gold_key.jpg",
"A Gold Key of Sherwood - the rarest trust Robin Hood gives. Grants a seat at the War Council, lifetime allowlist access, a share of secondary royalties, and first priority on every future Sherwood airdrop.",
GOLD_END - GOLD_START + 1
);
}
}
function _positionInTier(uint256 tokenId, Tier t) internal pure returns (uint256) {
if (t == Tier.Bronze) return tokenId - BRONZE_START + 1;
if (t == Tier.Silver) return tokenId - SILVER_START + 1;
return tokenId - GOLD_START + 1;
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
require(_ownerOf(tokenId) != address(0), "Token does not exist");
Tier t = tierOf(tokenId);
(
string memory name_,
string memory metal,
string memory gem,
string memory imageFile,
string memory description,
uint256 tierSupply
) = _tierInfo(t);
uint256 position = _positionInTier(tokenId, t);
string memory json = string(
abi.encodePacked(
'{"name":"', name_, ' #', Strings.toString(position), '",',
'"description":"', description, '",',
'"image":"ipfs://', imagesCID, '/', imageFile, '",',
'"attributes":[',
'{"trait_type":"Collection","value":"Keys of Sherwood"},',
'{"trait_type":"Tier","value":"', name_, '"},',
'{"trait_type":"Metal","value":"', metal, '"},',
'{"trait_type":"Gem","value":"', gem, '"},',
'{"trait_type":"Tier Supply","value":"', Strings.toString(tierSupply), '"}',
']}'
)
);
return string(
abi.encodePacked(
"data:application/json;base64,",
Base64.encode(bytes(json))
)
);
}
// ---------------------------------------------------------------
// Royalties (ERC-2981) - lets marketplaces pay secondary royalties
// ---------------------------------------------------------------
function setDefaultRoyalty(address receiver, uint96 feeBps) external onlyOwner {
_setDefaultRoyalty(receiver, feeBps);
}
// Required override for multiple inheritance (ERC721 + ERC2981)
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC2981)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}Chain explorer2985msChain node77ms