Hello, this is a small technical recount about how I built a Digital Crypto Native Scavenger hunt online. It was pretty cool and I hope you enjoy 😘.
UPDATE: Note that I have now dropped the requirement to own the NFT to play the hunt, so you can check out the puzzles yourself at https://skavengerkeys.themta.io/
WEB3️⃣, NFTs and PUZZLES.
Before this article begins. Of course there is going to be a puzzle hidden in this article somewhere, I'm not telling you where it begins, nor where it ends. But if you solve you get to see a cute pic of a puppy 🐶.
Stupid Boring Backstory
A few months ago I was recruited by Kollectiff which is the company behind the Metaverse Travel Agency NFT project to build a Crypto Native Digital scavenger hunt that was NFT gated for owners of the project’s “Metahelmet” this was to be M.T.A SKVNGR HUNT 3️⃣.0. Now whether you are 🤮🤮or 😍😍 when you hear the word NFT, I still think it’s important to understand what web3️⃣ is and the differences when compared to coding in a traditional style. A great introduction to the subject is here by Fireship.io which is a fantastic channel which I highly recommend.
Now this gig was riiiiiiiight up my alley for a few reasons. The first being that I am a massive fan of esoteric and interesting puzzles (many of which may or may not have been inspired by my work on DownUnderCTF hehe). The second being that I enjoy coding and building projects from the ground up. Thirdly because I have never built anything utilising web3️⃣ technologies before and I love learning new things. Finally, because the team was awesome and gave me a lot of creative autonomy to design the hunt in anyway that I like.
So as always, this isn’t going to be a super deep dive into the technical of the code aspects of how I did everything but more of a high level overview of both design of the hunt and how I utilised web3️⃣ libraries to make this a native web3️⃣ experience for the players!
Okay enough with the boring lets talk about what I actually did!
So let’s dig into a quick overview of the design of the hunt that I came up with for this event.
I took a lot of inspiration in designing my hunt from working on DownUnderCTF (Australia’s biggest Cyber Security Competition) as well as playing other online puzzle games like NotPron.
I also had been thinking about the Metaverse concept that many NFT projects are pushing (and now Facebook🤮) and how many NFT projects were promising a world similar to the book/movie “Ready Player One”.
I really liked the idea of collecting different parts of a whole, then putting them together at the end for an epic finale. This is where the idea of a path based structure came from. So what I settled on was a “assemble the machine” type of hunt, where players had to find each part of a “transporter” which then once all pieces had been found, you put them all together and start the final step of the hunt.
The diagram below showcases the design of how this works, essentially 5 different hunts you can do in any order (this way if players get stuck, they can move onto a different path and still enjoy the puzzle without having to wrack their brains on why the heck there is 5 astronauts in a picture and I need to get some kinda password out of it in Spanish👩🚀).
The pre-hunt steps were also necessary, as not to overload the main hunt hub with visitors as soon as the hunt opens. Ya know, when 4000 people hit your site at the same time and you’re not prepared for it, you’re going to have a bad time (hunt 2.0 flashbacks). Though if you want to know how you can get around this check out my other article here.
For the tech stack, it was pretty simple:
Front End: Next JS enabled React App statically exported and hosted on S3. Most of the web3 bits was helped with NoahZinsmeister’s web3-react library.
Backend: NodeJS express app with a few basic routes, hosted on an EC2 instance managed by PM2.
Database 1: MongoDB also running on an EC2 instance
Database 2: Da BlockChain baby ETHEREUM be like 🔺 SMART🧠CONTRACTS
Alright so now you know the hunt’s design and the infrastructure supporting it lets dive in to how we made it web3 ENABLED.
Turning on the Web3
web * web * web = web³
haha get it
Ok so this was the most interesting part of this whole project was enabling the web3️⃣ aspect.
There are a few things that are different when building a web3️⃣ enabled app directly related to this project.
- Authentication🔒 — using a public and private key, instead of a username and password. Auth is “stateless” no cookies, or auth tokens, just proof that you are who you say you are through your public/private key combo.
- Authorisation🔎 — Rather than looking up in a private database whether a user has “permission” to participate in the hunt or even exists in the first place. You would scour the blockchain’s essentially open database to verify if the wallet has at least one of the token’s required to play the hunt.
- Writing Data✍️ — Again without any auth keys/cookies, when receiving HTTP requests from your backend, there is no way to determine who sent you each request. This required using signed messages using a players private key when writing data and to think very carefully around what data you are saving and what you are writing.
There were also 3️⃣ main user flows which utilised these web3️⃣ components which we will dive into.
Signing in, probably isn’t the right term here, since the way that this works is now instead of having to manage users and authentication yourself through user management (or passing it to another centralised provider), the idea with web3️⃣ is that you have 1 account (wallet) which follows you across the web3️⃣.
So you don’t sign in, rather you ‘connect’ your wallet to each web3️⃣ site and app that you want to interact with. This is mostly done through some kind of “provider” which allows you to view your wallet, send messages and transactions. Examples of these are MetaMask, Coinbase Wallet, MyEtherWallet and plenty more. These usually work by injecting a
window.ethereum object into the browser environment and applications can utilise web3️⃣ operations through interacting with this object. However, each app that wishes to do this requires permission from the user to do this. This is what acts as our “sign in” method, rather it’s a “hey web3️⃣ person, can I have permission to do some cool things with your wallet thanks xx 😆”.
Once permission is granted, the app then can read the wallet address that was just connected and start its magic.
Bang, you’re connected let’s see if you have the right NFT associated with your wallet so you are allowed to play. We can do this through creating a contract object with the Ethereum contract ABI (this is kind of like a function signature, that allows the web3️⃣ library to know what type of parameters to send and to receive) and the address of the contract on the Ethereum network. Then calling one of the methods of the contract to check the token balance of a given wallet address.
The following is a snippet of React code showing how this would work using web3.js.
const contract = new library.eth.Contract(CONTRACT_ABI, CONTRACT_ADDRESS);
const balance = await contract.methods.balanceOf(walletAddress).call(); setNumberOfHelmets(balance);
This resulting value would then be the determining factor as to whether you would be able to participate or not and if the page would load.
The astute hackers👨💻among you, may read this and go, uhhhhh isn’t that just client side authentication???????? Couldn’t you just intercept the request or change that variables value and then you could play the hunt🤔🤔🤔🤔?
Yes☑️ and no❌.
Yes because, by doing this the frontend would act as if your wallet has the token to play the hunt and the site would load up as usual.
However, once you did anything that required interaction with the backend server, you would run into issues (more on this in the next section). Could I have implemented a way so that their was no way for anyone without owning the NFT access the website at all? Yes, but the effort required wasn’t worth it, so I stuck with this method.
So now we have our users, properly gated by whether or not they own the NFT required. Now let’s get into how the actual hunt will work once you find a key.
Submitting a new Key
Each individual path ends with providing the user a keyphrase. This part didn’t require any fancy web3️⃣ tech. It was just a set of cool puzzles sending players all over the internet and finally ending with a page which displayed which part of the transporter you found as well as an access code you needed to prove you got to the end. Here is an example of such page.
If you’re interested in these puzzles, you can view the official walkthrough of the hunt on YouTube here.
The web3️⃣ aspect came in once users were submitting the key on the Hunt Hub.
So the plan was, you get the keyphrase and submit it on the homepage hub to verify you had completed a path. So again without any auth tokens or cookies, how do users send a secure message that proves it was sent by them?
The solution of course is signing messages using your wallet private key. By simply signing a message with the keyphrase and sending over 3️⃣ things to the backend API:
- Message: The raw message being sent (the keyphrase)
- Signature: The signature of the raw message
- Wallet: The public address of the wallet that signed the above message
Then once the request hits the backend it performs 6 steps:
- Parse wallet, signature and message
- Validate the wallet is a valid public wallet address
- Validate the wallet has the associated NFT to be allowed to participate in the hunt (this is where them hackers will run into trouble if they managed to bypass the client side check).
- Recover the original signing wallet from the signature and verify it matches the wallet parameter.
- Check if key is correct or not
- Save new progress for the wallet in MongoDB
Through this method we both verify the original sender both has access to the private key of the wallet that sent the message and said wallet owns the token required to participate, pretty neat.
Connecting to Discord
The final interesting piece of the web3️⃣ puzzle for this project was being able to link your wallet with your Discord account. The reason for this was that it gave us ease of being able to give out prizes and connect with the winners of the event and also reduced the likelihood that people would participate twice as it required them to send their NFT to a different wallet (costing gas) and then creating a new Discord account as well.
My solution to this was very similar to the above approach of submitting a new key. Since all we needed was to authenticate the user once and save their discord data in MongoDB.
I am not going to go into the OAuth flow and how that works but just the bits that I had to play with.
Once the user clicked the “Connect to Discord” button they would be taken to Discord’s OAuth flow permission box which asks them to grant permission to the Hunt to access their identifying data. Once permission is granted they are then redirected back to the Hub Home page with the access code in the URL. The reason this is necessary rather than saving it directly is because if this code is directly sent to the backend, it does not know which wallet it corresponds to, and could lead to security issues.
So the user along with the access code is sent back to the user on the Hunt home page. They then sign another message with the Discord access code to verify the wallet that is connecting with the Discord account. The backend does the regular checks like in the key submission as well as verify the code and then links them together then badabing badaboom you gotcha self a wallet which has been discord’d.
So how did it all go?
I was really happy with how the event went. In regards to the tech that powered the hunt, it went without a hitch. No downtime, no issues, no cheeky fellas playing the hunt without owning the token.
The puzzles I designed were also well received, although the main complaint being that they were too hard so I will definitely take that into account for next time (by making them EVEN HARDER🖕)
If you want to give the hunt a go, you can head over to https://skavengerkeys.themta.io/ to give it a go as I have dropped the requirement of owning the NFT, but you will need a MetaMask wallet setup. and turn on the lateral thinking parts of your 🧠. Unfortunately I am unable to open-source the code as it is in the hands of the company as to whether they would like to do that or not.
Web3️⃣ technologies are just getting started and I think at the moment the use cases are pretty limited. However, I think as a technologist I think it super important to immerse yourself in these new types of technology and be aware of the power that they bring to then allow yourself to innovate in the space. As always the Medici effect comes into play.
Innovation comes at the intersection of disciplines and industries
Tee Vee Snacks?
Get in touch with me @ samcalamos.me
Finally for the folks trying to solve the puzzle in this article please note that there isn’t any prize! Once you have your solution navigate to:
so if you found the solution was bruh you will need to head to: