What if there was an NFT collection that can't be bought with money, but needs to be earned with skill and dedication?
Here's a story about a project I had a ton of fun building. I hope I can share at least a small fraction of my excitement with you through this post!
Every NFT collection on Ethereum is a smart contract. But the majority of NFT projects have a very simple way of getting the collection’s items: minting them for a fee. I thought it was very underwhelming, and set to explore other ways.
I started with a few challenges that can only be completed on-chain: computing hashes, sending ether, understanding block density and time, voting, etc.
The idea completely took over me. I kept thinking about it for weeks. Finally, I set aside ~48 hours and built a collection named Runes of Ethereum.
The collection has only 5 NFTs. Anyone can claim the NFT from its current owner if they demonstrate a higher skill or dedication. Runes are following the worthy!
If you are a solidity developer (or aspiring to be one!) check out the source on Etherscan and see if you can figure out what each of them does!
https://etherscan.io/address/0x555555551777611FD8eb00Df11Ea0904B560CF74
function claimRuneOfPower(uint256 nonce) external {
uint256 power = uint256(
keccak256(abi.encodePacked(lastPower, nonce, msg.sender))
);
require(power > lastPower, "Not powerful enough");
lastPower = power;
_transfer(ownerOf(RUNE_OF_POWER), msg.sender, RUNE_OF_POWER);
}
To get the Rune of Power, you need to submit a "stronger" nonce. When hashed with your address it should be larger than the previous power. I also included lastPower
into the hash so nobody can pre-compute several nonces in advance.
function claimRuneOfWealth() external payable {
require(msg.value > lastPrice, "Insufficient payment");
address lastClaimer = ownerOf(RUNE_OF_WEALTH);
uint256 refund = lastPrice;
uint256 gift = address(this).balance - refund;
_transfer(lastClaimer, msg.sender, RUNE_OF_WEALTH);
lastPrice = msg.value;
bool success = payable(lastClaimer).send(refund);
if (!success) {
WETH weth = WETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
weth.deposit{value: refund}();
require(weth.transfer(lastClaimer, refund), "Payment failed");
}
payable(creator).transfer(gift);
}
The Rune of Wealth goes to the one who paid more ETH than the previous person. The previous owner gets their money refunded back.
Interesting detail: the previous owner can "reject" the refund (griefing attack). So the Runes contract will fallback to wrapping and sending WETH
.
function claimRuneOfTime() external {
require(block.timestamp > lastTime + cooldown, "Need to wait");
lastTime = block.timestamp;
cooldown = cooldown + cooldown / 10;
_transfer(ownerOf(RUNE_OF_TIME), msg.sender, RUNE_OF_TIME);
}
The Rune of Time is available for anyone to claim after a cooldown. The cooldown itself is growing 10% at a time, so the more times it's claimed, the longer the next owner needs to wait.
To get the Rune you have to demonstrate dedication (or the ability to wield scripts and FlashBots).
function claimRuneOfSpace() external {
require(block.basefee > lastBasefee, "Block space not dense enough");
lastBasefee = block.basefee;
_transfer(ownerOf(RUNE_OF_SPACE), msg.sender, RUNE_OF_SPACE);
}
The Rune of Space can be claimed when the network gas price goes higher than the last time. The meaning behind it is that the gas price reflects block space density. I used basefee
and not gasprice
. The base fee can't be (efficiently) manipulated by the claimer.
Like with the Rune of Time, you have to show dedication to get the Rune.
function nominate(address collection, uint256 tokenId, address who) external {
require(tx.origin == msg.sender, "Only humans");
require(who != address(0), "Address is 0");
require(
collection == 0x4D2BB1FDfBdd3e5aC720a4c557117daB75351bfC ||
collection == 0xFF9C1b15B16263C61d017ee9F65C50e4AE0113D7 ||
collection == 0x5180db8F5c931aaE63c74266b211F580155ecac8 ||
collection == 0x8d04a8c79cEB0889Bdd12acdF3Fa9D207eD3Ff63,
"Not from this universe"
);
require(
IERC721Ownership(collection).ownerOf(tokenId) == msg.sender,
"Not authorized"
);
address prev = nominations[collection][tokenId];
if (prev != address(0)) {
votes[prev] -= 1;
}
nominations[collection][tokenId] = who;
votes[who] += 1;
if (votes[who] > lastVotes) {
lastVotes = votes[who];
_transfer(ownerOf(RUNE_OF_INFLUENCE), who, RUNE_OF_INFLUENCE);
}
}
The Rune of Influence is awarded to the person who has the most votes from the holders of @cryptopunksnfts, @crypto_coven, @lootproject, and @blitmap. These collections are very influential in the NFT space and I wanted the owner to be worthy of their vote.
Human consensus is a very hard problem, aligning communities together is a rare and valuable skill. I agree it's not as technical as other Runes, but at the same time, it also reflects the spirit of crypto.
Building Runes of Ethereum
The NFT metadata and SVG images are stored on-chain. The game will run for as long as Ethereum is alive.
The design inspiration comes from the Runic alphabet. I picked the letters that had a meaning related to the code of each NFT item. Then traced the letters and @0xHab added his magic to make it all look really nice.
Looking back, I wish I made the contract upgradeable. After I released the initial collection I had ideas for more fun Runes. I also regret not adding a way to send the Rune owners some kind of NFT that shows that they once owned that Rune. Maybe I'll do a v2 at some point!
I had a ton of fun making each Rune. It exercised almost every bit of Solidity knowledge and wit I had at the time. Mad respect to folks who managed to snipe at least one Rune (@frolic, @z0age, @jacobdehart, @eccogrinder, @EthereumCIA, @bradleyc, @asnared, and others), you are the legends!
I hope this project inspires you to think outside the box. To push the limits. To challenge the status quo. And to have fun doing things that you love.
If you liked the project but can’t get a Rune yet, feel free to mint this post’s edition, there are only 5!