<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>starrdev</title>
        <link>https://paragraph.com/@starrdev</link>
        <description>Web Developer @ AirSwap // Building DeFi // React, TypeScript, Tailwind, Frontend Developer.</description>
        <lastBuildDate>Sat, 04 Apr 2026 09:11:08 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>starrdev</title>
            <url>https://storage.googleapis.com/papyrus_images/495a4b45b04e25d0290a0e6e09ba9b860f83be2be038661323ef1a96f8fc32ef.jpg</url>
            <link>https://paragraph.com/@starrdev</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[Building the AirSwap Debugger App: A Technical Overview]]></title>
            <link>https://paragraph.com/@starrdev/building-the-airswap-debugger-app-a-technical-overview</link>
            <guid>h6sM4F2riWtbqzQUTZT8</guid>
            <pubDate>Fri, 15 Dec 2023 16:30:44 GMT</pubDate>
            <description><![CDATA[This blog post will give a technical overview of how I built the AirSwap Debugger app. I&apos;ll discuss the code, challenges faced, and lessons learned. Going forward, I’ll sometimes refer to the app as airswap-debugger. Since I started contributing to AirSwap, this was the second major app that I worked on from its inception, and the first web app that I’ve pushed most of the code to GitHub! airswap-debugger is a web app that helps AirSwap market-makers debug their API connections. GitHub: ...]]></description>
            <content:encoded><![CDATA[<p>This blog post will give a technical overview of how I built the AirSwap Debugger app. I&apos;ll discuss the code, challenges faced, and lessons learned.</p><p>Going forward, I’ll sometimes refer to the app as <code>airswap-debugger</code>.</p><p>Since I started contributing to AirSwap, this was the second major app that I worked on from its inception, and the first web app that I’ve pushed most of the code to GitHub!</p><p><code>airswap-debugger</code> is a web app that helps AirSwap market-makers debug their API connections.</p><p>GitHub: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/airswap/airswap-debugger">https://github.com/airswap/airswap-debugger</a></p><p>App: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://check.airswap.eth.limo/">https://check.airswap.eth.limo/</a></p><h2 id="h-what-is-the-airswap-debugger" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>What is the AirSwap Debugger?</strong></h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/774d65a2d5d7b9deac69694af898d20d693f16ffe90fd471fe33c6fc78fa9cd8.png" alt="The AirSwap OTC platform relies on makers to facilitate orders" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">The AirSwap OTC platform relies on makers to facilitate orders</figcaption></figure><p>To understand this app, first you must understand what a “maker” is. AirSwap makers are liquidity providers for the trading platform.</p><p>Makers run web servers that implement APIs like RFQ and LastLook. To build these APIs, makers provide JSON objects with certain values.</p><p>To learn more about makers, RFQ and LastLook, read the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://about.airswap.io/technology/makers"><strong>official documentation</strong></a>.</p><p><code>airswap-debugger</code> is a web app where AirSwap makers can debug their JSON objects and server URLs. This will be covered in more detail later in this blog post.</p><h2 id="h-tech-stack" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Tech Stack</h2><p>This app is built using TypeScript, JavaScript, React, and TailwindCSS, with blockchain connections facilitated by the <code>viem</code> and <code>wagmi</code> libraries. The app was initialized with Vite.</p><p><code>viem</code> and <code>wagmi</code> provide hooks and functions that make it easy to connect your app to EVM blockchains like Ethereum. For more details on how these libraries work, check out some of my old blog posts like “<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/starrdev.eth/ACRuWBd-jArASW50cTwG1ECftUCC-AqxUnVVVT-jnjE">Creating Seamless Web3 Wallet Connections in Your DApp Using Wagmi Hooks</a>”.</p><h2 id="h-how-the-debugger-app-works" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">How The Debugger App Works</h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/366aac2dce1f4b856d7c67aa4db76909079b3037bc7fbd786a063e77326daddc.png" alt="To use the app, paste an OTC order URL into the textarea. Order details will be displayed in the left column, and issues with the order URL will be displayed in the right column" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">To use the app, paste an OTC order URL into the textarea. Order details will be displayed in the left column, and issues with the order URL will be displayed in the right column</figcaption></figure><p>There are 2 main functions in the app: <code>validateJson</code> and <code>check</code>.</p><ol><li><p><code>validateJson</code>: this is a JavaScript function that uses TypeScript. This checks a user’s JSON input on the front-end of the application. If these validations fail, the <code>check</code> function won’t run.</p></li><li><p><code>check</code>: this function exists on the AirSwap <code>SwapERC20</code> smart contract, and requires 11 different inputs to run without errors. This is the function that checks whether or not a JSON is valid.</p></li></ol><h2 id="h-validate-json-function" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Validate JSON function</h2><p><code>validateJson</code> is a JavaScript function that uses TypeScript to validate the shape and contents of the user’s JSON input. If these validations fail, the <code>check</code> function won’t run.</p><p>For non-developers, errors returned by <code>check</code> can be vague if any values are missing. This is why the <code>validateJson</code> was implemented on the front-end app.</p><p><code>validateJson</code> has 3 major checks:</p><ul><li><p>JSON must be in valid syntax</p></li><li><p>Specific JSON key-value pairs are present</p></li><li><p>JSON values are in the correct format. For example, some values must be a valid ERC20 addresses. <code>validateJson</code> checks this by using the <code>isAddress</code> function, which is imported into the React component from the <code>viem</code> library</p></li></ul><p><code>validateJson</code> has several <code>if..else</code> code blocks which run validations. If any part of the JSON is invalid, an error gets pushed into an array called <code>errorsList</code> and returned by the function.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/130becbc88cf51b38f74f45aa2e5b84cc22737396aff06a31e29c604792771ca.png" alt="Code snippet from the validateJson function" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Code snippet from the validateJson function</figcaption></figure><p>If there are no errors, <code>validateJson</code> returns <code>false</code>. Otherwise it returns a list of errors. We’ll revisit this <code>errorsList</code> variable later in the post.</p><h2 id="h-check-function" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Check Function</h2><p>The <code>check</code> function lives on the AirSwap <code>SwapERC20</code> <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0x0C9b31Dc37718417608CE22bb1ba940f702BF90B#readContract">smart contract</a>. <code>airswap-debugger</code> is just a UI that makes it easier to use this function.</p><p><code>check</code> validates the following inputs:</p><ol><li><p>nonce</p></li><li><p>expiry</p></li><li><p>signer wallet</p></li><li><p>signer token</p></li><li><p>signer amount</p></li><li><p>sender token</p></li><li><p>sender amount</p></li><li><p>sender wallet</p></li><li><p>transaction signatures (3 separate values: “v”, “r” and “s”)</p></li></ol><p>The AirSwap protocol collects revenue from trading fees, so it’s critical that makers are able to validate their technical setups.</p><h2 id="h-ui-components" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">UI Components</h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/24f88368544525d83f5b984bcac5774e82dc14311bbe7a46519060bcd44403e8.png" alt="File component structure" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">File component structure</figcaption></figure><p>The main React components include: <code>App</code>, <code>Toggle</code>, <code>JsonForm</code>, <code>UrlForm</code>, <code>Errors</code>, <code>Selector</code> and <code>Dialogue</code>. There are some smaller components that aren’t important to go over, for example the header.</p><h2 id="h-app" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">App</h2><p>This is the main entry point of the app. Here’s a breakdown of the different sections of the <code>App</code> component.</p><h3 id="h-imports" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Imports:</strong></h3><p>At the top of the component you’ll see various imports from <code>React</code>, <code>Wagmi</code>, <code>@airswap/libraries</code>, and various hooks and components from other files.</p><h3 id="h-react-state-hooks" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">React State Hooks</h3><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/cd4da5ddc0d7b525d067663f2eb1ed9e30920e06d8ff95e2eba085ee3ffd1516.png" alt="Code snippet of all the useState hooks used" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Code snippet of all the useState hooks used</figcaption></figure><p><code>airswap-debugger</code> utilizes React’s <code>useState</code> hook. Here’s an overview of each state variable:</p><ul><li><p><code>inputType</code>: this can either be <code>json</code> or <code>url</code>. Depending on which value it is, different forms will render, and different functions will run.</p></li><li><p><code>jsonString</code>: when a user pastes a JSON, it’s a string that gets saved to this variable. Later on, <code>jsonString</code> gets parsed into a valid JSON object, and stored in a different variable.</p></li><li><p><code>urlString</code>: when a user pastes a server URL into the form, it gets saved into this variable. This variable gets passed as a prop down to the <code>Toggle</code> component, and used in a click handler.</p></li><li><p><code>parsedJson</code>: This variable gets set after a user presses the submit input button. The JSON gets parsed in the native JavaScript function <code>JSON.parse</code>.</p></li><li><p><code>decompressedJson</code>: when a user enters a server URL, the app decompresses it into a JSON object and saves it into this variable. This takes place in the <code>Dialog</code> component.</p></li><li><p><code>swapContractAddress</code>: this variable stores the smart contract address of AirSwap’s <code>SwapERC20</code> address. We need the correct address so that <code>wagmi</code> is able to call the correct contract functions.</p></li><li><p><code>selectedChainId</code>: if a user selects a chain ID from the selector, it will be stored here. This is different than if a user hard-codes a chain ID into a JSON object. This will be covered in more detail later in the <code>Selector</code> component section.</p></li><li><p><code>errors</code>: this is a list of errors if a JSON or server URL is invalid.</p></li><li><p><code>isEnabledCheck</code>: this is a boolean value that serves as an on/off switch for the <code>check</code> function. It helps prevents the app from rendering outdated data.</p></li><li><p><code>isNoErrors</code>: this is a boolean value that gets set to <code>true</code> if no errors were found. If <code>true</code>, and if <code>isEnabledCheck</code> is also true, a special message will be displayed to the user. If <code>isEnabledCheck</code> is false, then the <code>check</code> function hasn’t yet run. In this case we show the user nothing until they run the debugger.</p></li></ul><h3 id="h-custom-hooks" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Custom Hooks</h3><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c51c696d6eb85e1e919d72ffd463a0c70e776d7eb1e92004220b688ae2a3dbc5.png" alt="Code snippet of useDecompressedOrderFromUrl hook" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Code snippet of useDecompressedOrderFromUrl hook</figcaption></figure><p><code>useDecompressedOrderFromUrl</code>: this hook was build to convert a server URL into JSON. The library <code>@airswap/util</code> library has a function called <code>decompressFullOrderERC20</code>. This accepts a server URL string and converts it to human-readable JSON.</p><p><code>useJsonValues</code>: This function takes in a JSON as an object. It parses the JSON, converts each of its vales into the correct format, and returns its individual values in an object. The correct formats are determined by what the <code>check</code> function require.</p><h3 id="h-smart-contract-interaction" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Smart Contract Interaction</strong>:</h3><p><code>useContractRead</code> hook from <code>wagmi</code> is utilized 5 times. The first time is in the <code>check</code> function.</p><p>The other 4 return values are: <code>protocolFee</code>, <code>domainName</code>, <code>domainChainId</code>, and <code>domainVersion</code>. These values are specific to the deployed <code>SwapERC20</code> contract. <code>SwapERC20</code> is deployed on various chains, including Mainnet, BSC, Avalanche, and others. On different chains, <code>domainChainId</code> would change. Upgraded contract versions might have different <code>domainVersion</code> values.</p><p>If a user’s JSON object has errors, it’s possible that the JSON is not using the correct value for one of these 4 values. This feedback is returned to the user.</p><h3 id="h-checkfunctionargs" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><code>checkFunctionArgs</code>:</h3><p>This is a list of inputs that we pass into the <code>args</code> property of <code>useContractRead</code> for the <code>check</code> function.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0c87fc3574d0b0ceac9879b9c2389380632aeec53839fc9acc32479b0b828454.png" alt="checkFunctionArgs utilizes TypeScript for validations" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">checkFunctionArgs utilizes TypeScript for validations</figcaption></figure><h3 id="h-event-handlers" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Event Handlers</strong>:</h3><p>There are 2 handlers that control the forms on the app: <code>handleChangeTextAreaJson</code> and <code>handleChangeTextAreaUrl</code>. These function similarly, and get called on each of the <code>textarea</code> HTML elements within the <code>JsonForm</code>, and <code>UrlForm</code>. There’s also the <code>handleSubmit</code>, which will be explained next.</p><h3 id="h-submit-function-handlesubmit" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Submit Function (<code>handleSubmit</code>)</h3><p>Several smaller functions and hooks make up the submit function. The function begins by initializing the following <code>useState</code> hooks to their default values: <code>setParsedJson</code>, <code>setIsEnabledCheck</code>, <code>setErrors</code>, <code>setIsNoErrors</code>.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/32cafebd2f0208deebd6e0f5ec5ecd7b929f158477fffe5cebdef8d7b23de90a.png" alt="Clicking this button on the UI runs the handleSubmit function" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Clicking this button on the UI runs the handleSubmit function</figcaption></figure><p>The following functions make up the <code>handleSubmit</code> function:</p><h3 id="h-validateinputs" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><code>validateInputs</code></h3><p>This checks for blank inputs. If inputs are blank, <code>handleSubmit</code> returns nothing and ends.</p><h3 id="h-handlejsonsubmission" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><code>handleJsonSubmission</code></h3><p>If input type is <code>json</code>, this function runs and parses a JSON string into valid JSON format. It then sets the <code>parsedJson</code> variable equal to this value. Lastly, it runs the function <code>checkSmartContractError</code>.</p><h3 id="h-checksmartcontracterror" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><code>checkSmartContractError</code></h3><p>This accepts in an argument <code>errorCheck</code>, which gets returned from the <code>check</code> function. This function handles unreadable errors returned from the smart contract, and makes them more human-readable.</p><h3 id="h-handleurlsubmission" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><code>handleUrlSubmission</code></h3><p>This is similar to <code>handleJsonSubmission</code>, but for server URLs.</p><h3 id="h-validateinputs" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><code>validateInputs</code></h3><p>This function checks that a user input isn’t blank. Otherwise the smart contract <code>check</code> function won’t run.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/45d6c32b70b995673b2bc44196dacfdf4609db5b22521d73d2bb9d5ce6f4d8ec.png" alt="If the user tries submitting a blank form, the app will return an error" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">If the user tries submitting a blank form, the app will return an error</figcaption></figure><h3 id="h-useeffect-hook-1" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><code>useEffect</code> Hook #1</h3><p>There are 2 <code>useEffect</code> hooks in the <code>App</code> component. Here’s how the first one works:</p><ol><li><p>The hook first gets triggered when the <code>parsedJson</code> value changes. Recall that running <code>handleSubmit</code> updates <code>parsedJson</code>.</p></li><li><p>At the start of this <code>useEffect</code> hook, the <code>validateJson</code> function runs. <code>validateJson</code> returns a boolean value. If the value returned is <code>true</code>, there are errors which will get pushed into the <code>errors</code> array.</p></li><li><p>Next, the function <code>getOutputErrorsList</code> runs. This takes in an argument <code>checkFunctionData</code>, which is an array of errors returned from the <code>check</code> function. These errors are unreadable, in hex format. <code>getOutPutErrorsList</code> maps over this array of hex values, then uses the <code>hexToString</code> function, which is imported from <code>viem</code>, to decode the hex formats into human-readable strings.</p></li><li><p>Next in the <code>useEffect</code> hook is the function <code>displayErrors</code>. This parses errors returned from the smart contract, and makes them human-readable. For example, if an error contains <code>&apos;NonceAlreadyUsed&apos;</code>, then <code>displayErrors</code> will switch that into the error message: <code>&apos;Nonce: the nonce entered is invalid&apos;</code>.</p></li><li><p>Next is the function <code>handleFormattedListErrors</code>. This cleans up any errors returned from the smart contract that have too much blank space. These errors get pushed as strings into the <code>errors</code> array.</p></li><li><p>Lastly, the <code>useEffect</code> hook checks if there are no errors in the <code>errors</code> array. If no errors, the state variable <code>isNoErrors</code> gets set to <code>true</code>.</p></li></ol><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d3c0020d4ccffd8a8eb8890e096740c4b99ae5c991d9b68ce8c8a84379473e1b.png" alt="If there are no errors, this special message is displayed " blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">If there are no errors, this special message is displayed</figcaption></figure><h3 id="h-useeffect-hook-2" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><code>useEffect</code> Hook #2</h3><p>This hook checks for a change in <code>chainId</code>, the blockchain network ID. When a change is detected, the function <code>getAddress</code> from the AirSwap <code>SwapERC20</code> contract gets called, and sets the correct address in the <code>swapContractAddress</code>variable. <code>swapContractAddress</code> gets passed into the <code>useContractRead</code> hook from <code>wagmi</code>, which is used to call the <code>check</code> smart contract function.</p><h2 id="h-forms-and-toggle" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Forms &amp; Toggle</h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/136c01fa5a5f9e7cefc2a90dfaf426d2eb528ebdd59060ca8bcfaea73d3d431c.png" alt="You can toggle between JSON and URL forms" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">You can toggle between JSON and URL forms</figcaption></figure><p>We have 2 forms on the app: <code>jsonForm</code> and <code>urlForm</code>. Each form lives in separate React components. Both forms correspond to the relevant functions mentioned above. The <code>Toggle</code> component contains 2 buttons, which lets you select the form you want to use.</p><h2 id="h-errors" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Errors</h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d84fd01a47ce98ccf24f78accc13e000bcd75409b4b73859ccd7e7070573acaa.png" alt="When a user enters an invalid JSON, errors are displayed on the right" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">When a user enters an invalid JSON, errors are displayed on the right</figcaption></figure><p>This component’s purpose is to render errors that are returned from the smart contract, or any of the following functions: <code>validateJson</code>, <code>checkSmartContractErrors</code>, or <code>displayErrors</code>.</p><p>The <code>Errors</code> component either displays nothing, a list of errors, or a message that reads “🎊 No errors found! 🎊”.</p><p><code>airswap-debugger</code> is mobile responsive. On tablets and larger screen, there are 2 columns: forms on the left, and <code>Errors</code> on the right. On mobile devices, the UI displays 1 column: forms on top, and <code>Errors</code> on the bottom.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/55ec680a0ccebfeba44a880185c6e41c5d2fe369738ac793a33ecc219377aa1a.png" alt="On mobile devices, the app displays as a single column" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">On mobile devices, the app displays as a single column</figcaption></figure><h2 id="h-selector" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Selector</h2><p>The selector has several blockchain networks (chain IDs) a user can choose. Users can choose a <code>chainId</code> from the selector, or optionally hard-code it into their JSON objects. If <code>chainId</code> is hard-coded into a JSON, it’ll override the selector option, but not vice-versa.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0eca7850f0640fb8964ad38462a40231d72af01e4bf49b6c0e0e5da2ba264541.png" alt="The selector lets users choose from a variety of networks" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">The selector lets users choose from a variety of networks</figcaption></figure><p>Building the selector was fun and challenging. Styling a native HTML selector with CSS is more difficult than it looks. For this reason, I ended up using the <code>RadixSelect</code> component from <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.radix-ui.com/primitives/docs/components/select">Radix-UI</a>. Radix-UI is easy to use, and is compatible with TailwindCSS!</p><h2 id="h-dialog-component" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Dialog Component</h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/49e3805e8e58f93ed4dba3f778e1adba3b6b88146616be42426963423c84b92d.png" alt="The dialog is a modal which displays the decompressed URL" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">The dialog is a modal which displays the decompressed URL</figcaption></figure><p><code>dialog</code> is a modern HTML element, and its common use case is for building modals. I built the dialog for when users enter a server URL. The JSON from the decompressed URL gets rendered onto the dialog, as seen in the image above.</p><h2 id="h-challenges-i-faced-while-building-the-airswap-debugger" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Challenges I Faced While Building The AirSwap Debugger</h2><h3 id="h-managing-state-among-react-components" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Managing state among React components</strong></h3><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d84fd01a47ce98ccf24f78accc13e000bcd75409b4b73859ccd7e7070573acaa.png" alt="Getting the errors component to display the correct errors took some trial &amp; error" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Getting the errors component to display the correct errors took some trial &amp; error</figcaption></figure><p>As the app grew in complexity, it became hard to manage the <code>errors</code> array state variable. Various functions, hooks, and components update <code>errors</code>, and sometimes I had issues with duplicate values being pushed into the array. Other times the array wasn’t being reset to a blank array when I wanted it to.</p><p>These issues happened over the development cycle of the app. At this point I cannot recall every time it happened. To fix these issues, I used <code>console.log</code> a lot.</p><p>A common process I use to track down bugs is locate the function that is causing the error. Within that function, I’ll put <code>console.log</code> statements after every line, then hunt down the exact issue. Simple, yet effective!</p><h3 id="h-error-buffer-buffer-not-found" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Error: <code>Buffer: buffer not found</code></h3><p>This was an error that came up, and similar ones as well. It was related to Vite not having polyfills. In JavaScript, polyfills are scripts that ensure cross-browser compatibility. Some older browsers may lack support for certain features, and polyfills bridge the gap, allowing newer features to function in these legacy browsers.</p><p>To fix this error, I found an npm called <code>vite-plugin-node-polyfills</code>. I added this to my project, and updated my <code>vite.config.js</code> file like so:</p><pre data-type="codeBlock" text="export default defineConfig({
  plugins: [
    react(),
    nodePolyfills({
      include: [&apos;path&apos;, &apos;stream&apos;, &apos;util&apos;],
      exclude: [&apos;http&apos;],
      globals: {
        Buffer: true,
        global: true,
        process: true,
      },
      overrides: {
        fs: &apos;memfs&apos;,
      },
      protocolImports: true,
    }),
  ],
});
"><code>export <span class="hljs-keyword">default</span> <span class="hljs-title function_ invoke__">defineConfig</span>({
  <span class="hljs-attr">plugins</span>: [
    <span class="hljs-title function_ invoke__">react</span>(),
    <span class="hljs-title function_ invoke__">nodePolyfills</span>({
      <span class="hljs-attr">include</span>: [<span class="hljs-string">'path'</span>, <span class="hljs-string">'stream'</span>, <span class="hljs-string">'util'</span>],
      <span class="hljs-attr">exclude</span>: [<span class="hljs-string">'http'</span>],
      <span class="hljs-attr">globals</span>: {
        <span class="hljs-attr">Buffer</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">global</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">process</span>: <span class="hljs-literal">true</span>,
      },
      <span class="hljs-attr">overrides</span>: {
        <span class="hljs-attr">fs</span>: <span class="hljs-string">'memfs'</span>,
      },
      <span class="hljs-attr">protocolImports</span>: <span class="hljs-literal">true</span>,
    }),
  ],
});
</code></pre><h3 id="h-designing-the-ux" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Designing the UX</h3><p>Other than small personal projects, this was the first time I designed an entire UX! I took inspiration from existing AirSwap apps, and used similar color schemes and design patterns.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e843f9ff82cded18dde1dfac33b6628e99347d774d8a10764d38c89a530e8a9d.png" alt="For airswap-debugger, I took inspiration from airswap-voter-rewards" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">For airswap-debugger, I took inspiration from airswap-voter-rewards</figcaption></figure><p>I took into account different screen sizes, and the best way to display data to the user. One example was deciding between displaying the decompressed JSON to the user in a prompt, or a dialog. Prompts are harder to close than dialogs, so I opted for a dialog. The design was more congruent with the rest of the app, and the dialog opens and closes smoothly.</p><h3 id="h-navigating-a-project-solo-start-to-finish" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Navigating a project solo, start to finish</h3><p>Typically I prefer to work with a group. When I originally took on this project, I expected it to be a simple app that would take a week or 2. Instead, it out to be a good 6 week challenge!</p><p>The journey of building this app came with a lot of self-doubt. At times I thought that I wouldn’t be able to finish it by myself. Other people would have helped me if I needed, but I wanted to challenge myself and see what I was capable of.</p><p>My confidence as a software developer has improved because of this. Building the <code>airswap-debugger</code> app from the ground up is my proudest coding achievement so far.</p><h2 id="h-conclusion" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Conclusion</h2><p>This blog post is a technical write-up of a debugger app that I built for AirSwap. It’s a DeFi tool to help AirSwap makers debug their servers.</p><p>If you’ve read this far, thank you for your time. I wrote this blog post for several reasons:</p><ul><li><p>Enhance my writing skills</p></li><li><p>Build my personal brand, showcase my abilities, and build in public</p></li><li><p>Improve my ability to organize thoughts</p></li><li><p>Create valuable resources for others</p></li><li><p>Establish future sentimental value (it&apos;ll be enjoyable reading this post in 5 years)</p></li></ul><p>During the writing process, I reviewed each line of code, allowing me to audit my work and identify areas for improvement.</p><p>Now that my thoughts are written down, it’ll be easier to talk about this project in person (ask me about it next time you see me!)</p><p>Check out the GitHub repository for a deeper look into the code: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/airswap/airswap-debugger">https://github.com/airswap/airswap-debugger</a></p><p>Here’s the deployed app. <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://check.airswap.eth.limo/">https://check.airswap.eth.limo/</a></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0b857cffdf081e64bc5d72651ad6533da44519f269b0216cd69a913b0620ceb9.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure>]]></content:encoded>
            <author>starrdev@newsletter.paragraph.com (starrdev)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/a07cf9b47693847c9000679d9e7c116beaf57c74e38683996ca0eeb9cb4da0ec.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Creating Seamless Web3 Wallet Connections in Your DApp Using Wagmi Hooks]]></title>
            <link>https://paragraph.com/@starrdev/creating-seamless-web3-wallet-connections-in-your-dapp-using-wagmi-hooks</link>
            <guid>APajJJwgTUNVEvrclCCT</guid>
            <pubDate>Mon, 23 Oct 2023 18:36:14 GMT</pubDate>
            <description><![CDATA[Table of ContentsIntroductionWhat is WagmiComponent StructureMainWalletConnectionWalletConnectionModalUserAccountDetailMain Config HooksWalletConnection ComponentWalletConnectionModal ComponentRabby WalletConclusionIntroductionA view of the Web3 wallet connection implementation we&apos;ll be discussingFor a Web3 application, a wallet connection is essential. It&apos;s the equivalent of a user signing in with a username and password, except using a wallet as a form of identification. By connec...]]></description>
            <content:encoded><![CDATA[<h2 id="h-table-of-contents" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Table of Contents</h2><ol><li><p>Introduction</p></li><li><p>What is Wagmi</p></li><li><p>Component Structure</p><ol><li><p>Main</p></li><li><p>WalletConnection</p></li><li><p>WalletConnectionModal</p></li><li><p>UserAccountDetail</p></li></ol></li><li><p>Main Config Hooks</p></li><li><p>WalletConnection Component</p></li><li><p>WalletConnectionModal Component</p></li><li><p>Rabby Wallet</p></li><li><p>Conclusion</p></li></ol><h2 id="h-introduction" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Introduction</h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/de58388039926c98a7a5b42dfa4e9462eab0ee5e6e83cbafdbef1e936d89e453.png" alt="A view of the Web3 wallet connection implementation we&apos;ll be discussing" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">A view of the Web3 wallet connection implementation we&apos;ll be discussing</figcaption></figure><p>For a Web3 application, a wallet connection is essential. It&apos;s the equivalent of a user signing in with a username and password, except using a wallet as a form of identification. By connecting your wallet to an app, you have the ability to sign transactions and interact with the app in a meaningful way.</p><p>This blog post will cover how to implement a Web3 wallet connection. It’s how it was implemented on the AirSwap Member Dashboard app.</p><p>With this setup, a user clicks the “CONNECT” button, which opens a Modal containing various wallets for a user to chose from. This setup relies on TypeScript, React, and the Wagmi library for Ethereum connections.</p><h2 id="h-what-is-wagmi" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">What is Wagmi</h2><p>Wagmi is a collection of React Hooks containing everything you need to start working with Ethereum. It makes it easy to &quot;Connect Wallet,&quot; display ENS and balance information, sign messages, interact with contracts, and much more — all with caching, request deduplication, and persistence. You can see more on the official docs: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://wagmi.sh">https://wagmi.sh</a></p><h2 id="h-component-structure" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Component structure</h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/b9b1f8f3d85070945125345bc1bd8b7593c048cc89d44d998524e6c6bc183a7a.png" alt="File structure of the Wallet Connection components, except main.tsx" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">File structure of the Wallet Connection components, except main.tsx</figcaption></figure><p>There are 4 major components involved:</p><ol><li><p><strong>Main</strong> - <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/airswap/airswap-voter-rewards/blob/main/src/main.tsx">Code</a>. This is the main React component of the project, but it contains 2 hooks with important configuration data.</p></li><li><p><strong>WalletConnection</strong> - <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/airswap/airswap-voter-rewards/blob/main/src/features/chain-connection/WalletConnection.tsx">Code</a>. This component is responsible for rendering the “CONNECT” button, and contains logic for whether or not to render the Wallet Connection Modal.</p></li><li><p><strong>WalletConnection Modal</strong> - <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/airswap/airswap-voter-rewards/blob/main/src/features/chain-connection/WalletConnectionModal.tsx">Code</a>. This component is responsible rendering a selection of wallets that a user can use to connect to the application.</p></li><li><p><strong>UserAccountDetail</strong> - <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/airswap/airswap-voter-rewards/blob/main/src/features/chain-connection/UserAccountDetail.tsx">Code</a>. This component it’s mostly for aesthetic purposes. It’s used to render a user’s ENS avatar, and a disconnect button.</p></li></ol><h2 id="h-main-config-hooks" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Main config hooks</h2><p>In our main React file, Main.tsx, there are 2 main hooks that we use to set up our configuration, and 1 wrapper that we place around our entire app.</p><p><strong>configureChains</strong></p><p>The first hook is called <code>configureChains</code>. This allows you to set up specific chains you want your app to use, as well as setting up your RPC providers. There’s also optional functionality in this hook that lets you setup batched multi-calls, but that’s beyond the scope of this blog post.</p><pre data-type="codeBlock" text="const { chains, publicClient, webSocketPublicClient } = configureChains(
  [mainnet, goerli, avalanche, bsc, polygon],
  [
    infuraProvider({ apiKey: import.meta.env.VITE_INFURA_API_KEY || &quot;&quot; }),
    publicProvider(),
  ],
);
"><code>const { chains, publicClient, webSocketPublicClient } <span class="hljs-operator">=</span> configureChains(
  [mainnet, goerli, avalanche, bsc, polygon],
  [
    infuraProvider({ apiKey: <span class="hljs-keyword">import</span>.meta.env.VITE_INFURA_API_KEY <span class="hljs-operator">|</span><span class="hljs-operator">|</span> <span class="hljs-string">""</span> }),
    publicProvider(),
  ],
);
</code></pre><p>In the code snippet above, <code>configureChains</code> takes in arguments of an array of chains, and an array of RPC providers. The array of chains includes values like <code>mainnet</code>, <code>goerli</code>, and <code>avalanche</code>. This means that our app will support those chains. The value <code>infuraProvider</code> configures our app to use Infura as an RPC provider.</p><p><code>configureChains</code> returns several values which get passed into the <code>createConfig</code>hook.</p><p><strong>createConfig</strong></p><p><code>createConfig</code> is a hook where you can setup more options, such as the connectors you want to use. “Connectors” in this context is a technical term for wallets. You can pass in popular options such as MetaMask and Coinbase Wallet. <code>createConfig</code> also allows you to select options such as auto-connect, or to pass in a web socket connection.</p><pre data-type="codeBlock" text="const config = createConfig({
  autoConnect: true,
  publicClient,
  webSocketPublicClient,
  queryClient,
  connectors: [
    new MetaMaskConnector({ chains }),
    new InjectedConnector({ chains }),
    new CoinbaseWalletConnector({
      chains,
      options: {
        appName: &quot;AirSwap Member Dashboard&quot;,
        appLogoUrl: AirSwapLogo,
      },
    }),
    new WalletConnectConnector({
      chains,
      options: {
        projectId: import.meta.env.VITE_WALLETCONNECT_ID,
      },
    }),
  ],
});
"><code>const config <span class="hljs-operator">=</span> createConfig({
  autoConnect: <span class="hljs-literal">true</span>,
  publicClient,
  webSocketPublicClient,
  queryClient,
  connectors: [
    <span class="hljs-keyword">new</span> MetaMaskConnector({ chains }),
    <span class="hljs-keyword">new</span> InjectedConnector({ chains }),
    <span class="hljs-keyword">new</span> CoinbaseWalletConnector({
      chains,
      options: {
        appName: <span class="hljs-string">"AirSwap Member Dashboard"</span>,
        appLogoUrl: AirSwapLogo,
      },
    }),
    <span class="hljs-keyword">new</span> WalletConnectConnector({
      chains,
      options: {
        projectId: <span class="hljs-keyword">import</span>.meta.env.VITE_WALLETCONNECT_ID,
      },
    }),
  ],
});
</code></pre><h2 id="h-walletconnection-component" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">WalletConnection Component</h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e0ec066729d98535950a387ab73e2917787bd094ade14f85f935eaf381b6af6b.png" alt="Clicking this “connect” button will open the WalletConnectionModal" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Clicking this “connect” button will open the WalletConnectionModal</figcaption></figure><p>The variable name of this component is <code>WalletConnection</code>. It renders 3 things:</p><ol><li><p>A “connect” button when the user isn’t connected. If the user is connected, the button shows the wallet address or ENS name.</p></li><li><p><code>WalletConnectionModal</code>, which contains is where the user can select a wallet to connect.</p></li></ol><pre data-type="codeBlock" text="{showConnectionModal &amp;&amp; (
   &lt;WalletConnectionModal
      setShowConnectionModal={setShowConnectionModal}
   /&gt;
)}
"><code>{showConnectionModal <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> (
   <span class="hljs-operator">&#x3C;</span>WalletConnectionModal
      setShowConnectionModal<span class="hljs-operator">=</span>{setShowConnectionModal}
   <span class="hljs-operator">/</span><span class="hljs-operator">></span>
)}
</code></pre><p><code>showConnectionModal</code> gets set to <code>true</code> when a user clicks the “connect” button, but isn’t yet connected to the app.</p><ol><li><p>It renders <code>UserAccountDetail</code> if the user’s wallet is connected. Wagmi has a hook called <code>useAccount</code>, which returns a boolean value <code>isConnected</code>. We can use the variable <code>isConnected</code> to show or hide things in our JSX code.</p></li></ol><pre data-type="codeBlock" text="{isConnected &amp;&amp; (
   &lt;UserAccountDetail
      setShowUserAccountDetail={setShowUserAccountDetail}
      showUserAccountDetail={showUserAccountDetail}
   /&gt;
)}
"><code>{isConnected <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> (
   <span class="hljs-operator">&#x3C;</span>UserAccountDetail
      setShowUserAccountDetail<span class="hljs-operator">=</span>{setShowUserAccountDetail}
      showUserAccountDetail<span class="hljs-operator">=</span>{showUserAccountDetail}
   <span class="hljs-operator">/</span><span class="hljs-operator">></span>
)}
</code></pre><p><code>**handleShowConnectionModal**</code></p><p>This function gets called when a user isn’t yet connected, clicks the “connect” button, but hasn’t yet connected to a chosen wallet. This function changes the values of <code>showConnectionModal</code> and <code>showUserAccountDetail</code>. These 2 variables control the visibility of <code>WalletConnectionModal</code> and <code>UserAccountDetail</code>, respectively.</p><pre data-type="codeBlock" text="const handleShowConnectionModal = () =&gt; {
   !isConnected
      ? setShowConnectionModal(true)
      : setShowUserAccountDetail(!showUserAccountDetail);
};
"><code>const handleShowConnectionModal <span class="hljs-operator">=</span> () <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
   <span class="hljs-operator">!</span>isConnected
      ? setShowConnectionModal(<span class="hljs-literal">true</span>)
      : setShowUserAccountDetail(<span class="hljs-operator">!</span>showUserAccountDetail);
};
</code></pre><h2 id="h-walletconnectionmodal-component" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">WalletConnectionModal Component</h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/bbb283dbeff50e2d3ae005ec5a1c02b4d2d13506b7749af31fd8de6a593c7253.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>This component will be displayed after a user clicks the “connect” button. It displays various wallets that a user can use to connect to the application.</p><p>Within the JSX code of <code>WalletConnectionModal</code>, there’s an unnamed function that loops over an array of <code>connectors</code>. <code>connectors</code> is an array containing all the wallets that users can connect to, such as MetaMask or Coinbase Wallet.</p><pre data-type="codeBlock" text="{connectors
   .filter(
      (connector) =&gt;
         connector.ready &amp;&amp;
         // Don&apos;t show inject if it&apos;s MetaMask (we&apos;re already showing it)
         !(connector.id === &quot;injected&quot; &amp;&amp; connector.name === &quot;MetaMask&quot;),
      )
      .map((connector: Connector) =&gt; {
         const isInjected = connector.id === &quot;injected&quot;;
         return (
            &lt;button
               className=&quot;flex flex-row items-center rounded border border-gray-800 bg-gray-900 p-4 hover:bg-gray-800 disabled:cursor-not-allowed font-medium&quot;,
               )}
               disabled={!connector.ready}
               onClick={() =&gt; connect({ connector })}
               key={connector.id}
             &gt;
"><code>{connectors
   .filter(
      (connector) <span class="hljs-operator">=</span><span class="hljs-operator">></span>
         connector.ready <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span>
         <span class="hljs-comment">// Don't show inject if it's MetaMask (we're already showing it)</span>
         <span class="hljs-operator">!</span>(connector.id <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-string">"injected"</span> <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> connector.<span class="hljs-built_in">name</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-string">"MetaMask"</span>),
      )
      .map((connector: Connector) <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
         const isInjected <span class="hljs-operator">=</span> connector.id <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-string">"injected"</span>;
         <span class="hljs-keyword">return</span> (
            <span class="hljs-operator">&#x3C;</span>button
               className<span class="hljs-operator">=</span><span class="hljs-string">"flex flex-row items-center rounded border border-gray-800 bg-gray-900 p-4 hover:bg-gray-800 disabled:cursor-not-allowed font-medium"</span>,
               )}
               disabled<span class="hljs-operator">=</span>{<span class="hljs-operator">!</span>connector.ready}
               onClick<span class="hljs-operator">=</span>{() <span class="hljs-operator">=</span><span class="hljs-operator">></span> connect({ connector })}
               key<span class="hljs-operator">=</span>{connector.id}
             <span class="hljs-operator">></span>
</code></pre><p>The primary aspect of the function involves a <code>map</code> function that iterates over the <code>connectors</code> array and renders each one onto its own button.. Within this function there’s extra logic that checks if it’s an “injected” connector. More on injected connectors in the next section.</p><h2 id="h-rabby-wallet" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Rabby Wallet</h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/2a807b57368126267598b2eb57c6f2b3bdcb2eff4b485ef4e9937e1d600ca791.png" alt="Rabby is a formidable competitor to MetaMask" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Rabby is a formidable competitor to MetaMask</figcaption></figure><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://rabby.io">Rabby</a> is a competing wallet to MetaMask. It has more advanced features such as security warnings, and the ability to import multiple private keys into 1 installation. However, enabling Rabby presented some challenges that I’ll cover below.</p><p><strong>What is an Injected Connector?</strong></p><p>An injected connector refers to a user&apos;s default wallet. It&apos;s the wallet that a DApp will use if no other wallet is connected. If you&apos;re using Rabby, there&apos;s a switch you can toggle that sets it to your injected connector.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c912915bed5d12459ab5f72a1a75faa6c9d8b4e79130057c32b6b88860b4e5d6.png" alt="In the bottom right of the Rabby browser extension, clicking the “Flip” button will toggle it to MetaMask. Currently, Rabby is the injected connector." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">In the bottom right of the Rabby browser extension, clicking the “Flip” button will toggle it to MetaMask. Currently, Rabby is the injected connector.</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/aaf73b7fe6dd756369240b0316d7a8ebed8bd759288c114073d56e77d0e1fbf0.png" alt="Here MetaMask has been toggled, so MetaMask will be the injected connector. Clicking “Flip” will switch it back to Rabby." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Here MetaMask has been toggled, so MetaMask will be the injected connector. Clicking “Flip” will switch it back to Rabby.</figcaption></figure><p>If you look again at the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/airswap/airswap-voter-rewards/blob/main/src/features/chain-connection/WalletConnectionModal.tsx">code</a> in <code>WalletConnectionModal</code>, you’ll see 3 different things related to the injected connector:</p><ol><li><p>A boolean value called <code>injectedIsMetamask</code>, which is a React state variable. This value gets set in a <code>useEffect</code> after checking whether or not Rabby wallet is active. If <code>injectedIsMetaMask</code> is true, Rabby will be shown instead of MetaMask on the connection modal.</p></li><li><p>A <code>useEffect</code> hook checking if the connecting wallet is using Rabby. Here’s the code used for that check:</p></li></ol><pre data-type="codeBlock" text="useEffect(() =&gt; {
    const rabbyConnector = connectors.find(
      (connector) =&gt; connector.name.toLowerCase() === &quot;rabby wallet&quot;,
    );
    if (rabbyConnector &amp;&amp; !rabbyConnector.options.getProvider()._isRabby) {
      // Rabby is forwarding to metamask
      setInjectedIsMetaMask(true);
    }
  }, [connectors]);
