This article is not a tutorial. It's my thoughts and notes about my experience building an app that wraps and unwraps Ether. The app is called WrapETH.
WrapETH is an app that allows you to wrap and unwrap ETH, (or another EVM chain's native asset). The current app version supports 7 different blockchains. The app is a front-end that communicates with the Wrapped Ether smart contract.
The Wrapped Ether (WETH) smart contract is decentralized code that exists on the Ethereum blockchain. It's not owned, or even affiliate with WrapETH the app. Other EVM chains also have a smart contract to wrap its native asset. For example, Polygon has WMATIC (Wrapped Matic). The WMATIC smart contract is a fork of the original WETH contract.
Funds are never in the custody of WrapETH. The app is only a front-end that calls functions on the smart contracts. The only 2 functions called are deposit
and withdraw
. You can view the code in the "contract" tab, and see what other functions are built into the contract.
My first major task was to replace Web3Modal with RainbowKit. Web3Modal is a JavaScript library that lets you connect apps to the blockchain. It makes it easy to connect your wallet to an app. It's makes developers' lives easier.
RainbowKit is similar to Web3Modal, but with a better UX for both users and developers. You can easily add in support for new EVM chains and various wallets. The "Connect Wallet" modal comes pre-built and styled, and you can customize it as desired.
"WAGMI makes it easy to "Connect Wallet," display ENS and balance information, sign messages, interact with contracts, and much more — all with caching, request deduplication, and persistence." (from the docs).
It's a library of pre-made hooks that uses Ethers.js under the hood. Ethers.js is a JavaScript library that lets you connect to the Ethereum blockchain. It has more features that you knew existed. WAGMI is an abstraction of Ethers.js.
An example of a WAGMI hook is useAccount
. This returns an object that has details like the connected address, as well as boolean values such as isConnected
.
Another useful hook is useBalance
. The gets data about the connected wallet's account balance. It has a boolean property called watch
, which updates the connected balance at every new block.
This hook saved me from writing many extra lines of code.
Another set of hooks I used were usePrepareContractWrite
, useContractWrite
and useWaitForTransaction
. If you want to call "read" and "write" functions on a contract, the WAGMI docs suggest using these hooks together.
usePrepareContractWrite
is used to prepare an instance of a smart contract. It returns an object that has a key called config
. config
can get passed into the useContractWrite
hook, which is used to call smart contract functions that were extracted from usePrepareContractWrite
.
useWaitForTransaction
extracts data from useContractWrite
and returns various data such as a hash of a successful transaction.
Implementing these hooks was challenging. I've only scratched the surface of what WAGMI hooks can do. Check out the WAGMI docs to learn more.
It was my first time using the WAGMI library. To get used to the hooks, I first placed everything into the main app
component in my Next.js directory. This quickly became messy.
I tried prop drilling to pass hooks to child components of app
, but this quickly becme messy, and introduced bugs to my code. I also tried calling hooks within a useEffect
hook, but learned that you cannot nest hooks within other React hooks.
Like magic, refactoring all WAGMI hooks into custom hooks, within a /hooks
directory, helped fix many of my bugs. Placing hooks into their own files made my code more readable and easier to maintain.
The idea of build custom React hooks used to intimidate me, but hooks are simple. Basically hooks are functions with the word use
at the start of its name.
This was my second major task of the project. It was also the most challenging for me. React-Hook-Form is a JavaScript library that helps manage forms, form state and validations.
React-form-hook has some funky syntax, but the documentation is well written and has good video explanations.
The form on WrapETH only has 1 input, but it takes in a monetary value that gets sent to a smart contract. When money is involved, it's critical that strict validations are in place. A bug in the front-end can cause an unexpected loss of funds.
People will track you down and send hate mail if they lose money in your app.
Raid Guild has a Design System, which is built with Storybook and custom Chakra UI style components. Each design component aligns with the branding aesthetic that Raid Guild has in place.
My tasks were to upgrade some of the existing components and to create new ones. Chakra UI comes with pre-made React components, but it's also highly customizable.
One of my major challenges was setting up the NumberInput component, and integrating it with react-hook-form with it.
NumberInput takes in a spread of props from react-hook-form. If you're not used to Chakra, it can be confusing to figure out the correct syntax. After lots of practice and frustration, I eventually figured it out.
I received lots of help on this task. Since finishing the WrapETH project, I've been working on another project and still creating custom Chakra UI components. After more practice, I feel confident going back to Raid Guild Design-System and creating new components with less trouble.
Another challenge I faced was passing data from the form to the usePrepareContractWrite
WAGMI hook. The numeric value of Ether that users pass into the form get passed into this hook. If using JavaScript, passing numerical data to EVM blockchains is complicated.
Ethereum processes numbers in units of wei, (1 Ether is equal to 10^18 wei (source). Due to limitations that JavaScript has on integers, you need to use BigNumer to correctly pass numerical values to Ethereum smart contracts.
Figuring out the correct conversions took a bit of tinkering, but eventually I figured it out.
An important part of front-end development is giving users feedback when they perform an action. For example, if you click "submit", you probably want confirmation that the form was actually submitted
Feedback especially matters when money is involved. Your heart will skip a beat if you transfer funds, but don't get confirmation that the transaction actually gets sent.
With this in mind, I added a "toast" feature that alerts a user after a transaction has been initiated, and completed.
Chakra UI has a built in hook that triggers a toast. I changed its style on Raid Guild's Design System, and added the toast to WrapETH.
A toast gets fired off in the following scenarios:
pending deposit
pending withdrawal
successful deposit
successful withdrawal
There are custom hooks in the app called useDeposit
and useWithdraw
. Within these hooks, I added the WAGMI hooks useContractWrite
and useWaitForTransaction
.
useContractWrite
is used to write transactions to a contract. The WAGMI docs recommend using it with usePrepareContractWrite
. This prepares an instance of a contract and takes in parameters such as address
and abi
.
After a transaction has been initiated with useContractWrite
, you can use built-in logic to determine if the transaction is pending or not. The hook returns a function called onSuccess
; this gets invoked when the hook fetches data.
Within the custon useDeposit
hook, when onSuccess
gets called, the useCustomToast
hook from Design-System will be invoked. useCustomToast
displays the alert you see in the upper right corner of the following image:
Similar logic is used within the useWaitForTransaction
hook. This waits for a transaction to be processed, then returns an onSuccess
function. onSuccess
displays the "success" toast seen above.
This was my first time using TypeScript. It took me a while to get used to the syntax, but with practice I got the hang of it. This project was a great opportunity to "learn by doing." I prefer this to following tutorials nonstop.
At first I didn't know how to fix type errors, so I'd set the type to be any
. This can work to troubleshoot unrelated problems, but it negates the benefits of using TypeScript in the first place.
As I got more experience with TypeScript, it grew to like it more. It seems useful for maintaining code, especially as your codebase grows, and more people contribute.
Since finishing the WrapETH project, I've continued using TypeScript in other apps. With more practice, it'll become second nature.
Most new developers don’t think about their development environments. If you're new to tech, you likely don't even know what an environment is. I didn't.
Towards the end of this project, random package manager issues appeared. These were major blockers. I didn’t even know where begin troubleshooting. Thanks to a mentor, I was able to get help troubleshooting these issues. (More on mentors later in this post).
I cannot remember all of the bugs I fixed, but here are some of my notes:
The first iteration of WrapETH was build using Node version 16.15. I was using Node version 18. Using different versions lead to bugs that were hard to troubleshoot. This can best avoided by… using different versions of Node.
Node Version Manager (NVM) solves this.
With NVM, you can run multiple versions of Node in separate terminals. For example, if you're building a new project, “Project A, ”you might want to run Node version 18. If you’re simultaneously working on “Project B,” you might want Node version 16. Without NVM, it’ll be a pain to manage different versions.
Path ($PATH
) is a list of directories that your system uses, to search for anything that you type on the command line. I cannot explain this topic deeply because I don't fully understand it yet.
After troubleshooting bugs that were related to my dependency tree, it was determined that deleting and reinstalling my OS was a logical next step.
When I first started learning software development, I followed many YouTube tutorials. Often I’d install things that altered my path, and I didn’t know why. Often times these changes didn't solve my issues. This lead to a Path file so confusing, Satoshi Nakamoto wouldn't even make sense of it.
Reinstalling Mac OS fixed many of the dependency issues I was facing. This helped me understanding some of the reasons why many developers use a virtual machine for their projects. You can change your virtual machine environment in isolation from your regular computer's setup.
After reinstalling everything, I followed this guide ”Setting Up a New MacBook for JavaScript Development which worked well.
Merge conflicts and other git issues are a pain point for me (and most developers).
Another person and I sometimes worked on the same branch. This caused merge conflicts. Git gave feedback that I needed to rebase. I botched some of the rebases, but I managed to work around it by switching between branches and copy pasting. This got messy and wasn't ideal.
The person who mentored me through this project recommended I try Git Tower, a visual tool for git. In the future I might write about this tool after using it more.
Before working on WrapETH I've never used git stash
. Sometimes Git recommends this command if you're trying to commit changes, but your branch isn't up to date with the latest changes.
git stash
saves the current version of your working branch. You can later apply your changes from the stash onto whatever branch you'd like.
Pro-tip (for git newbies): Name your stashes with the command: git stash save 'name-stash'
. Naming a stash isn't required, but if you have too many stashes saved, you’ll get confused.
I learned that VS Code has a tab on the left side called 'Source Control'. This has visual helpers such as: commits, branches, remotes, stashes and more.
If you click the 'STASHES' dropdown, you'll see your stashes. Right clicking on a stash will give you several options, for example 'apply stash'.
This project was part of my apprenticeship at Raid Guild. Before becoming a full member of the DAO, you must be an apprentice first. The purpose of the apprenticeship is to gauge skill level, and alignment with other members. Once a full member, you can vote on proposals that affect the direction of the DAO.
To learn about my earlier experience at Raid Guild, read my article: "My Experience as a Raid Guild Apprentice".
The apprenticeship has certain terms on it. For example, the apprenticeship must last a minimum of 60 days before becoming a full member, as well as complete a project.
One of the most valuable parts my apprenticeship experience is receiving mentorship. This has lowered my learning curve. Having access to a mentor is a time saver because you can ask for help when you’re stuck. I ask a lot of questions, but I'm always working to balance asking for help, vs. struggling to find a solution by myself.
Mentorship is a use case for DAOs. I encourage all beginner (and all level) developers to join a DAO for this reason. Mentorship is a 2 way street. The mentee learns faster, and the mentor gains a better understand by explaining to someone else.
Raid Guild is a strong network of talented people who are on the cutting-edge of Web3. Learn more about Raid Guild here.
Working on WrapETH was both challenging and a great learning experience. I got practice reading documentation, implementing new libraries and building interesting tools.
By using many different libraries for the first time, I realized that many libraries are similar; it's all JavaScript. If you understand data types and data structures, you can figure out most libraries.
WrapETH currently supports these networks: Ethereum mainnet, Gnosis (XDAI), Polygon, Arbitrum, Optimism, Goerli and Sepolia testnets.
There are no fees (other than network fees), so try it out next time you need to wrap and unwrap ETH. I've used it several times on Ethereum mainnet, which was the most rewarding part of the experience.
WrapETH GitHub.