Inspiration

I was inspired to make this project because I wanted to:

  1. Learn about what full-stack development entails for a Web3 application.

  2. Learn a bit of Solidity and how to interact with Chainlink services in a contract.

  3. Work on a project that would be simple enough (and fun) that I would 100% be able to ship it and setup a website for it.

What it does

vrf8ball.link provides a web application that allows users to ask the Magic 8 Ball a question, and get an answer generated based on randomness from Chainlink's VRF V2 product; it is deployed to the Goerli testnet and you can see my demo here.

How we built it

I started by watching the videos produced for this hackathon, Intro to Hardhat and Intro to Front-end Development in Web3.

I then used the starter code for each of those projects to help me, i.e., the Chainlink hardhat-starter-kit repo and create-next-app.

From there, I was able to piece together what I needed for the contracts via the VRF docs and Hardhat docs, and by making modifications to the RandomNumberConsumerV2.sol contract. Once I had the contract approximately working, I shifted to spend most of my time on the frontend.

For the frontend, I used the starting point from the aforementioned Chainlink workshop, along with React, Typescript, react-moralis, and web3uikit. I also re-used a pre-built React Magic 8 Ball component codepen with some modifications (my source code credits the author and includes the required license, per the license).

At this point, I setup my website, deployed the application, and tried to make some progress (to no avail) on a feature that would show the user a log of their questions and answers over time, so that's where my project stopped.

Challenges we ran into

In no particular order:

  • Adding some complexity to my fulfillRandomness function without updating the callbackGasLimit value bit me very, very hard. What would happen is I would make some modifications to how I was processing the callback request, and suddenly I would have a large number of test failures. I spent a ton of time trying to understand what was going on here before it clicked.

  • A pretty general challenge I had on the frontend was that I was polling my contract at a very high level in the component tree to know how to render much of the rest of the application. This made it challenging to create an incremental UX where I can show parts of the application even if the user is, e.g., connected to the wrong network. I opted for the simple thing which basically locks the user out of the application if they are connected to the wrong network, but I think there are better ways to solve this type of problem, and using React's Context API probably would have helped here.

  • Getting accustomed to Hardhat and the dev, test, deploy workflow.

  • Dependency hell when trying to setup Jest tests in the frontend (I gave up on this eventually).

  • Trying to handle various UX edge cases in the front end without using local storage, e.g., attempting to return the user to a loading state if they refresh the page while their question transaction is being processed. Also, getting the polling to work correctly and consistently.

Accomplishments that we're proud of

  • I shipped my project!
  • I've created my first full-stack application using Solidity and Chainlink.
  • I learned a ton and had a lot of fun.

What we learned

  • I learned that, like a lot of software, it's much easier to do things correctly the first time, rather than have to change it later on; this was especially true for state management in my contract. If I did it again, I would do things completely differently from the start, and probably begin with a state diagram before writing any code.

  • Again, similar to other software, it's important to think how difficult it will be to change certain decisions later on. For example, my instinct told me that storing human-readable strings in the contract was probably not a great idea, and it would be better to resolve the values client-side. Sure enough, I realized I had a typo in one of the Magic 8 Ball answers, which was only resolvable by...re-deploying my contract. In the future, I think it will be best to think about using the contract to store the minimum state necessary, and derive as much other state as possible elsewhere. With my example, I could have done things differently by just returning an ID to the frontend, and then mapping the ID to a human-readable string.

  • I learned the basics of Solidity, Hardhat, testing my contract, and Chainlink VRF, and have a much better idea of how I would start a new Solidity or Chainlink project.

  • I got a lot of experience with the workflow for deploying a contract, funding the VRF subscription, updating consumers of the contract (my frontend) to use the new contract address, etc.

  • I learned that there are lots of error + edge cases when working with contracts, both internally, and also externally via a frontend client or a wallet, and that handling all of them gracefully in a way that doesn't degrade the user experience is challenging.

  • Be mindful of any hard-coded values, gas limits, etc., in your code, as they can cause problems if you aren't careful.

What's next for vrf8ball.link

  1. I'm probably going to re-write the backend from scratch to simplify all the state management.

  2. I'd also like to request + store multiple random words, rather than just a single one which I did for this project.

  3. Better error handling, especially figuring out if there's any way my contract can handle the case where the callback reverts.

  4. Then, I'd like to implement the question + answer history feature I didn't have time for.

  5. And while it's not strictly a part of this project, I would also like to take certain things I ran into while working on my project (e.g. some minor things with web3uikit) and see if there's an opportunity to push some changes upstream.

Built With

Share this project:

Updates