"><code>useEffect(() <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
    const rabbyConnector <span class="hljs-operator">=</span> connectors.find(
      (connector) <span class="hljs-operator">=</span><span class="hljs-operator">></span> connector.<span class="hljs-built_in">name</span>.toLowerCase() <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-string">"rabby wallet"</span>,
    );
    <span class="hljs-keyword">if</span> (rabbyConnector <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> <span class="hljs-operator">!</span>rabbyConnector.options.getProvider()._isRabby) {
      <span class="hljs-comment">// Rabby is forwarding to metamask</span>
      setInjectedIsMetaMask(<span class="hljs-literal">true</span>);
    }
  }, [connectors]);
</code></pre><p>Note that on the <code>rabbyConnector</code> object, if the <code>getProvider</code> method does not contain <code>_isRabby</code>, then we set the value is <code>injectedIsMetaMask</code> to be <code>true</code>.</p><ol><li><p>In the JSX before the <code>map</code> function, we filter through the <code>connectors</code> array before looping over it with the <code>map</code> method. Here, we’re filtering to see if MetaMask is the injected wallet connector. It gets filtered out, because in our main.tsx component, we’re adding in MetaMask to our <code>connectors</code> array in the <code>createConfig</code> hook. (Scroll up the section “Main config hooks” to see that code snippet).</p></li></ol><h2 id="h-additional-logic-in-walletconnectionmodal" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Additional logic in WalletConnectionModal</strong></h2><p>There’s another <code>useEffect</code> hook in this component:</p><pre data-type="codeBlock" text="useEffect(() =&gt; {
    isConnected &amp;&amp; setShowConnectionModal(false);
    // close modal when WalletConnect or Coinbase modal is open
    pendingConnector &amp;&amp; setShowConnectionModal(false);
  }, [isConnected, pendingConnector, setShowConnectionModal]);
"><code>useEffect(() <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
    isConnected <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> setShowConnectionModal(<span class="hljs-literal">false</span>);
    <span class="hljs-comment">// close modal when WalletConnect or Coinbase modal is open</span>
    pendingConnector <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> setShowConnectionModal(<span class="hljs-literal">false</span>);
  }, [isConnected, pendingConnector, setShowConnectionModal]);
</code></pre><p>This code was added after a small bug was discovered with WalletConnect, Coinbase Wallet, and any other wallet that uses its own modal.</p><p>When connecting to WalletConnect, we found that the <code>WalletConnectionModal</code> was overlaying:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/5ea89b02c449b7e43c9baa74c4dba8fcd7b636ddf145567790593eb00d7fc8e2.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>The code snippet above fixed this bug, so WalletConnect (and Coinbase Wallet) rendered correctly like so:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0c45f3a791d4938da0f4846d35f0a88a4289425563eb2b858ce30191dbfc3813.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h2 id="h-useraccountdetail-component" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">UserAccountDetail Component</h2><p>This component is mostly for aesthetic purposes. There are 2 functional aspects worth mentioning:</p><ol><li><p><strong>Disconnect function:</strong> This utilizes the Wagmi hook <code>useDisconnect</code>. Here’s the code: <code>const { disconnect } = useDisconnect();</code>. Calling the <code>disconnect</code> function on the button will log out the user.</p></li><li><p><strong>ENS Avatar</strong>: this is a nice touch if a user has an ENS name associated with an address. It builds familiarity and trust with the app. Here’s the code:</p></li></ol><pre data-type="codeBlock" text="const { data: ensName } = useEnsName({ address, chainId: 1 });
const { data: avatarUrl } = useEnsAvatar({
   name: ensName,
   chainId: 1,
});
"><code>const { data: ensName } = useEnsName({ <span class="hljs-selector-tag">address</span>, chainId: <span class="hljs-number">1</span> });
const { data: avatarUrl } = useEnsAvatar({
   name: ensName,
   chainId: <span class="hljs-number">1</span>,
});
</code></pre><p>With this code in place, we can use <code>avatarUrl</code> (which is returned from the <code>useEnsAvatar</code> hook, to render a user’s avatar:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3b047cda27895b9b454759f8a05f46712c7a44d9715f3e3d16707897658f67dc.png" alt="The red arrow points to the avatar URL" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">The red arrow points to the avatar URL</figcaption></figure><h2 id="h-video-demo" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Video Demo</h2><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://vimeo.com/877284150?share=copy">https://vimeo.com/877284150?share=copy</a></p><h2 id="h-conclusion" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Conclusion</h2><p>In this post we covered a technical explanation of how to implement a Web3 wallet connection using Wagmi hooks and React.</p><p>We explored key components such as the Main Config Hooks, Wallet Connection Component, and Wallet Connection Modal, each playing a vital role in creating a seamless and secure user experience within decentralized applications.</p><p>This demonstrates the simplicity of the implementation. Let me know if my explanation wasn’t simple enough, or what I can do to improve. Thanks for reading!</p><p><strong>App</strong>: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://dao.airswap.eth.limo">https://dao.airswap.eth.limo</a></p>]]></content:encoded>
            <author>starrdev@newsletter.paragraph.com (starrdev)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/24fbf485e358be1bc5d566bf71c926256499a1b39a24b9bda0776184f8664018.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Creating a Staking Feature with React and TypeScript]]></title>
            <link>https://paragraph.com/@starrdev/creating-a-staking-feature-with-react-and-typescript</link>
            <guid>fjUkaCv0ZmD3Xkk3yUnY</guid>
            <pubDate>Sat, 14 Oct 2023 00:54:40 GMT</pubDate>
            <description><![CDATA[Table of contents:IntroVideo DemoTech stack usedThe Staking flow - how it worksComponents and functions usedHow to build a modal with ReactZustand storeWagmi hooksControlling variablesTransaction TrackerAction ButtonsSummaryConclusionIntroThis article is a technical write-up about the token staking feature for the AirSwap Member Dashboard app. The majority of my time working on this app was spent on this feature. Users can stake tokens into the AirSwap Staking smart contract, which gives them...]]></description>
            <content:encoded><![CDATA[<h2 id="h-table-of-contents" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Table of contents:</h2><ol><li><p>Intro</p></li><li><p>Video Demo</p></li><li><p>Tech stack used</p></li><li><p>The Staking flow - how it works</p></li><li><p>Components and functions used</p></li><li><p>How to build a modal with React</p></li><li><p>Zustand store</p></li><li><p>Wagmi hooks</p></li><li><p>Controlling variables</p></li><li><p>Transaction Tracker</p></li><li><p>Action Buttons</p></li><li><p>Summary</p></li><li><p>Conclusion</p></li></ol><h2 id="h-intro" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Intro</h2><p>This article is a technical write-up about the token staking feature for the AirSwap Member Dashboard app. The majority of my time working on this app was spent on this feature.</p><p>Users can stake tokens into the AirSwap <code>Staking</code> <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/airswap/airswap-protocols/blob/develop/source/staking/contracts/Staking.sol">smart contract</a>, which gives them access to voting on proposals, then claim rewards from the AirSwap <code>Pool</code> <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/airswap/airswap-protocols/blob/develop/source/pool/contracts/Pool.sol">smart contract</a>. This article will detail the logic on how I built the staking feature.</p><h2 id="h-video-demo" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Video demo</h2><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://vimeo.com/874258804">https://vimeo.com/874258804</a></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://vimeo.com/manage/videos/877284150">https://vimeo.com/manage/videos/877284150</a></p><h2 id="h-tech-stack-used" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Tech stack used</h2><p>The app is built using React, TypeScript, and TailwindCSS. All smart contract interactions use a library called Wagmi. Here’s a brief overview:</p><ul><li><p><strong>React</strong> is a popular JavaScript library for building user interfaces. <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://react.dev/">Docs</a>.</p></li><li><p><strong>TypeScript</strong> is a superset of JavaScript that adds static typing to the language. <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.typescriptlang.org/">Docs</a>.</p></li><li><p><strong>TailwindCSS</strong> is a CSS framework that allows you to write CSS classes directly into HTML, or JSX in the case of React. Some love it, some hate. I’m among those who love it. <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://tailwindcss.com/docs/installation">Docs</a>.</p></li><li><p><strong>Wagmi</strong>: Wagmi is a collection of React hooks that make it simple to interact with Ethereum and EVM blockchains. <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://wagmi.sh/">Docs</a>.</p></li></ul><h2 id="h-the-staking-flow-how-it-works" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">The Staking flow - how it works</h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0d0c74cfd27ed2e0274e691409463631ae28b3194324c0aa4213c84fb98bf02d.png" alt="The screen you&apos;ll see after successfully approving AST" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">The screen you&apos;ll see after successfully approving AST</figcaption></figure><p>If you hold AirSwap Token, (<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://dexscreener.com/ethereum/0x117439f6fdde9a09d28eb78759cd5c852a8653f0">AST</a>), you can use this app to stake your tokens. Here’s the flow of how staking works:</p><ol><li><p><strong>Approve</strong>: To stake tokens, first you have to approve the AirSwap Staking contract to spend your AST tokens. This is done by calling the <code>approve</code> function, and is a feature in most smart contracts.</p></li><li><p><strong>Stake</strong>: After you’ve approved the spending of your token, you can call the <code>stake</code> function. When you stake your tokens, you’ll receive sAST tokens in exchange for locking up your AST tokens in the smart contract. sAST is kind of like an IOU for AST tokens that you receive from the smart contract. It’s an IOU, but your sAST balance is also used to calculate your voting power.</p></li><li><p><strong>Unstake</strong>: Calling the <code>unstake</code> function lets you unlock your tokens from the smart contract. Note that when you stake <code>AST</code> tokens, your tokens unlock linearly over time.</p></li></ol><h2 id="h-components-and-functions-used" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Components and functions used</h2><p>This section covers various components and functions in the Staking feature. Every function won’t be described here, but feel free to <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/airswap/airswap-voter-rewards/tree/main/src/features/staking">view all the code on GitHub</a> if you’re curious.</p><h2 id="h-how-to-build-a-modal-with-react" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">How to build a modal with React</h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e843f9ff82cded18dde1dfac33b6628e99347d774d8a10764d38c89a530e8a9d.png" alt="The modal is the box in the center of the screen. Clicking the X in the upper right corner, or pressing the &quot;escape&quot; key will close the modal." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">The modal is the box in the center of the screen. Clicking the X in the upper right corner, or pressing the &quot;escape&quot; key will close the modal.</figcaption></figure><p>The <code>Modal</code> component in the app utilizes the HTML <code>dialog</code> element. The <code>dialog</code> element is a modern, elegant way to create a modal. It comes with built-in JavaScript methods, such as <code>showModal</code> and <code>close</code>.</p><p>To create a modal, use the JSX tag <code>&lt;dialog /&gt;</code>, and pass it a ref. For the ref, we can utilize the <code>useRef</code> hook. <code>useRef</code> is a React hook that persists a value between renders. Since the value persists, it means that our modal can remain open (or closed) when we want it to.</p><p><strong>Here’s a code sample of the</strong> <code>Modal</code> <strong>component:</strong></p><pre data-type="codeBlock" text="import { useKeyboardEvent } from &quot;@react-hookz/web&quot;;
import { useEffect, useRef } from &quot;react&quot;;

export const Modal = ({
  // omitted code
  isClosable = true,
  onCloseRequest,
}: {
  // omitted code
  isClosable?: boolean;
  onCloseRequest: () =&gt; void;
}) =&gt; {
  const modalRef = useRef&lt;HTMLDialogElement&gt;(null);

  useKeyboardEvent(&quot;Escape&quot;, () =&gt; {
    if (!isClosable) return;
    onCloseRequest &amp;&amp; onCloseRequest();
    modalRef.current?.close();
  });

  useEffect(() =&gt; {
    if (modalRef.current &amp;&amp; !modalRef.current.hasAttribute(&quot;open&quot;)) {
      modalRef.current.showModal();
    }
  }, [modalRef]);

  return (
    &lt;dialog ref={modalRef} &gt;
    // omitted code
    &lt;/dialog&gt;
  );
};
"><code><span class="hljs-keyword">import</span> { <span class="hljs-title">useKeyboardEvent</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"@react-hookz/web"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title">useEffect</span>, <span class="hljs-title">useRef</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"react"</span>;

export const Modal <span class="hljs-operator">=</span> ({
  <span class="hljs-comment">// omitted code</span>
  isClosable <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>,
  onCloseRequest,
}: {
  <span class="hljs-comment">// omitted code</span>
  isClosable?: boolean;
  onCloseRequest: () <span class="hljs-operator">=</span><span class="hljs-operator">></span> void;
}) <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  const modalRef <span class="hljs-operator">=</span> useRef<span class="hljs-operator">&#x3C;</span>HTMLDialogElement<span class="hljs-operator">></span>(null);

  useKeyboardEvent(<span class="hljs-string">"Escape"</span>, () <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
    <span class="hljs-keyword">if</span> (<span class="hljs-operator">!</span>isClosable) <span class="hljs-keyword">return</span>;
    onCloseRequest <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> onCloseRequest();
    modalRef.current?.close();
  });

  useEffect(() <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
    <span class="hljs-keyword">if</span> (modalRef.current <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> <span class="hljs-operator">!</span>modalRef.current.hasAttribute(<span class="hljs-string">"open"</span>)) {
      modalRef.current.showModal();
    }
  }, [modalRef]);

  <span class="hljs-keyword">return</span> (
    <span class="hljs-operator">&#x3C;</span>dialog ref<span class="hljs-operator">=</span>{modalRef} <span class="hljs-operator">></span>
    <span class="hljs-comment">// omitted code</span>
    <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">/</span>dialog<span class="hljs-operator">></span>
  );
};
</code></pre><p>In the code above, first we create a ref object called <code>modalRef</code>, which gets passed into the <code>dialog</code>. We also have a hook called <code>useKeyboardEvent</code>, which gets returned from the react-hookz library. <code>useKeyboardEvent</code> returns a callback function that closes the modal when the user presses the “escape” key.</p><p>Note that <code>close</code> is a method which gets called on <code>modalRef</code>. Since the <code>dialog</code> node has a ref of <code>modalRef</code>, the ref object now can access the JavaScript functions that are associated with the HTML <code>dialog</code> element, and pass it along to the <code>dialog</code>.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/8f9fabec9a8e4ab5c011c241307fce65cd797c561039a6015034a5276a64a510.png" alt="Clicking the blue “STAKING” button in the header opens the staking modal." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Clicking the blue “STAKING” button in the header opens the staking modal.</figcaption></figure><p>The <code>useEffect</code> hook checks whether the <code>dialog</code> is open. The <code>Modal</code> component opens when a user clicks on a button in another component, but I’ve omitted that code from this blog post. Opening the <code>Modal</code> component would give it an attribute of <code>open</code>.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/6e933e8b9a570826137bc7679904f8b8c00c7e5ec3e5e212be9e01e4c49e05f6.png" alt="Above is a screenshot of HTML if you log console.log(modalRef.current) in dev tools. Note how dialog has the attribute open." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Above is a screenshot of HTML if you log console.log(modalRef.current) in dev tools. Note how dialog has the attribute open.</figcaption></figure><h2 id="h-zustand-store" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Zustand store</strong></h2><p>Zustand is a lightweight alternative to Redux. We chose Zustand over Redux because the app is relatively small in size. Redux would’ve been overkill for our needs on this app.</p><p>We store values in the Zustand store so these values persist across component re-renders and various stages of the staking cycle. It’s possible to use React “prop-drilling” to pass values from parent to children components, but using Zustand produces cleaner code.</p><p>Another cool feature of Zustand is that it comes with <code>persist</code> middleware. This middleware makes it easy to “persist” data into local storage. This middleware isn’t used for the staking modal, but some other features in the app utilize it.</p><h2 id="h-wagmi-hooks" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Wagmi hooks</strong></h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/1bb48d9826084afa84087f1a148cc6e364a5ee01bbcb78a1658d579d0f7cba0a.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>In the Staking feature, there are main 4 custom hooks which call Wagmi hooks:</p><ol><li><p><strong>useApproveAst.ts</strong> - calls smart contract “write” function to approve the spending of <code>AST</code> token</p></li><li><p><strong>useAstAllowance.ts</strong> - calls a smart contract “read” function to return the amount of <code>AST</code> tokens held in a connected wallet</p></li><li><p><strong>useStakeAst.ts</strong> - calls smart contract “write” function to stake AST</p></li><li><p><strong>useUnstakeSast.ts</strong> - calls smart contract “write” function to unstake AST</p></li></ol><p>These hooks are mostly similar, so for brevity I’ll only cover <code>useStakeAst</code> in detail.</p><h3 id="h-staking-hook-usestakeast" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Staking hook -</strong> <code>useStakeAst</code></h3><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/adc10d6f9df1d338c0b64028a7cc55dcea6f621d26b98e4a0c1b7adde7d08249.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>This hook returns a function that users can use to stake tokens. It calls 3 Wagmi hooks:</p><ol><li><p><code>usePrepareContractWrite</code>. This hook prepares an object called <code>config</code>. This object later gets passed into <code>useContractWrite</code>, which is explained below. <code>usePrepareContractWrite</code> accepts several arguments:</p><ol><li><p><strong>Address</strong> - the smart contract address of the contract you want to use.</p></li><li><p><strong>ABI</strong> - an interface of the smart contract.</p></li><li><p><strong>Function</strong> - the function on the ABI you want to call. In our case, <code>stake</code>.</p></li><li><p><strong>Arguments</strong> - an optional array of arguments to be passed into the function want to call.</p></li><li><p><strong>Enabled</strong> - an optional boolean value. If <code>false</code>, the function will not run. Using this can optimize the performance of the app. For this purpose, variable called <code>canStake</code> was used. <code>canStake</code> is a boolean value that checks that the transaction type is “stake”, that <code>needsApproval</code> is false, and that the input the user entered is valid.</p></li></ol></li><li><p><code>useContractWrite</code> - This hook is used to call smart contract functions. It returns several values that were utilized:</p><ol><li><p><code>write</code> - this is the function that writes to the blockchain when it gets called. When a user clicks the “stake” button, this “write” function gets called.</p></li><li><p><code>data</code> - this object returns data about the transaction after <code>write</code> gets called. It contains useful info such as the transaction hash.</p></li><li><p><code>reset</code>- After a transaction completes, the status of the transaction will persist until it changes, or the user refreshes the page. The code will look like this: <code>status === &apos;success&apos;</code>. Calling <code>reset</code> will reset the transaction status and prevent UX pitfalls.</p></li></ol></li><li><p><code>useWaitForTransaction</code> - we use this hook primarily to get the status of a transaction, and its transaction hash.</p><ul><li><p><code>status</code> can either be: “error”, “idle”, “success”, or “loading. These values change during the course of a transaction’s lifetime.</p></li><li><p><code>data</code> is a return object that contains a transaction hash. We use the transaction has to link users to an etherscan link with their transaction data.</p></li></ul></li></ol><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/2e936970f80f298f71bb449717b729bbcb758ae1a192d87e271fefd6f16371f8.png" alt="Clicking on the &quot;View on Etherscan&quot; link opens an Etherscan transaction link" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Clicking on the &quot;View on Etherscan&quot; link opens an Etherscan transaction link</figcaption></figure><h2 id="h-controlling-variables" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Controlling variables</strong></h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/52d66c59a3425defd7761fa91a5c1ce78831f098bbf433ef8b9a6a63ab0dcbfd.png" alt="The transaction tracker conditionally renders a variety of things. Much of it is determined by the variables below." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">The transaction tracker conditionally renders a variety of things. Much of it is determined by the variables below.</figcaption></figure><p>Several variables used in the main <code>StakingModal</code> component are used to control the flow of certain hooks. For example, the following boolean variables: <code>needsApproval</code>, <code>canStake</code>, and <code>canUnstake</code>. If <code>needsApproval</code> is true, the hook <code>useApproveAst</code> will be enabled, and the hook <code>useStakeAst</code> will be disabled. For <code>canStake</code> to be true, <code>needsApproval</code> must be false.</p><p>Here are a few code snippets with explanations below:</p><pre data-type="codeBlock" text="const needsApproval =
    txType === TxType.STAKE &amp;&amp;
    Number(astAllowance) &lt; Number(stakingAmountFormatted) * 10 ** 4 &amp;&amp;
    validNumberInput;

const canStake =
  txType === TxType.STAKE &amp;&amp; !needsApproval &amp;&amp; validNumberInput;

const {
    writeAsync: approveAst,
    data: dataApproveAst,
    reset: resetApproveAst,
    isLoading: approvalAwaitingSignature,
  } = useApproveAst({
    stakingAmountFormatted: Number(stakingAmountFormatted) || 0,
    enabled: needsApproval,
  });
"><code>const needsApproval <span class="hljs-operator">=</span>
    txType <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> TxType.STAKE <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span>
    Number(astAllowance) <span class="hljs-operator">&#x3C;</span> Number(stakingAmountFormatted) <span class="hljs-operator">*</span> <span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> <span class="hljs-number">4</span> <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span>
    validNumberInput;

const canStake <span class="hljs-operator">=</span>
  txType <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> TxType.STAKE <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> <span class="hljs-operator">!</span>needsApproval <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> validNumberInput;

const {
    writeAsync: approveAst,
    data: dataApproveAst,
    reset: resetApproveAst,
    isLoading: approvalAwaitingSignature,
  } <span class="hljs-operator">=</span> useApproveAst({
    stakingAmountFormatted: Number(stakingAmountFormatted) <span class="hljs-operator">|</span><span class="hljs-operator">|</span> <span class="hljs-number">0</span>,
    enabled: needsApproval,
  });
</code></pre><p><strong>Check if a user can approve tokens or not:</strong></p><p><code>needsApproval</code> is a boolean value with a few checks. The user must toggle the “stake” option, the user’s allowance must be less than the amount they entered to stake, and the number inputted to the form must be valid.</p><p><strong>Transaction Type</strong></p><p>Let’s look at the following code: <code>txType === TxType.STAKE</code>. The <code>txType</code> variable is stored in the Zustand store. (Code is not shown above). The value of <code>txType</code> changes when you click on the STAKE/UNSTAKE toggle.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/17c0afeddc2a1f8abd9d54d6c2adbcc3901f6334a9d7b791693e6fc48436b5cf.png" alt="Clicking the &quot;STAKE&quot; option will toggle txType so it&apos;s equal to txType.STAKE" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Clicking the &quot;STAKE&quot; option will toggle txType so it&apos;s equal to txType.STAKE</figcaption></figure><p><code>TxType</code> is a TypeScript enum with the following shape:</p><pre data-type="codeBlock" text="export enum TxType {
  STAKE = &quot;stake&quot;,
  UNSTAKE = &quot;unstake&quot;,
}
"><code>export enum TxType {
  <span class="hljs-attr">STAKE</span> = <span class="hljs-string">"stake"</span>,
  <span class="hljs-attr">UNSTAKE</span> = <span class="hljs-string">"unstake"</span>,
}
</code></pre><p><strong>Check if a user can stake or not:</strong></p><p><code>canStake</code> checks that the user has toggled the “stake” option, that <code>needsApproval</code> is false, and that a valid number has been inputted into the staking form.</p><p><strong>Loading transaction variable:</strong></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/2e936970f80f298f71bb449717b729bbcb758ae1a192d87e271fefd6f16371f8.png" alt="If txIsLoading is true, the screen above might render." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">If txIsLoading is true, the screen above might render.</figcaption></figure><pre data-type="codeBlock" text="const txIsLoading =
  approvalAwaitingSignature ||
  stakeAwaitingSignature ||
  unstakeAwaitingSignature ||
  txStatus === &quot;loading&quot;;
"><code>const txIsLoading <span class="hljs-operator">=</span>
  approvalAwaitingSignature <span class="hljs-operator">|</span><span class="hljs-operator">|</span>
  stakeAwaitingSignature <span class="hljs-operator">|</span><span class="hljs-operator">|</span>
  unstakeAwaitingSignature <span class="hljs-operator">|</span><span class="hljs-operator">|</span>
  txStatus <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-string">"loading"</span>;
</code></pre><p>This variable is a boolean that changes depending on the current transaction status. This value gets passed into the <code>Modal</code> component as a prop called <code>isClosable</code>. If <code>isClosable</code> is <code>true</code>, the close button in the modal will be disabled.</p><pre data-type="codeBlock" text="&lt;Modal
  className=&quot;w-full max-w-none xs:max-w-[360px] text-white&quot;
  heading={modalLoadingStateHeadlines}
  isClosable={!txIsLoading}
  onCloseRequest={() =&gt; setShowStakingModal(false)}
&gt;
"><code><span class="hljs-operator">&#x3C;</span>Modal
  className<span class="hljs-operator">=</span><span class="hljs-string">"w-full max-w-none xs:max-w-[360px] text-white"</span>
  heading<span class="hljs-operator">=</span>{modalLoadingStateHeadlines}
  isClosable<span class="hljs-operator">=</span>{<span class="hljs-operator">!</span>txIsLoading}
  onCloseRequest<span class="hljs-operator">=</span>{() <span class="hljs-operator">=</span><span class="hljs-operator">></span> setShowStakingModal(<span class="hljs-literal">false</span>)}
<span class="hljs-operator">></span>
</code></pre><p>Above is a code snippet that shows the <code>Modal</code> component accepting <code>txIsLoading</code> as a prop for the <code>isClosable</code> variable</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/adc10d6f9df1d338c0b64028a7cc55dcea6f621d26b98e4a0c1b7adde7d08249.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>We want to disable the close button when a transaction is processing because closing the modal will make a user confused about the status of an active blockchain transaction. It’s bad UX when this happens. Blockchain users usually want a status update on their transaction and will often look at their screen until they see a “success” (or “failed”) confirmation of a completed transaction.</p><h2 id="h-transaction-tracker" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Transaction Tracker</strong></h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/52d66c59a3425defd7761fa91a5c1ce78831f098bbf433ef8b9a6a63ab0dcbfd.png" alt="The Transaction Tracker takes many forms. This is the tracker after a successful staking transaction." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">The Transaction Tracker takes many forms. This is the tracker after a successful staking transaction.</figcaption></figure><p>This component was designed to be as generic as possible and to display the current state of transaction data. It’s used for the staking feature, as well as the claims feature (which will not be covered in this post).</p><p>The tracker takes in several props, including a transaction hash. This is a hash that gets passed into the Wagmi hook <code>useWaitForTransaction</code>, which returns the status of a transaction. The status is either: idle, loading, successful, or failed. The values of these status variables are used to determine which text and images to display in the transaction tracker. This provides users with feedback on their current stage in the staking and unstaking process.</p><h2 id="h-action-buttons" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Action Buttons</strong></h2><p>The staking modal has 1 main button to handle user actions. These actions include: approve, stake, unstaking. There’s also a toggle where users can switch between staking and unstaking.</p><p><code>actionButtonsObject</code> is a function that takes in 4 arguments and returns an object. The return object takes the shape of <code>ActionButton</code>, which returns button labels and callback functions. 3 of these arguments are functions that were returned from Wagmi hooks.</p><pre data-type="codeBlock" text="type ActionButton = {
  afterSuccess: { label: string; callback: () =&gt; void };
  afterFailure: { label: string; callback: () =&gt; void };
}; 
"><code>type ActionButton = {
  afterSuccess: { <span class="hljs-selector-tag">label</span>: string; callback: () => void };
  afterFailure: { <span class="hljs-selector-tag">label</span>: string; callback: () => void };
}; 
</code></pre><p><strong>Here are some examples of how the button would change:</strong></p><ul><li><p>After successfully calling the <code>“approve”</code> function, the button label will read “Continue”, and clicking on it would call the function <code>resetApproveAst</code>, which resets the status of the function. At this point, the user is permitted to stake tokens.</p></li><li><p>After a failed attempt at calling the <code>&quot;approve&quot;</code> function, the button label will read “Try again”, and clicking on it would also call <code>resetApproveAst</code>. The status of the approval function will reset, but the user will still be required to approve token spending before staking is allowed.</p></li></ul><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/52d66c59a3425defd7761fa91a5c1ce78831f098bbf433ef8b9a6a63ab0dcbfd.png" alt="After successfully approving, the button says “CONTINUE”. Clicking that button would call the resetApproveAst function." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">After successfully approving, the button says “CONTINUE”. Clicking that button would call the resetApproveAst function.</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3e3a6e0bd30e367e542113df32b8ff578b11c9141e12e9943378b160a957a070.png" alt="After clicking the “CONTINUE” button in the previous screenshot, the staking modal will appear as shown above. The approved amount (20 AST) will persist in the form, allowing for a seamless user experience when staking the tokens by clicking “STAKE”." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">After clicking the “CONTINUE” button in the previous screenshot, the staking modal will appear as shown above. The approved amount (20 AST) will persist in the form, allowing for a seamless user experience when staking the tokens by clicking “STAKE”.</figcaption></figure><p>The staking modal component also has a function called <code>actionButtonLogic</code>. This checks the status of various staking actions and returns a value based on which staking action is <code>true</code>. The return value of <code>actionButtonLogic</code> gets passed as props into the <code>TransactionTracker</code> component. The value that gets passed into the Transaction Tracker is what the user will see on the button.</p><p>The following code snippet shows how the <code>TransactionTracker</code> component is called in the <code>StakingModal</code> component.</p><pre data-type="codeBlock" text="&lt;TransactionTracker
  actionButtons={actionButtonLogic()}
  successContent={
    &lt;span&gt;
      You successfully {verb}{&quot; &quot;}
      &lt;span className=&quot;text-white&quot;&gt;{stakingAmountFormatted} AST&lt;/span&gt;
    &lt;/span&gt;
  }
  failureContent={&quot;Your transaction has failed&quot;}
  signatureExplainer={
    isApproval
      ? &quot;To stake AST you will first need to approve the token spend.&quot;
      : undefined
    }
    txHash={currentTransactionHash}
  /&gt;
"><code><span class="hljs-operator">&#x3C;</span>TransactionTracker
  actionButtons<span class="hljs-operator">=</span>{actionButtonLogic()}
  successContent<span class="hljs-operator">=</span>{
    <span class="hljs-operator">&#x3C;</span>span<span class="hljs-operator">></span>
      You successfully {verb}{<span class="hljs-string">" "</span>}
      <span class="hljs-operator">&#x3C;</span>span className<span class="hljs-operator">=</span><span class="hljs-string">"text-white"</span><span class="hljs-operator">></span>{stakingAmountFormatted} AST<span class="hljs-operator">&#x3C;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span>
    <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">/</span>span<span class="hljs-operator">></span>
  }
  failureContent<span class="hljs-operator">=</span>{<span class="hljs-string">"Your transaction has failed"</span>}
  signatureExplainer<span class="hljs-operator">=</span>{
    isApproval
      ? <span class="hljs-string">"To stake AST you will first need to approve the token spend."</span>
      : undefined
    }
    txHash<span class="hljs-operator">=</span>{currentTransactionHash}
  <span class="hljs-operator">/</span><span class="hljs-operator">></span>
</code></pre><p><code>signatureExplainer</code> is a prop in TransactionTracker. It pops up when a user has initiated a transaction, but has not yet signed the transaction in his or her wallet (for example MetaMask). The explainer is used to tell the user the next steps in the Staking flow.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/adc10d6f9df1d338c0b64028a7cc55dcea6f621d26b98e4a0c1b7adde7d08249.png" alt="This screen will appear when a user&apos;s MetaMask wallet is open, but the transaction hasn&apos;t yet been signed." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">This screen will appear when a user&apos;s MetaMask wallet is open, but the transaction hasn&apos;t yet been signed.</figcaption></figure><h2 id="h-summary" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Summary</strong></h2><p>Here’s a video demo of the staking flow in action:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://vimeo.com/874258804">https://vimeo.com/874258804</a></p><p>This article covers many of the functions that make up the staking modal, but there are several details I skipped over. Dive into the code on GitHub to see the rest of it. Here’s a high-level recap of how the staking modal works:</p><ul><li><p>First, a user must approve the smart contract to spend AST tokens. In the StakingModal component, the boolean variable <code>needsApproval</code> checks whether or not a user can approve.</p></li><li><p>If <code>needsApproval</code> is true, the custom hook <code>useApproveAst</code> is enabled. Now, when a user clicks the “Approve” button in the TransactionTracker component, <code>useApproveAst</code> returns a callback function that writes the &quot;approve&quot; transaction to the blockchain.</p></li><li><p>After successfully approving, <code>needsApproval</code> should be set to false. Now the boolean value <code>canStake</code> determines whether a user can stake tokens or not.</p></li><li><p>If <code>canStake</code> is true, the custom hook <code>useStakeAst</code> will return the Wagmi callback function <code>stakeAst</code> (the original function name returned from Wagmi is <code>writeAsync</code>, but I renamed it in the staking Modal component). This process is similar to the token spending approval process, and unstaking tokens is similar to the token staking process.</p></li><li><p><code>useApproveAst</code>, <code>useStakeAst</code>, <code>useUnstakeSast</code> all return objects called data, which contain a transaction hash (or undefined). These hooks also return the status of transactions: <code>&quot;idle&quot;</code>, <code>&quot;loading&quot;</code>, <code>&quot;success&quot;</code>, or <code>&quot;failed&quot;</code>.</p></li><li><p>These status variables and transaction hashes are passed into the TransactionTracker component, and give users feedback on their transactions.</p></li></ul><p>The AirSwap Member Dashboard has more features than Staking, but I want to keep this blog post specific and won’t go into the other features today.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/8f657ec1e83107296ae194bdc0284e67f9ab05454c7022622f8938ce92413766.png" alt="Users can also vote on proposals that affect the AirSwap protocol." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Users can also vote on proposals that affect the AirSwap protocol.</figcaption></figure><h2 id="h-conclusion" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Conclusion</h2><p>Developing this Staking feature was one of my most challenging projects. I did countless refactors and put in many hours of work to make sure it came out as expected. I was fortunate to work alongside a more senior developer on this app. The guidance and mentorship were priceless.</p><p>Here’s an incomplete list of some of my learning lessons from building this feature:</p><ul><li><p>It’s almost impossible to refactor code too much. Continuously refactoring code can seem tedious, but in the end, it makes you a more skilled developer.</p></li><li><p>Often the cause of bugs is having too much code, rather than too little. If there are bugs that are hard to fix, it can help to remove code until there aren’t any bugs. Once you reach the point of having no bugs, add in code until you can isolate exactly what was causing the original bugs.</p></li><li><p>Working with a mentor (or mentee) is priceless. Even if you’re building stuff that works, it’s nice to get other perspectives and hear of ways to improve your code.</p></li><li><p>Conducting code reviews for others can enhance your learning by providing insights into their coding structure.</p></li></ul><p>I take pride in the work I&apos;ve accomplished on this app, and I hope this blog post effectively conveys my enthusiasm during its development. Feel free to explore the app and test the staking feature. If you&apos;re interested, drop me a message for some Goerli AST to try it out. While there are areas for optimization, I am open to any feedback you may have</p><p>If you’ve read this far, I’d like to thank you for your attention.</p><p><strong>GitHub</strong>: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/airswap/airswap-voter-rewards">https://github.com/airswap/airswap-voter-rewards</a>.</p><p><strong>Demo</strong>: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://dao.airswap.eth.limo/">http://dao.airswap.eth.limo</a>.</p>]]></content:encoded>
            <author>starrdev@newsletter.paragraph.com (starrdev)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/5cdc58e13f0d45e731e17e84a5cf0f1137345a0bf9b6e311223d7ae43b5c8dee.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Behind the Scenes: Creating the AirSwap Member Dashboard App]]></title>
            <link>https://paragraph.com/@starrdev/behind-the-scenes-creating-the-airswap-member-dashboard-app</link>
            <guid>qmTDOWJjzAOmZzwZykpq</guid>
            <pubDate>Mon, 09 Oct 2023 12:24:50 GMT</pubDate>
            <description><![CDATA[Table of ContentsIntroductionWhat is the AirSwap Voter Rewards App?App Features I DevelopedMentorshipCode ReviewsTypeScriptWagmi LibraryCustom React ComponentsVariable NamingRegressionsLaunch DayPutting Apps into ProductionConclusionIntroductionIn this post, I&apos;ll share my experience and the key lessons I learned while building the AirSwap Member Dashboard app. This app provides users with the ability to stake AST tokens, vote on proposals that influence the AirSwap protocol, and claim re...]]></description>
            <content:encoded><![CDATA[<h2 id="h-table-of-contents" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Table of Contents</h2><ol><li><p>Introduction</p></li><li><p>What is the AirSwap Voter Rewards App?</p></li><li><p>App Features I Developed</p></li><li><p>Mentorship</p></li><li><p>Code Reviews</p></li><li><p>TypeScript</p></li><li><p>Wagmi Library</p></li><li><p>Custom React Components</p></li><li><p>Variable Naming</p></li><li><p>Regressions</p></li><li><p>Launch Day</p></li><li><p>Putting Apps into Production</p></li><li><p>Conclusion</p></li></ol><h2 id="h-introduction" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Introduction</h2><p>In this post, I&apos;ll share my experience and the key lessons I learned while building the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://dao.airswap.eth.limo">AirSwap Member Dashboard</a> app. This app provides users with the ability to stake <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://dexscreener.com/ethereum/0x117439f6fdde9a09d28eb78759cd5c852a8653f0">AST</a> tokens, vote on proposals that influence the AirSwap protocol, and claim rewards for participating in votes.</p><p>As a core contributor to the frontend development of this application, I&apos;ll take you through my journey and the valuable insights gained along the way.</p><h2 id="h-what-is-the-airswap-member-dashboard-app" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">What is the AirSwap Member Dashboard App?</h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/667aee2bb7a33fdae347eefd14524c22690262cc554f09906e2190a201da3f19.png" alt="AirSwap Voting Rewards app - proposals view" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">AirSwap Voting Rewards app - proposals view</figcaption></figure><h3 id="h-the-main-functionalities-of-the-app-include" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">The Main Functionalities of the App Include:</h3><ol><li><p>Users can stake and unstake <code>AST</code> tokens to the <code>staking</code> smart contract.</p></li><li><p>Users have the ability to vote on proposals that influence the AirSwap protocol.</p></li><li><p>Users can claim rewards from a <code>pool</code> smart contract.</p></li></ol><p><strong>How it Works</strong>: When a user stakes AST tokens, their address gains voting rights on proposals. By voting on proposals, you become eligible to claim rewards from a pool once a month. The more tokens a user has staked, the greater their voting power. These rewards are protocol fees that get collected from swaps made on the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://swap.eth.limo">AirSwap web app</a>.</p><h2 id="h-app-features-i-developed" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">App Features I Developed</h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3c130df65d76404aeba220513c449232487aae3977b1acc7881f16a5236587e5.png" alt="Staking Modal - this is where users can approve, stake and unstake AST tokens" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Staking Modal - this is where users can approve, stake and unstake AST tokens</figcaption></figure><p>I built the following major features of the app:</p><ul><li><p><strong>Staking Flow</strong>: A set of components where the user can approve, stake, and unstake tokens to the AirSwap <code>staking</code> smart contract. <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/airswap/airswap-voter-rewards/tree/main/src/features/staking">GitHub</a>.</p></li><li><p><strong>Wallet connection</strong>: The buttons and modals in this feature that allow users to connect and disconnect to the app. <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/airswap/airswap-voter-rewards/tree/main/src/features/chain-connection">GitHub</a>.</p></li></ul><p>There were several other smaller features that I built, such as the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/starrdev.eth/Tz8YeS9hGwTwndMakBwNKFvVCqY-umFmDxu8JdonCMg">accordion component</a>. I won’t go into details about these features in this blog post. However, I plan to write a technical post about how I built the staking modal.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f921900658add527e9c5df2dcc9416acde14fc55e8d078933d686f65c4dd5c0e.png" alt="Wallet Connection modal" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Wallet Connection modal</figcaption></figure><h2 id="h-mentorship" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Mentorship</h2><p>Receiving mentorship was a major benefit for me working on this project. Working alongside the co-founder and another experienced developer was great. I was able to get feedback on my code and ask for guidance when needed.</p><p>I was exposed to critical pieces of software development, such as CI/CD. I helped with the CI/CD process by creating a .yaml file with GitHub actions. After changes are merged with the <code>main</code> branch, these GitHub actions automatically run a build, deploying the code to production.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/28064a7b7e64e71245224f9e23ee46fd453129a5f87cc4ab9087bf8595a051b7.png" alt="Diagram of CI/CD" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Diagram of CI/CD</figcaption></figure><p>I also learned a lot about caching data in local storage. Caching is a key aspect of frontend development, but it&apos;s something that most newer developers probably haven&apos;t encountered much.</p><p>In this app, we used Zustand, which is a lightweight alternative to Redux. I learned that you can import &apos;persist,&apos; which is Zustand middleware that saves state changes into local storage. While local storage and caching aren’t exactly the same thing, they share a similar concept.</p><h2 id="h-code-reviews" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Code Reviews</h2><p>Having my code constantly reviewed pushed me to design better solutions. Some of my pull requests (PRs) took weeks to merge due to necessary refactors. These refactors forced me to contemplate the &quot;why&quot; behind my code choices and find more efficient ways of accomplishing tasks.</p><p>I asked hundreds of questions, which proved immensely valuable. Mentorship is a two-way street; the mentor benefits from reinforcing their own knowledge and honing their communication skills.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/abfa97b8c9a36e4aec935ae4a83c422aba6de12e07eba05ca5361e492099fb80.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Many new developers may feel hesitant to seek mentorship, fearing they have little to offer in return. This is a misconception. I&apos;ve had many mentors on my software journey and plan to pay it forward to others someday.</p><h2 id="h-typescript" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">TypeScript</h2><p>The best way for me to learn is through continuous, repeated exposure to something. While I had been exposed to TypeScript enums and records while working on the AirSwap Web app, the additional practice I gained while working on AirSwap Member Dashboard helped solidify my understanding and finally made it all click.</p><p>An enum is a data type with named constant values, ideal for finite options. Before enums, I used type-casted objects. Enums enhance readability and type safety, especially for finite value sets.</p><p>In TypeScript, a record defines the structure of an object with specific key-value pairs. Records improve code readability and maintainability compared to type-casted objects.</p><p>Here&apos;s an example from the repo:</p><pre data-type="codeBlock" text="export enum ContractTypes {
  AirSwapToken,
  AirSwapStaking,
  AirSwapPool,
}

type ContractList = { [k in ContractTypes]?: `0x${string}` };

export const contractAddressesByChain: Record&lt;number, ContractList&gt; = {
  1: {
    [ContractTypes.AirSwapToken]: &quot;0x27054b13b1B798B345b591a4d22e6562d47eA75a&quot;,
    [ContractTypes.AirSwapStaking]:
      &quot;0x9fc450F9AfE2833Eb44f9A1369Ab3678D3929860&quot;,
    [ContractTypes.AirSwapPool]: &quot;0xEEcD248D977Fd4D392915b4AdeF8154BA3aE9c02&quot;,
  },
  5: {
    [ContractTypes.AirSwapToken]: &quot;0x1a1ec25DC08e98e5E93F1104B5e5cdD298707d31&quot;,
    [ContractTypes.AirSwapStaking]:
      &quot;0x51F372bE64F0612532F28142cECF8F204B272622&quot;,
    [ContractTypes.AirSwapPool]: &quot;0xa55CDCe4F6300D57831b2792c45E55a899D8e2a4&quot;,
  },
};
"><code>export <span class="hljs-keyword">enum</span> <span class="hljs-title">ContractTypes</span> {
  AirSwapToken,
  AirSwapStaking,
  AirSwapPool,
}

<span class="hljs-keyword">type</span> ContractList <span class="hljs-operator">=</span> { [k in ContractTypes]?: `0x${<span class="hljs-keyword">string</span>}` };

export const contractAddressesByChain: Record<span class="hljs-operator">&#x3C;</span>number, ContractList<span class="hljs-operator">></span> <span class="hljs-operator">=</span> {
  <span class="hljs-number">1</span>: {
    [ContractTypes.AirSwapToken]: <span class="hljs-string">"0x27054b13b1B798B345b591a4d22e6562d47eA75a"</span>,
    [ContractTypes.AirSwapStaking]:
      <span class="hljs-string">"0x9fc450F9AfE2833Eb44f9A1369Ab3678D3929860"</span>,
    [ContractTypes.AirSwapPool]: <span class="hljs-string">"0xEEcD248D977Fd4D392915b4AdeF8154BA3aE9c02"</span>,
  },
  <span class="hljs-number">5</span>: {
    [ContractTypes.AirSwapToken]: <span class="hljs-string">"0x1a1ec25DC08e98e5E93F1104B5e5cdD298707d31"</span>,
    [ContractTypes.AirSwapStaking]:
      <span class="hljs-string">"0x51F372bE64F0612532F28142cECF8F204B272622"</span>,
    [ContractTypes.AirSwapPool]: <span class="hljs-string">"0xa55CDCe4F6300D57831b2792c45E55a899D8e2a4"</span>,
  },
};
</code></pre><p>This code showcases an enum, <code>ContractTypes</code>, and a record, <code>contractAddressesByChain</code>. Enums define contract types, and the record associates contract addresses with chain IDs. This setup ensures validation and prevents unexpected bugs, a crucial aspect in blockchain applications.</p><h2 id="h-wagmi-library" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Wagmi Library</strong></h2><p>Wagmi is a React library of hooks that simplifies EVM blockchain connections. While I had prior experience with this library, working on this app deepened my understanding of its features. Tooling for blockchain connections is becoming increasingly user-friendly.</p><p>Although I focused on the frontend, connecting the UI to smart contract functions provided valuable exposure to AirSwap&apos;s smart contracts. Reading smart contract functions has been a fantastic way to expand my knowledge of crypto and blockchain.</p><h2 id="h-variable-naming" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Variable Naming</strong></h2><p>Consistent and clear variable names are crucial. Poor naming not only makes it challenging for future contributors but also complicates maintenance for the author. Confusing variable names can lead to unexpected bugs.</p><p>This situation arose when I attempted to change the name of a variable that a hook returned. Later, when I used the hook in another part of the code, I had to revert the variable&apos;s name back to its original form.</p><p>Variable names might seem clear to you when you create them, but if they are not descriptive, they can become a source of confusion later on. Ironically, sometimes I&apos;d ask ChatGPT to review a code snippet, and it would highlight issues with my variable names. I&apos;ve been making a conscious effort to improve them.</p><h2 id="h-regressions" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Regressions</strong></h2><p>In software development, &quot;regression&quot; refers to an undesirable behavior that emerges in an app, which previously worked fine, due to a code update or fix. I hadn&apos;t considered this before, but it became evident when we were striving to ensure everything worked correctly for the app&apos;s launch. Regressions are particularly common when working under tight deadlines.</p><h2 id="h-launch-day" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Launch Day</strong></h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f74c5658c338eb539bd318a3468898981c54c055c1a9801ae4b254dcee9079a7.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Participating in a product launch is a fascinating experience. Users eagerly anticipate a great product, and it&apos;s a mix of excitement and mild stress. Fortunately, in our case, it was mostly excitement.</p><p>We built this app because AirSwap relied on third-party software for users to stake tokens and claim rewards. When the third-party service announced its shutdown, we had a 65-day deadline to develop and launch our app, bringing this functionality in-house.</p><p>Perfection in app development is elusive. In most cases, apps will have some minor cosmetic bugs to address at a minimum. What matters most is that the core features function correctly, and critical bugs are absent. In the lead-up to the launch, we worked diligently to test the core features and ensure that everything operated as intended.</p><p>We met the deadline, shipping an MVP of the app, and the community&apos;s initial response was overwhelmingly positive!</p><h2 id="h-putting-apps-into-production" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Putting Apps into Production</strong></h2><p>Apps in production often reveal overlooked bugs from the development phase. This was also the case with the Member Dashboard app, but fortunately, these issues were manageable. User feedback is invaluable in identifying areas for improvement and addressing any lingering bugs.</p><h2 id="h-conclusion" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Conclusion</strong></h2><p>These are just some of the lessons I learned while working on the AirSwap Member Dashboard app. Collaborating closely with an experienced developer provided me with insights that are challenging to encapsulate in a single blog post. Witnessing how he built features and organized code felt like completing an entire university course.</p><p>Now that the app is live, it&apos;s in maintenance mode. I take pride in having my name associated with this product and will continue working on it as a maintainer.</p><p>My time at AirSwap over the past five months has been a highlight of my professional journey, and I look forward to building more exciting products in the future!</p><p><strong>App</strong>: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://dao.airswap.eth.limo/">https://dao.airswap.eth.limo/</a>.</p><p>Here’s the GitHub: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://github.com/airswap/airswap-voter-rewards">https://github.com/airswap/airswap-voter-rewards</a>.</p>]]></content:encoded>
            <author>starrdev@newsletter.paragraph.com (starrdev)</author>
        </item>
        <item>
            <title><![CDATA[How to Build an Accordion Component]]></title>
            <link>https://paragraph.com/@starrdev/how-to-build-an-accordion-component</link>
            <guid>FdM7d4yt9HtsEu6KwIKp</guid>
            <pubDate>Tue, 22 Aug 2023 17:17:02 GMT</pubDate>
            <description><![CDATA[I&apos;ve been actively involved in the frontend development of the new AirSwap Member Dashboard app. AirSwap currently leverages a platform named Activate for user stake management. However, within a month, Activate will be phased out, prompting the development of a new app for $AST token stakers. A notable feature of this app is the Accordion component, which I&apos;ll discuss here, illustrating its creation with React, Radix, and TailwindCSS.What is Radix-UI?Radix-UI logoRadix-UI is an ope...]]></description>
            <content:encoded><![CDATA[<p>I&apos;ve been actively involved in the frontend development of the new AirSwap Member Dashboard app. AirSwap currently leverages a platform named Activate for user stake management. However, within a month, Activate will be phased out, prompting the development of a new app for $AST token stakers. A notable feature of this app is the Accordion component, which I&apos;ll discuss here, illustrating its creation with React, Radix, and TailwindCSS.</p><h2 id="h-what-is-radix-ui" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">What is Radix-UI?</h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0de61b9df1679039473c4381330436fb029ab66348012a7fd3d67b12b00b3839.jpg" alt="Radix-UI logo" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Radix-UI logo</figcaption></figure><p>Radix-UI is an open-source library boasting ready-to-use components. These components are functional out-of-the-box but come unstyled, allowing for greater design flexibility. Radix effectively handles the underlying JavaScript, freeing you to focus on styling. If you’re without design guidelines, Radix&apos;s documentation provides some style suggestions.</p><p>You can install the Radix-UI accordion into your project with:</p><pre data-type="codeBlock" text="npm install @radix-ui/react-accordion
# or
yarn add @radix-ui/react-accordion
"><code>npm install @radix<span class="hljs-operator">-</span>ui<span class="hljs-operator">/</span>react<span class="hljs-operator">-</span>accordion
# or
yarn add @radix<span class="hljs-operator">-</span>ui<span class="hljs-operator">/</span>react<span class="hljs-operator">-</span>accordion
</code></pre><p>The <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.radix-ui.com/themes/docs/overview/getting-started">documentation</a> has many more components you can use.</p><h2 id="h-radix-accordion-component" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Radix Accordion Component</strong></h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/dd7d1b5d8f7793536bafbc63f9bf6befdc4e4a49b3bba58a4867c2e555f4d788.png" alt="Accordion component with multiple items. The first item is open" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Accordion component with multiple items. The first item is open</figcaption></figure><p>Start by importing the Accordion:</p><pre data-type="codeBlock" text="import * as Accordion from &apos;@radix-ui/react-accordion&apos;;
"><code><span class="hljs-keyword">import</span> <span class="hljs-operator">*</span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">Accordion</span> <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'@radix-ui/react-accordion'</span>;
</code></pre><p>The Accordion has various segments, including Root, Item, Header, Trigger, and Content. For an in-depth understanding of these, refer to the Radix-UI <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.radix-ui.com/primitives/docs/components/accordion">Radix-UI documentation</a>.</p><p>The Accordion supports numerous props for enhanced control. A notable one is the <code>type</code> prop, which allows selections of either <code>&quot;single&quot;</code> or <code>&quot;multiple&quot;</code>, determining if one or multiple accordion items can be open simultaneously. For a comprehensive list of props, the Radix-UI docs have you covered. Below is my implementation of the component, with a brief explanation thereafter::</p><pre data-type="codeBlock" text="import * as RadixAccordion from &apos;@radix-ui/react-accordion&apos;;
import { ReactNode, useState } from &quot;react&quot;;
import { MdChevronRight } from &quot;react-icons/md&quot;;

interface AccordionProps {
  rootStyles: string;
  type?: &quot;single&quot; | &quot;multiple&quot;;
  itemId: string;
  trigger: ReactNode;
  content: ReactNode;
}

export const Accordion = ({
  rootStyles,
  type = &quot;multiple&quot;,
  itemId,
  trigger,
  content,
}: AccordionProps) =&gt; {
const [isAccordionOpen, setIsAccordionOpen] = useState&lt;boolean&gt;(false);
const toggleAccordion = () =&gt; setIsAccordionOpen((isAccordionOpen) =&gt; !isAccordionOpen);

return (
    &lt;RadixAccordion.Root className={rootStyles} type={type}&gt;
      &lt;RadixAccordion.Item value={itemId} className=&quot;flex flex-col overflow-hidden&quot;&gt;
        &lt;RadixAccordion.Header&gt;
          &lt;div className=&quot;flex border p-2&quot;&gt;
            {trigger}
            &lt;RadixAccordion.Trigger /&gt;
              &lt;div onClick={toggleAccordion}&gt;
                  {!isAccordionOpen ? (
                    &lt;MdChevronRight size={32} className=&quot;rotate-90 transition-transform duration-150&quot;
                  /&gt;
                ) : (
                  &lt;MdChevronRight size={32} className=&quot;-rotate-90 transition-transform duration-150&quot;
                  /&gt;
                )}
              &lt;/div&gt;
            &lt;/RadixAccordion.Trigger&gt;  
          &lt;/div&gt;
        &lt;/RadixAccordion.Header&gt;
        &lt;RadixAccordion.Content&gt;{content}&lt;/RadixAccordion.Content&gt;
      &lt;/RadixAccordion.Item&gt;
    &lt;/RadixAccordion.Root&gt;
  );
};
"><code><span class="hljs-keyword">import</span> <span class="hljs-operator">*</span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">RadixAccordion</span> <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'@radix-ui/react-accordion'</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title">ReactNode</span>, <span class="hljs-title">useState</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title">MdChevronRight</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"react-icons/md"</span>;

<span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">AccordionProps</span> </span>{
  rootStyles: <span class="hljs-keyword">string</span>;
  <span class="hljs-keyword">type</span>?: <span class="hljs-string">"single"</span> <span class="hljs-operator">|</span> <span class="hljs-string">"multiple"</span>;
  itemId: <span class="hljs-keyword">string</span>;
  trigger: ReactNode;
  content: ReactNode;
}

export const Accordion <span class="hljs-operator">=</span> ({
  rootStyles,
  <span class="hljs-keyword">type</span> <span class="hljs-operator">=</span> <span class="hljs-string">"multiple"</span>,
  itemId,
  trigger,
  content,
}: AccordionProps) <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
const [isAccordionOpen, setIsAccordionOpen] <span class="hljs-operator">=</span> useState<span class="hljs-operator">&#x3C;</span>boolean<span class="hljs-operator">></span>(<span class="hljs-literal">false</span>);
const toggleAccordion <span class="hljs-operator">=</span> () <span class="hljs-operator">=</span><span class="hljs-operator">></span> setIsAccordionOpen((isAccordionOpen) <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-operator">!</span>isAccordionOpen);

<span class="hljs-keyword">return</span> (
    <span class="hljs-operator">&#x3C;</span>RadixAccordion.Root className<span class="hljs-operator">=</span>{rootStyles} <span class="hljs-keyword">type</span><span class="hljs-operator">=</span>{<span class="hljs-keyword">type</span>}<span class="hljs-operator">></span>
      <span class="hljs-operator">&#x3C;</span>RadixAccordion.Item value<span class="hljs-operator">=</span>{itemId} className<span class="hljs-operator">=</span><span class="hljs-string">"flex flex-col overflow-hidden"</span><span class="hljs-operator">></span>
        <span class="hljs-operator">&#x3C;</span>RadixAccordion.Header>
          <span class="hljs-operator">&#x3C;</span>div className<span class="hljs-operator">=</span><span class="hljs-string">"flex border p-2"</span><span class="hljs-operator">></span>
            {trigger}
            <span class="hljs-operator">&#x3C;</span>RadixAccordion.Trigger <span class="hljs-operator">/</span><span class="hljs-operator">></span>
              <span class="hljs-operator">&#x3C;</span>div onClick<span class="hljs-operator">=</span>{toggleAccordion}<span class="hljs-operator">></span>
                  {<span class="hljs-operator">!</span>isAccordionOpen ? (
                    <span class="hljs-operator">&#x3C;</span>MdChevronRight size<span class="hljs-operator">=</span>{<span class="hljs-number">32</span>} className<span class="hljs-operator">=</span><span class="hljs-string">"rotate-90 transition-transform duration-150"</span>
                  <span class="hljs-operator">/</span><span class="hljs-operator">></span>
                ) : (
                  <span class="hljs-operator">&#x3C;</span>MdChevronRight size<span class="hljs-operator">=</span>{<span class="hljs-number">32</span>} className<span class="hljs-operator">=</span><span class="hljs-string">"-rotate-90 transition-transform duration-150"</span>
                  <span class="hljs-operator">/</span><span class="hljs-operator">></span>
                )}
              <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">/</span>div<span class="hljs-operator">></span>
            <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">/</span>RadixAccordion.Trigger>  
          <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">/</span>div<span class="hljs-operator">></span>
        <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">/</span>RadixAccordion.Header>
        <span class="hljs-operator">&#x3C;</span>RadixAccordion.Content>{content}<span class="hljs-operator">&#x3C;</span><span class="hljs-operator">/</span>RadixAccordion.Content>
      <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">/</span>RadixAccordion.Item>
    <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">/</span>RadixAccordion.Root>
  );
};
</code></pre><h2 id="h-breaking-down-the-code" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0"><strong>Breaking Down the Code:</strong></h2><h3 id="h-typescript-types-accordionprops" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>TypeScript Types (AccordionProps)</strong>:</h3><p>This interface encapsulates a set of props, catering to scenarios where the Accordion component might be invoked multiple times with varied data.</p><ul><li><p><code>rootStyles</code>: Directs styling for the Root component, accepting TailwindCSS classes.</p></li><li><p><code>type</code>: Specifies if the accordion operates in “single” or “multiple” mode.</p></li><li><p><code>itemId</code>: A unique identifier for the Item.</p></li><li><p><code>trigger</code>: It&apos;s positioned outside of the <code>&lt;RadixAccordion.Trigger /&gt;</code> to ensure only the <code>MdChevronRight icon</code>, when clicked, initiates the accordion&apos;s action.</p></li><li><p><code>content</code>: A <code>ReactNode</code> type defining the content within <code>&lt;RadixAccordion.Content&gt;</code> nodes.</p></li></ul><h3 id="h-arrow-icon-behavior" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0"><strong>Arrow Icon Behavior</strong></h3><p>Clicking the arrow icon (<code>MdChevronRight</code>) toggles the accordion&apos;s state. The <code>isAccordionOpen</code> state starts as <code>false</code>, with the accordion defaulting to a closed position.</p><h2 id="h-design-philosophy" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Design Philosophy</h2><p>The Accordion is intentionally kept versatile, with <code>trigger</code> and <code>content</code> props as <code>ReactNode</code>, facilitating potential reuse. Avoiding excessive inline styling makes the component more maintainable.</p><h3 id="h-conclusion" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Conclusion</h3><p>My experience with Radix-UI was enlightening, and I foresee further engagements. While I&apos;ve worked with component libraries like Chakra-UI that offer pre-styled components, the customization process can be daunting, especially with specific brand guidelines in play.</p><p>Feel free to share your thoughts or questions regarding my Accordion component implementation below!</p><p><strong>GitHub</strong>: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://github.com/airswap/airswap-voter-rewards/blob/main/src/features/common/Accordion.tsx">https://github.com/airswap/airswap-voter-rewards/blob/main/src/features/common/Accordion.tsx</a>.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c01519d213d644ad0d44b4cb853c5015425f0c5333532ce9f79130e2759fe3c5.png" alt="Accordion component with all items closed" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Accordion component with all items closed</figcaption></figure>]]></content:encoded>
            <author>starrdev@newsletter.paragraph.com (starrdev)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/bbdeffa676bb814c257a7781304eb6878d0b3725a8778019938b6f9efbe79e95.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[
Building A Chain Selector For AirSwap]]></title>
            <link>https://paragraph.com/@starrdev/building-a-chain-selector-for-airswap</link>
            <guid>FrrqMGPMNbXKGlxq3LK8</guid>
            <pubDate>Fri, 09 Jun 2023 17:04:43 GMT</pubDate>
            <description><![CDATA[My Introduction to AirSwapMy introduction to AirSwap dates back to 2017 during the ICO boom. The project&apos;s resilience and its part in the Consensys ecosystem piqued my interest. Encouraged by AirSwap&apos;s open-source philosophy, I considered contributing to its growth.As a software enthusiast, my path led me to front-end development. The allure of instant feedback and the real-time reflection of JavaScript code sparked my interest in this field. For this reason, I joined as a front-end...]]></description>
            <content:encoded><![CDATA[<h2 id="h-my-introduction-to-airswap" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">My Introduction to AirSwap</h2><p>My introduction to AirSwap dates back to 2017 during the ICO boom. The project&apos;s resilience and its part in the Consensys ecosystem piqued my interest. Encouraged by AirSwap&apos;s open-source philosophy, I considered contributing to its growth.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/b8e0127d9954e08caceb72e1e98ebfaf1e533fd0ffa0c8a071bca6212ed2f59c.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>As a software enthusiast, my path led me to front-end development. The allure of instant feedback and the real-time reflection of JavaScript code sparked my interest in this field. For this reason, I joined as a front-end developer.</p><h1 id="h-understanding-airswap" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Understanding AirSwap</h1><p>AirSwap&apos;s primary product is a decentralized exchange (DEX) allowing for peer-to-peer swaps. Its unique model circumvents common issues such as slippage and front-running, typically associated with fully on-chain exchanges like UniSwap. The platform&apos;s ability to facilitate trades against institutional market makers adds to its appeal. Recently, AirSwap also launched a white-labelled NFT marketplace.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/881d605b52989bc67a7f1053781edd0b8c55381b861b970bb70ff51bdb3a0350.webp" alt="How AirSwap works" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">How AirSwap works</figcaption></figure><h1 id="h-why-airswap" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Why AirSwap?</h1><p>Before AirSwap, I was with RaidGuild, building apps in startup type of environments. Seeking a new challenge on a larger scale led me to AirSwap. With a substantial user base, a robust treasury, and a stimulating community, AirSwap became my chosen destination.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/247666b47ce588808399aacff7c1d42e84cd0a69062c4ed7b43e418198cd7016.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h1 id="h-acclimating-to-airswap" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Acclimating to AirSwap</h1><h2 id="h-exploring-the-codebase-of-airswap" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Exploring the Codebase of AirSwap</h2><p>Comprehending AirSwap&apos;s extensive codebase was an initial hurdle. A week of rigorous code perusal helped me understand the structure of its components. The process involved meticulous code scrutiny, peer consultations, and leveraging tools like ChatGPT and StackOverflow. Also diving deep into its documentations and reading its smart contract code was important for understanding what was happening.</p><h1 id="h-the-new-feature-network-selector" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">The New Feature: Network Selector</h1><p>Web3&apos;s interoperability, enabling apps to function across different blockchains, gives users the power to choose their preferred blockchain. Given that AirSwap&apos;s smart contracts are written in Solidity, they can be deployed on any EVM-compatible chain.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/a06997db419cedc17931e7428ffc21e5fc4c4a5a6caf38807b18eb4d32d96b01.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h2 id="h-multiple-chains-one-selector" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Multiple Chains, One Selector</h2><p>The ChainSelectionPopover and ChainSelector components are the feature&apos;s core. The ChainSelector button reveals a popover listing user-selectable chains.</p><p>Here are key code snippets and explanations illustrating the feature&apos;s core functionality:</p><pre data-type="codeBlock" text="import addEthereumChain from &quot;../../../helpers/addEthereumChain&quot;;
import switchToChain from &quot;../../../helpers/switchToChain&quot;;

const addAndSwitchToEthereumChain = async (chainId: number) =&gt; {
  const chainNotAddedCode = 4902;

  await switchToChain(chainId).catch((error: any) =&gt; {
    if (error.code === chainNotAddedCode) {
      addEthereumChain(chainId);
    }
  });
};

export default addAndSwitchToEthereumChain;
"><code><span class="hljs-keyword">import</span> <span class="hljs-title">addEthereumChain</span> <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"../../../helpers/addEthereumChain"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-title">switchToChain</span> <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"../../../helpers/switchToChain"</span>;

const addAndSwitchToEthereumChain <span class="hljs-operator">=</span> async (chainId: number) <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  const chainNotAddedCode <span class="hljs-operator">=</span> <span class="hljs-number">4902</span>;

  await switchToChain(chainId).catch((<span class="hljs-function"><span class="hljs-keyword">error</span>: <span class="hljs-title">any</span>) => </span>{
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">error</span>.<span class="hljs-built_in">code</span> <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> chainNotAddedCode) {
      addEthereumChain(chainId);
    }
  });
};

export default addAndSwitchToEthereumChain;
</code></pre><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/4b7a44519778d07d1ab673b3937404f667715d11c02c5a9f44104f13190d36d3.png" alt="Each of these buttons with different chains has the \`addAndSwitchToEthereumChain\` under the hood" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Each of these buttons with different chains has the \`addAndSwitchToEthereumChain\` under the hood</figcaption></figure><p><code>addAndSwitchToEthereumChain</code> function: This function is designed to either switch the user to a specified chain or, if the chain hasn&apos;t been added to their MetaMask wallet, add it. If the chain switch fails with a specific error code (indicating the chain hasn&apos;t been added), it triggers the addition of the chain.</p><pre data-type="codeBlock" text="import { CHAIN_PARAMS } from &quot;../constants/supportedNetworks&quot;;

// https://eips.ethereum.org/EIPS/eip-3085

const addEthereumChain = (chainId: number): Promise&lt;null&gt; =&gt; {
  return window.ethereum.request({
    method: &quot;wallet_addEthereumChain&quot;,
    params: [
      {
        chainId: `0x${CHAIN_PARAMS[chainId].chainId.toString(16)}`,
        rpcUrls: CHAIN_PARAMS[chainId].rpcUrls,
        chainName: CHAIN_PARAMS[chainId].chainName,
      },
    ],
  });
};

export default addEthereumChain;
"><code><span class="hljs-keyword">import</span> { <span class="hljs-title">CHAIN_PARAMS</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"../constants/supportedNetworks"</span>;

<span class="hljs-comment">// https://eips.ethereum.org/EIPS/eip-3085</span>

const addEthereumChain <span class="hljs-operator">=</span> (chainId: number): Promise<span class="hljs-operator">&#x3C;</span>null<span class="hljs-operator">></span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  <span class="hljs-keyword">return</span> window.ethereum.request({
    method: <span class="hljs-string">"wallet_addEthereumChain"</span>,
    params: [
      {
        chainId: `0x${CHAIN_PARAMS[chainId].chainId.toString(<span class="hljs-number">16</span>)}`,
        rpcUrls: CHAIN_PARAMS[chainId].rpcUrls,
        chainName: CHAIN_PARAMS[chainId].chainName,
      },
    ],
  });
};

export default addEthereumChain;
</code></pre><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/90e51f076ef279ed913fc2276b6641dfcfde99ff22ac591fd464dffd68354c31.png" alt="The \`wallet_addEthereumChain\` method will bring up this prompt if a user doesn&apos;t have the network added on MetaMask already" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">The \`wallet_addEthereumChain\` method will bring up this prompt if a user doesn&apos;t have the network added on MetaMask already</figcaption></figure><p><code>addEthereumChain</code> function: This function adds a specified chain to the user&apos;s MetaMask wallet. The function uses the <code>wallet_addEthereumChain</code> method from the Ethereum Provider API to add the chain and requires parameters such as <code>chainId</code>, <code>rpcUrls</code>, and <code>chainName</code>. These details are fetched from <code>CHAIN_PARAMS</code> constant, which presumably contains all the necessary information about the supported networks.</p><pre data-type="codeBlock" text="import nativeCurrency from &quot;../

constants/nativeCurrency&quot;;

const switchToChain = (chainId = 1): Promise&lt;null&gt; =&gt; {
  return window.ethereum.request({
    method: &quot;wallet_switchEthereumChain&quot;,
    params: [
      {
        chainId: `0x${nativeCurrency[chainId].chainId.toString(16)}`,
      },
    ],
  });
};

export default switchToChain;
"><code><span class="hljs-keyword">import</span> <span class="hljs-title">nativeCurrency</span> <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"../

constants/nativeCurrency"</span>;

const switchToChain <span class="hljs-operator">=</span> (chainId <span class="hljs-operator">=</span> <span class="hljs-number">1</span>): Promise<span class="hljs-operator">&#x3C;</span>null<span class="hljs-operator">></span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
  <span class="hljs-keyword">return</span> window.ethereum.request({
    method: <span class="hljs-string">"wallet_switchEthereumChain"</span>,
    params: [
      {
        chainId: `0x${nativeCurrency[chainId].chainId.toString(<span class="hljs-number">16</span>)}`,
      },
    ],
  });
};

export default switchToChain;
</code></pre><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f491e2473b455913393746a8269f5852a2f3f8b189f06a6db0aa68cde7e801b6.png" alt="The \`wallet_switchEthereumChain\` Method will open this prompt when switching networks" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">The \`wallet_switchEthereumChain\` Method will open this prompt when switching networks</figcaption></figure><p><code>switchToChain</code> function: This function switches the currently active chain in the MetaMask wallet to a specified chain. The function uses the <code>wallet_switchEthereumChain</code> method from the Ethereum Provider API to switch chains. The <code>chainId</code> parameter is used to specify which chain to switch to.</p><h1 id="h-styling-the-network-selector" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Styling the Network Selector</h1><p>AirSwap employs React Styled-Components for styling. These are CSS-in-JS tools that enable traditional CSS writing in JavaScript files. In our codebase, these Styled-Components are imported into other components, creating a structured, maintainable, and reusable styling scheme. This modular architecture enhances code maintainability and improves the development workflow.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/bdc5fe46b4e0044d37332c7748cfdfc3f72e380e920f38dabf8d28a3796cfb85.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h1 id="h-making-the-feature-mobile-responsive" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Making the Feature Mobile Responsive</h1><p>Ensuring mobile responsiveness is critical in today&apos;s digital age. AirSwap follows a &quot;mobile-first&quot; design approach, which means we design for the smallest screen first, then progressively enhance the design for larger screens.</p><p>To ensure mobile responsiveness, we used CSS breakpoints. These are points where a website&apos;s content responds to provide the user with the best possible layout. By using CSS breakpoints, we managed to define different styles for different devices according to their screen size. This ensured that our new feature looked good and functioned correctly across all devices, maintaining the usability and overall aesthetic.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/b96caad646f81a777d55a1361437516ac4b231e20b24957c84dcbbf01871359a.png" alt="Side-by-side comparison of desktop vs. mobile design. (Desktop is left, mobile is right)" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Side-by-side comparison of desktop vs. mobile design. (Desktop is left, mobile is right)</figcaption></figure><h1 id="h-conclusion" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Conclusion</h1><p>Contributing to AirSwap has been a profoundly educational journey into the realm of decentralized finance (DeFi). The opportunity to delve into its documentation and smart contracts has provided me with invaluable insights and learning experiences.</p><p>While my contributions thus far have focused primarily on front-end development, I&apos;m enthusiastic about expanding my role as I enhance my Solidity skills. The prospect of contributing to smart contract work excites me, as it opens a new avenue of impact and learning.</p><p>One aspect of open-source work that truly appeals to me is its transparency. Having my contributions publicly displayed, and my name now associated with the AirSwap project, is a source of pride and motivation.</p><p>Looking forward, my excitement to contribute to AirSwap goes beyond its core DEX product. I anticipate lending my skills and efforts to further enhance the platform, fostering innovation, and bolstering user experiences across the board. The journey thus far has been enriching, and I am enthusiastic about the opportunities that lie ahead.</p>]]></content:encoded>
            <author>starrdev@newsletter.paragraph.com (starrdev)</author>
        </item>
        <item>
            <title><![CDATA[How I Built An App That Wraps and Unwraps ETH]]></title>
            <link>https://paragraph.com/@starrdev/how-i-built-an-app-that-wraps-and-unwraps-eth</link>
            <guid>goJmYlxtG1qaQ8lPWjJB</guid>
            <pubDate>Mon, 05 Dec 2022 23:26:39 GMT</pubDate>
            <description><![CDATA[This article is not a tutorial. It&apos;s my thoughts and notes about my experience building an app that wraps and unwraps Ether. The app is called WrapETH.What is WrapETH?WrapETH is an app that allows you to wrap and unwrap ETH, (or another EVM chain&apos;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 Ethe...]]></description>
            <content:encoded><![CDATA[<figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/17b7d4574bbde3e9dab50eda7a2074541c8a0ff0e2755c39ae8d768bc83eac23.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>This article is not a tutorial. It&apos;s my thoughts and notes about my experience building an app that wraps and unwraps Ether. The app is called WrapETH.</p><h2 id="h-what-is-wrapeth" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">What is WrapETH?</h2><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://wrapeth.com/">WrapETH</a> is an app that allows you to wrap and unwrap ETH, (or another EVM chain&apos;s native asset). The current app version supports 7 different blockchains. The app is a front-end that communicates with the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2">Wrapped Ether</a> smart contract.</p><p>The Wrapped Ether (WETH) smart contract is decentralized code that exists on the Ethereum blockchain. It&apos;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.</p><p>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 <code>deposit</code> and <code>withdraw</code>. You can <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#code">view the code in the &quot;contract&quot; tab</a>, and see what other functions are built into the contract.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/021edcffa6d2b0950454b6870f6db1e3fd17ed1b3d002b54ead98e174e44b59d.png" alt="Wrapped Ether smart contract functions" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Wrapped Ether smart contract functions</figcaption></figure><h2 id="h-replacing-web3modal-with-rainbowkit-and-wagmi" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Replacing Web3Modal with RainbowKit and WAGMI</h2><p>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&apos;s makes developers&apos; lives easier.</p><p>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 &quot;Connect Wallet&quot; modal comes pre-built and styled, and you can customize it as desired.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/cbb37d0b99ec1ef6c67778ab154d650f68eff874e6116390c30bdf69ee4bda82.png" alt="RainbowKit UI - you can easily add support for various wallets" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">RainbowKit UI - you can easily add support for various wallets</figcaption></figure><h2 id="h-what-are-wagmi-hooks" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">What Are WAGMI Hooks?</h2><blockquote><p>&quot;WAGMI makes it easy to &quot;Connect Wallet,&quot; display ENS and balance information, sign messages, interact with contracts, and much more — all with caching, request deduplication, and persistence.&quot; (from the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://wagmi.sh/">docs)</a>.</p></blockquote><p>It&apos;s a library of pre-made hooks that uses <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.ethers.io/v5/">Ethers.js</a> 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.</p><p>An example of a WAGMI hook is <code>useAccount</code>. This returns an object that has details like the connected address, as well as boolean values such as <code>isConnected</code>.</p><p>Another useful hook is <code>useBalance</code>. The gets data about the connected wallet&apos;s account balance. It has a boolean property called <code>watch</code>, which updates the connected balance at every new block.</p><p>This hook saved me from writing many extra lines of code.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/380237c9a5121c30306f57d8c18df6a2ec1d47421f70c1ebed2c69c3678efbaf.jpg" alt="The ETH balance gets updated automatically" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">The ETH balance gets updated automatically</figcaption></figure><p>Another set of hooks I used were <code>usePrepareContractWrite</code>, <code>useContractWrite</code> and <code>useWaitForTransaction</code>. If you want to call &quot;read&quot; and &quot;write&quot; functions on a contract, the WAGMI docs suggest using these hooks together.</p><p><code>usePrepareContractWrite</code> is used to prepare an instance of a smart contract. It returns an object that has a key called <code>config</code>. <code>config</code> can get passed into the <code>useContractWrite</code> hook, which is used to call smart contract functions that were extracted from <code>usePrepareContractWrite</code>.</p><p><code>useWaitForTransaction</code> extracts data from <code>useContractWrite</code> and returns various data such as a hash of a successful transaction.</p><p>Implementing these hooks was challenging. I&apos;ve only scratched the surface of what WAGMI hooks can do. Check out the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://wagmi.sh/">WAGMI docs</a> to learn more.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/fd43544755546e3934ca94b81e569388b40edd57da41e46589945870aa42e6e5.png" alt="WAGMI logo" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">WAGMI logo</figcaption></figure><h2 id="h-refactoring-code-and-building-custom-hooks" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Refactoring Code And Building Custom Hooks</h2><p>It was my first time using the WAGMI library. To get used to the hooks, I first placed everything into the main <code>app</code> component in my Next.js directory. This quickly became messy.</p><p>I tried prop drilling to pass hooks to child components of <code>app</code>, but this quickly becme messy, and introduced bugs to my code. I also tried calling hooks within a <code>useEffect</code> hook, but learned that you cannot nest hooks within other React hooks.</p><p>Like magic, refactoring all WAGMI hooks into custom hooks, within a <code>/hooks</code> directory, helped fix many of my bugs. Placing hooks into their own files made my code more readable and easier to maintain.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/49b7066c255f87c5806eb155852d1eed2f3b262c596061d0a6d395415d585d9d.png" alt="File structure containing hooks" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">File structure containing hooks</figcaption></figure><p>The idea of build custom React hooks used to intimidate me, but hooks are simple. Basically hooks are functions with the word <code>use</code> at the start of its name.</p><h2 id="h-replacing-formik-with-react-hook-form" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Replacing Formik With React-Hook-Form</h2><p>This was my second major task of the project. It was also the most challenging for me. <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://react-hook-form.com/">React-Hook-Form</a> is a JavaScript library that helps manage forms, form state and validations.</p><p>React-form-hook has some funky syntax, but the documentation is well written and has good video explanations.</p><p>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&apos;s critical that strict validations are in place. A bug in the front-end can cause an unexpected loss of funds.</p><p>People will track you down and send hate mail if they lose money in your app.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/4a4d285023b95e7f0ed977cb4042ca4a5d4e22cee2387762f158260bd00262c0.jpg" alt="React-Hook-Form lets you create custom error messages" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">React-Hook-Form lets you create custom error messages</figcaption></figure><h2 id="h-integrating-react-hook-form-with-raid-guild-design-system" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Integrating React-Hook-Form with Raid Guild Design System</h2><p>Raid Guild has a <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/raid-guild/design-system">Design System</a>, which is built with <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://storybook.js.org/">Storybook</a> and custom <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://chakra-ui.com/">Chakra UI</a> style components. Each design component aligns with the branding aesthetic that Raid Guild has in place.</p><p>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&apos;s also highly customizable.</p><p>One of my major challenges was setting up the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/raid-guild/design-system/tree/main/src/components/atoms/NumberInput">NumberInput</a> component, and integrating it with react-hook-form with it.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3ebfd436f2ddf40fd9c601aa9dd48834eecd05c3941444ef218b75c51113c3d1.png" alt="Raid Guild design system preview" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Raid Guild design system preview</figcaption></figure><p>NumberInput takes in a spread of props from react-hook-form. If you&apos;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.</p><p>I received lots of help on this task. Since finishing the WrapETH project, I&apos;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.</p><h3 id="h-passing-numeric-values-smart-contracts" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Passing numeric values smart contracts</h3><p>Another challenge I faced was passing data from the form to the <code>usePrepareContractWrite</code> 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.</p><p>Ethereum processes numbers in units of wei, (1 Ether is equal to 10^18 wei <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.ethers.io/v5/api/utils/bignumber/#BigNumber--notes-safenumbers">(source)</a>. Due to limitations that JavaScript has on integers, you need to use BigNumer to correctly pass numerical values to Ethereum smart contracts.</p><p>Figuring out the correct conversions took a bit of tinkering, but eventually I figured it out.</p><h2 id="h-other-ux-improvements" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Other UX improvements</h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0faafde47b6edae0ef4607dfaecb500138b1b0ce878b7e237e28b415f694b07a.jpg" alt="&quot;Pending Transaction&quot; toast is shown in the upper right" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">&quot;Pending Transaction&quot; toast is shown in the upper right</figcaption></figure><p>An important part of front-end development is giving users feedback when they perform an action. For example, if you click &quot;submit&quot;, you probably want confirmation that the form was actually submitted</p><p>Feedback especially matters when money is involved. Your heart will skip a beat if you transfer funds, but don&apos;t get confirmation that the transaction actually gets sent.</p><p>With this in mind, I added a &quot;toast&quot; feature that alerts a user after a transaction has been initiated, and completed.</p><p>Chakra UI has a <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://chakra-ui.com/docs/components/toast">built in hook</a> that triggers a toast. I changed its style on Raid Guild&apos;s <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/raid-guild/design-system">Design System</a>, and added the toast to WrapETH.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/07c44b4aa5db532ec9671c9c4ab63ebaa3762aefe2fe9157457943481839529e.png" alt="Chakra UI default Toast component. (Note the design difference between this, and the Toast in the upper right corner of the previous image)." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Chakra UI default Toast component. (Note the design difference between this, and the Toast in the upper right corner of the previous image).</figcaption></figure><h3 id="h-heres-how-i-implemented-the-toast-hook" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Here&apos;s how I implemented the toast hook:</h3><p>A toast gets fired off in the following scenarios:</p><ul><li><p>pending deposit</p></li><li><p>pending withdrawal</p></li><li><p>successful deposit</p></li><li><p>successful withdrawal</p></li></ul><p>There are custom hooks in the app called <code>useDeposit</code> and <code>useWithdraw</code>. Within these hooks, I added the WAGMI hooks <code>useContractWrite</code> and <code>useWaitForTransaction</code>.</p><p><code>useContractWrite</code> is used to write transactions to a contract. The WAGMI docs recommend using it with <code>usePrepareContractWrite</code>. This prepares an instance of a contract and takes in parameters such as <code>address</code> and <code>abi</code>.</p><p>After a transaction has been initiated with <code>useContractWrite</code>, you can use built-in logic to determine if the transaction is pending or not. The hook returns a function called <code>onSuccess</code>; this gets invoked when the hook fetches data.</p><p>Within the custon <code>useDeposit</code> hook, when <code>onSuccess</code> gets called, the <code>useCustomToast</code> hook from Design-System will be invoked. <code>useCustomToast</code> displays the alert you see in the upper right corner of the following image:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/14778c801ae0c658e488c19460976e585b25ef7170613db815dd1d73aa65378e.jpg" alt="&quot;Success&quot; toast is shown in the upper right. Note that " blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">&quot;Success&quot; toast is shown in the upper right. Note that</figcaption></figure><p>Similar logic is used within the <code>useWaitForTransaction</code> hook. This waits for a transaction to be processed, then returns an <code>onSuccess</code> function. <code>onSuccess</code> displays the &quot;success&quot; toast seen above.</p><h2 id="h-typescript" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">TypeScript</h2><p>This was my first time using <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.typescriptlang.org/">TypeScript</a>. 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 &quot;learn by doing.&quot; I prefer this to following tutorials nonstop.</p><p>At first I didn&apos;t know how to fix type errors, so I&apos;d set the type to be <code>any</code>. This can work to troubleshoot unrelated problems, but it negates the benefits of using TypeScript in the first place.</p><p>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.</p><p>Since finishing the WrapETH project, I&apos;ve continued using TypeScript in other apps. With more practice, it&apos;ll become second nature.</p><h2 id="h-local-environment-issues" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Local Environment Issues</h2><p>Most new developers don’t think about their development environments. If you&apos;re new to tech, you likely don&apos;t even know what an environment is. I didn&apos;t.</p><p>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).</p><p>I cannot remember all of the bugs I fixed, but here are some of my notes:</p><h3 id="h-make-sure-to-work-on-the-same-node-version-as-the-most-recent-version-of-the-app" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Make sure to work on the same Node version as the most recent version of the app.</h3><p>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.</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/nvm-sh/nvm">Node Version Manager (NVM)</a> solves this.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/48a7635a4d1066d00da4e8d5504d125511968bd767e4a1f150bace7d148b9b38.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>With NVM, you can run multiple versions of Node in separate terminals. For example, if you&apos;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.</p><h3 id="h-check-that-your-path-is-setup-correctly" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Check that your Path is setup correctly.</h3><p>Path (<code>$PATH</code>) 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&apos;t fully understand it yet.</p><p>After troubleshooting bugs that were related to my dependency tree, it was determined that deleting and reinstalling my OS was a logical next step.</p><h3 id="h-howd-i-even-get-into-this-mess" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">How&apos;d I even get into this mess?</h3><p>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&apos;t solve my issues. This lead to a Path file so confusing, Satoshi Nakamoto wouldn&apos;t even make sense of it.</p><p>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&apos;s setup.</p><p>After reinstalling everything, I followed this guide <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://medium.com/javascript-scene/setting-up-a-new-macbook-for-javascript-development-289df3f8f9">”Setting Up a New MacBook for JavaScript Development</a> which worked well.</p><h2 id="h-git-issues" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Git issues</h2><p>Merge conflicts and other git issues are a pain point for me (and most developers).</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f498155bcc31dc0ddb2a049578cd12e647b4df0a849f1c32ee5b612f6b085f16.png" alt="Diagram of various Git features" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Diagram of various Git features</figcaption></figure><p>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&apos;t ideal.</p><p>The person who mentored me through this project recommended I try <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.git-tower.com/">Git Tower</a>, a visual tool for git. In the future I might write about this tool after using it more.</p><h3 id="h-git-stash" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Git Stash</h3><p>Before working on WrapETH I&apos;ve never used <code>git stash</code>. Sometimes Git recommends this command if you&apos;re trying to commit changes, but your branch isn&apos;t up to date with the latest changes.</p><p><code>git stash</code> saves the current version of your working branch. You can later apply your changes from the stash onto whatever branch you&apos;d like.</p><p><strong>Pro-tip</strong> <em>(for git newbies)</em>: Name your stashes with the command: <code>git stash save &apos;name-stash&apos;</code>. Naming a stash isn&apos;t required, but if you have too many stashes saved, you’ll get confused.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/fbb5255353682ecc2b2a000dc3fcebeee6dd06ebc29768b81924e66c4fd06cd7.png" alt="Visual Studio Code built in with Git features" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Visual Studio Code built in with Git features</figcaption></figure><p>I learned that VS Code has a tab on the left side called &apos;Source Control&apos;. This has visual helpers such as: commits, branches, remotes, stashes and more.</p><p>If you click the &apos;STASHES&apos; dropdown, you&apos;ll see your stashes. Right clicking on a stash will give you several options, for example &apos;apply stash&apos;.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/413424e1cffff60c2358b384ecc4eb23074a4332caf226a46eacb7916ec0a835.png" alt="Right clicking on STASHES gives you several options" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Right clicking on STASHES gives you several options</figcaption></figure><h2 id="h-my-apprenticeship-at-raid-guild" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">My Apprenticeship At Raid Guild</h2><p>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.</p><p>To learn about my earlier experience at Raid Guild, read my article: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/starrdev.eth/CLyAqP66WK73n518rd9xTMTzH9NDUjop-Ju49Agb-O4">&quot;My Experience as a Raid Guild Apprentice&quot;</a>.</p><p>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.</p><p>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 <em>lot</em> of questions, but I&apos;m always working to balance asking for help, vs. struggling to find a solution by myself.</p><h2 id="h-the-benefits-of-mentorship" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">The Benefits Of Mentorship:</h2><p>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.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0c0d5781a3a575d0fb7aa8bb7c53a121f9636e66c93e1969c301386b47e097a5.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>Raid Guild is a strong network of talented people who are on the cutting-edge of Web3. <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://handbook.raidguild.org/docs/overview">Learn more about Raid Guild here</a>.</p><h2 id="h-conclusion" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Conclusion</h2><p>Working on WrapETH was both challenging and a great learning experience. I got practice reading documentation, implementing new libraries and building interesting tools.</p><p>By using many different libraries for the first time, I realized that many libraries are similar; it&apos;s all JavaScript. If you understand data types and data structures, you can figure out most libraries.</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://wrapeth.com">WrapETH</a> currently supports these networks: Ethereum mainnet, Gnosis (XDAI), Polygon, Arbitrum, Optimism, Goerli and Sepolia testnets.</p><p>There are no fees (other than network fees), so try it out next time you need to wrap and unwrap ETH. I&apos;ve used it several times on Ethereum mainnet, which was the most rewarding part of the experience.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/b2b28841614cdc1501ccdad1d08e3a364d3e27f7e2b321e74fda1b7d634f60f5.jpg" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>WrapETH <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/raid-guild/wrapeth">GitHub</a>.</p>]]></content:encoded>
            <author>starrdev@newsletter.paragraph.com (starrdev)</author>
        </item>
        <item>
            <title><![CDATA[My Experience as a Raid Guild Apprentice]]></title>
            <link>https://paragraph.com/@starrdev/my-experience-as-a-raid-guild-apprentice</link>
            <guid>o2n4Xzr0kxiwn0BpQzEB</guid>
            <pubDate>Fri, 16 Sep 2022 20:50:05 GMT</pubDate>
            <description><![CDATA[My Experience as a Raid Guild Apprentice“If you want to go fast, go alone. If you want to go far, go together...”My journey as a software developer started about 15 months ago. Coming from a non-technical background in marketing, I didn&apos;t know many engineers. Knowing the right people won&apos;t make you smarter, but they can point you in the right direction and you&apos;ll learn more efficiently. Having a strong network of people is half the battle to success. In January, 2022 I started ...]]></description>
            <content:encoded><![CDATA[<h1 id="h-my-experience-as-a-raid-guild-apprentice" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">My Experience as a Raid Guild Apprentice</h1><p><em>“If you want to go fast, go alone. If you want to go far, go together...”</em></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ec285f56bfbce028d1195e386075c4151a3ed58dbeae64de11a8a6c87ca7452b.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>My journey as a software developer started about 15 months ago. Coming from a non-technical background in marketing, I didn&apos;t know many engineers.</p><p>Knowing the right people won&apos;t make you smarter, but they can point you in the right direction and you&apos;ll learn more efficiently. Having a strong network of people is half the battle to success.</p><p>In January, 2022 I started a 15 week software engineering program at Flatiron School. My goal was to eventually work for a blockchain project, or to start my own.</p><p>During my bootcamp, I met somebody who worked at Consensys. His advice for me, to break into web3, was to join a DAO and contribute to open source projects. He recommended joining Raid Guild as a place to start.</p><p>When I completed the engineering bootcamp, I filled out an application for an apprenticeship at Raid Guild. A couple months later I got an email from the Guild.</p><h2 id="h-who-am-i" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Who am I?</h2><p>I&apos;ve been interested in crypto and blockchain for years. Starting out, I only speculated on prices. Eventually I became interested in how crypto actually works, so I began reading documentation such as the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://bitcoin.org/bitcoin.pdf">Bitcoin white paper</a> and &quot;<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/ethereumbook/ethereumbook">Mastering Ethereum</a>.&quot;</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/27581c6020fe1ca987d077ced795d5198133e2ecf221a0dd7c686043784b7ecc.png" alt="Excerpt from the Bitcoin Whitepaper" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Excerpt from the Bitcoin Whitepaper</figcaption></figure><p>I understood that learning tech would give me a huge advantage in life. I didn&apos;t have much of a technical background growing up, but I was determined to learn as much as I could about crypto.</p><p>In 2021 I began learning how to code. My end goal was to break into the blockchain industry and become a professional in the space. I purchased a Solidity course on Udemy and got started.</p><p>Most Solidity tutorials I watched had a section about React. I didn&apos;t understand JavaScript, so I felt confused. This lead me down a rabbit hole into learning front-end development. In hindsight, I could have continued without knowing any JavaScript.</p><p>During this time, my main focus in life was rock climbing and mountaineering. An overuse injury in my elbow forced me to take time off from climbing. Sitting at home with nothing to do, I began spending all of my time learning JavaScript.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/62d6659383ddadea1c2f3167c08e3311c75cfde5bc412624b29fd01e0413168b.jpg" alt="That&apos;s me climbing &quot;Chrystler Crack&quot; in Red Rock Canyon" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">That&apos;s me climbing &quot;Chrystler Crack&quot; in Red Rock Canyon</figcaption></figure><p>Unexpectedly, I actually enjoyed writing code. Coding also seemed to give my life a new sense of fulfillment outside of climbing. During this time, the voice inside my head was telling me that that I was the type of person who could succeed at this type of work.</p><p>After studying JavaScript for months and building an app (a simple game of blackjack built with vanilla JS), I decided to take a software engineering bootcamp. I chose a bootcamp that had a full-stack developer curriculum that focused on JavaScript, React, Ruby and Ruby on Rails.</p><p>Taking a bootcamp is a great way to lower your learning curve, but you can learn the same content from YouTube if you&apos;re motivated enough.</p><p>After I completed the bootcamp, I joined a few hackathons (organized by <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethglobal.com/events/hackathons">ETH Global</a>) and focused on improving my front-end development skills. I felt prepared to work for a DAO after the hackathons.</p><h2 id="h-what-is-a-dao" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">What is a DAO?</h2><p>Taken from <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethereum.org/en/community/get-involved/#decentralized-autonomous-organizations-daos">Ethereum.org</a>:</p><blockquote><p>&quot;DAOs&quot; are decentralized autonomous organizations. These groups leverage Ethereum technology to facilitate organization and collaboration. For instance, for controlling membership, voting on proposals, or managing pooled assets. While DAOs are still experimental, they offer opportunities for you to find groups that you identify with, find collaborators, and grow your impact on the Ethereum community</p></blockquote><p>Typically DAOs don&apos;t have a physical location (with exceptions like <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://empiredao.xyz/">Empire DAO</a>). Communication mostly happens on Discord. There’s no official boss or CEO and people can come and go as they wish. The more work you put into a DAO, the more you get back.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/1566e083d86226f48e32fd00b4b76852c97d14e6c9adc006ea5eaa6247b4185f.jpg" alt="Inside the Empire DAO co-working space in NYC" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Inside the Empire DAO co-working space in NYC</figcaption></figure><p>Most DAOs will require you to own shares, or assets such as a tokens or an NFT to gain access. This is analogous to owning shares in a company. In theory, by owning an asset related to the DAO, holders of these assets should feel incentivized to contribute their own resources (labor) to improve the organization.</p><h3 id="h-other-daos-worth-exploring" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Other DAOs worth exploring:</h3><p>Some DAOs create open-source software that can be used as public or common goods. Public goods might also include tools that the DAO has created for its own use. For example, a staking contract for humans seeking membership in an organization.</p><p>Some other DAOs worth exploring are <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.developerdao.com/">Developer DAO</a>, dOrg, YAPDAO, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://molochdao.com/">Moloch DAO</a>, <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://makerdao.com/en/">MakerDAO</a> and <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://yearn.finance/">Yearn Finance</a>. Each of these DAOs serve a different purpose, but some of them have overlap.</p><p>Developer DAO is like a social club where developers and non-developers work together on open source projects. It’s a great place to meet like-minded people and expand your network.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/b3aec6cc4d122068d9f995e3401e76f8ee09842ad9fe908f8e7fa4f12c881e3d.jpg" alt="DAOs are a great place to meet people and form a hackathon team" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">DAOs are a great place to meet people and form a hackathon team</figcaption></figure><p>YAPDAO is another service DAO that focuses on non-technical work for DeFi projects, such as marketing and communication. YAPDAO is a friendly fork of Raid Guild.</p><p>Both MakerDAO and Yearn Finance are DeFi projects that are also DAOs. If you own tokens for these projects, you have the right to vote with your tokens. For example, if a new proposal is made about a major change in the direction of the project, you have the right to vote on the decision.</p><p>In theory this allows anybody who feels passionately about these projects to have a voice in how the project should move forward. If you disagree with the direction a project is going, you can sell your tokens and remove yourself from the governance process, or ragequit.</p><p>The first DAO was formed in 2016 and had a catastrophic failure (which resulted in millions of dollars getting hacked). Onboarding new members to a DAO is one of the biggest problems that people are working to solve.</p><h2 id="h-my-experience-with-raid-guild" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">My experience with Raid Guild</h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f2e28d5c863e3fa65a58bf85b0603788e121d645474b77a65b39953c40751ee2.jpg" alt="Where the magic happens…" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Where the magic happens…</figcaption></figure><p>Today marks the start of my 4th week as an apprentice. DAO’s are still a new concept and are evolving rapidly. My experience will likely be vastly different from yours, if you decide to become a Raid Guild apprentice.</p><h3 id="h-the-onboarding-experience" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">The onboarding experience</h3><p>My journey with Raid Guild started by filling out an application here: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.raidguild.org/join">https://www.raidguild.org/join</a>. A month or 2 later I was contacted about an upcoming cohort.</p><p>During the first week of the apprenticeship, we met over voice calls in the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://discord.com/invite/rGFpfQf">Discord</a>. We introduced ourselves, our backgrounds and interests in web3 (and beyond web3). We were filled in on what to expect by joining a DAO. It was mostly educational and focused on team building.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/1527c898aec744b146e3eae3fb0799fb434445cdc4f6c68014619653f31824f6.jpg" alt="Week 1 was mostly team building" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Week 1 was mostly team building</figcaption></figure><p>In the second week it was time to start working on projects. By this point, about half of the original cohort had dropped out and fell victim to Moloch (the demon God of human coordination failure). Many of us worked together on an existing open-source project called “Rite of Moloch.” It was an internal Raid Guild project that had been started, but not yet completed.</p><p>For the apprenticeship, the purpose of this project was to give us a taste of what to expect if we became full Raid Guild members. It also gave us a chance to see how well we worked together as a team. It was up to us to figure out what still needed to be built, with minimal guidance.</p><p>In a DAO there’s no boss. Nobody assigns you work, so it’s up to you to take ownership of a task and make sure you complete what you said you will.</p><p>Another major task during the second week was for all cohort members to acquire the DAO’s native token, $RAID, and stake it into the Rite of Moloch smart contract. The purpose of staking tokens was to show commitment to the process, but also to form a sub-DAO for the cohort. This novel idea was taken from the Moloch DAO framework.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/9a8530e2f23612938d03f073998b8190cd3e2c85f2210f87cba5d0ce0ad9c17f.jpg" alt="After staking $RAID, cohort members receive a Soul Bound Token" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">After staking $RAID, cohort members receive a Soul Bound Token</figcaption></figure><p>Requiring cohort members to stake tokens into a smart contract gives a sense of commitment and ownership towards the DAO. At least it does for me.</p><h3 id="h-my-technical-experience-with-raid-guild" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">My technical experience with Raid Guild:</h3><p>Working on the “Rite of Moloch” project was a fun learning experience. My contributions were mostly with front-end development. The UI was built mostly with Next.js, Chakra UI and Ethers.js.</p><p>My biggest contribution so far has been creating a new route on Next.js called <code>deploy-cohort</code>. I built a controlled form with React, styled it with Chakra UI components, and built a function that uses Ethers.js to connect to the blockchain.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/67e533596481800c00080c80c0824a71811f0c5d56f88fc4458ab4a93601f6a7.jpg" alt="Screenshot of “Deploy Cohort” feature of Rite of Moloch DApp" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Screenshot of “Deploy Cohort” feature of Rite of Moloch DApp</figcaption></figure><p>This feature is a template that allows anybody to create their own DAO and customize its settings. After the cohort has been deployed and people stake their tokens into the contract, they’ll receive a soul bound token.</p><h3 id="h-education" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Education:</h3><p>One of the most valuable parts of being in a DAO is that you can ask other people for help. There’s a high chance that somebody with more experience can point you in the right direction.</p><p>Sometimes people will generously give a technical lecture to anybody who wants to listen. I was able to attend a smart contract developer walking us through a contract, and a product designer teaching us the basics of UX design.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e8389ae8821c7b0775debe2769fd034d47fec9fe7b91a843ae237b5fb8f25028.jpg" alt="We learn together" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">We learn together</figcaption></figure><h3 id="h-benefits-ive-gained-during-my-apprenticeship-at-raid-guild" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Benefits I’ve gained during my apprenticeship at Raid Guild</h3><p>I’ve already covered some of these, but here’s a list of benefits that come to mind by joining a DAO:</p><ul><li><p>Learn about crypto and web3 by working on cutting-edge projects</p></li><li><p>Grow your network and build connections with industry veterans. Some people in the DAO have been working in the space for years. Having access to these people is priceless, especially if you&apos;re new to the space</p></li><li><p>Improve your developer skills by contributing to open-source projects</p></li><li><p>Make friends. We usually talk about work-related topics, but we also have a good time</p></li><li><p>If you’re transitioning to a new career in software development, joining a DAO is an easy “foot in the door.” Working for a DAO is challenging, but getting in is easy because you don’t need to send in a resume and have formal interviews. As a recent web2 bootcamp graduate with no professional background in tech, all I can say is that it’s VERY difficult to go from zero-to-one and get your first job in tech. Joining a DAO is a hack which lowers your barrier to entry, if you want professional experience</p></li><li><p>On the topic of starting a new career in tech: you will probably make little or no money working for a DAO at the start. This should not be an issue if you’re job-seeking. My bootcamp at Flatiron School gave me access to a career coach for 6 months from my graduation date. My coach has even advised me to work an unpaid internship or apprenticeship as a means to get experience. My experience as an apprentice at Raid Guild is something I’ve highlighted on my resume.</p></li><li><p>Free technical education. So far I’ve attended technical discussions about Solidity and UX design. Someday I’d like to give back and lead my own technical discussion</p></li><li><p>Joining a DAO is somewhat of an entrepreneurial pursuit. It’s for self-starters who have clear goals and don’t need external motivation to get things done. This is not a good path for everyone, but it might be perfect for you?</p></li><li><p>Improve at non-technical skills. Web3 needs people skilled in project management, technical writing, accounting and more. In fact, I received some feedback from DAO members before publishing this article!</p></li></ul><h2 id="h-my-future-plans-with-raid-guild" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">My future plans with Raid Guild</h2><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/52add6e91c545fd26da992495717da9e24625ae39cf8c9c59c1f4a902db39313.jpg" alt="My main plan is to continue slaying Moloch" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">My main plan is to continue slaying Moloch</figcaption></figure><h6 id="h-my-primary-plan-is-to-continue-slaying-moloch" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">My primary plan is to continue slaying Moloch</h6><p>So far I’ve been enjoying my time with the guild. I plan to continue contributing where I can, whether it’s code for an open source project, or writing articles like this (which is essentially marketing for Raid Guild and for myself). As my software developer skills improve, I plan to join official “Raids”, where I’ll get paid for my work. I also plan to get a full time job working in web3, while simultaneously giving value back to the Guild, which has already given me so much.</p><p>My next project is another hackathon. It&apos;s not directly for the DAO, but I formed a team with people that I met here and we&apos;ll be representing Raid Guild at the hackathon.</p><h2 id="h-conclusion" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Conclusion</h2><p>Joining a DAO is great way to learn about web3 and gain professional-level experience. It takes time and dedication to be successful within a DAO, but you’ll reap great rewards for your efforts.</p><p>Raid Guild stands out from other DAO’s because of the cohort experience onboarding process. Most people who go through the cohort process feel a sense of ownership, pride and camaraderie. You’ll improve your skills as a developer, (or whatever skills you contribute) and build new connections within the web3 industry.</p><p>If you’re looking to break into a new career in tech, this is one of the best paths you can take. Improve your skills and make a positive impact on the world.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/39c4481bb31aee7519c890dd854bf1b8b6a101d598850268d2dd50d42912b5cf.jpg" alt="Raid Guild - we&apos;re ready to slay your Web3 deamons..." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Raid Guild - we&apos;re ready to slay your Web3 deamons...</figcaption></figure><p>If you&apos;re interested in joining Raid Guild, read more about it on <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.raidguild.org/">https://www.raidguild.org/</a>.</p>]]></content:encoded>
            <author>starrdev@newsletter.paragraph.com (starrdev)</author>
        </item>
    </channel>
</rss>