Building the AirSwap Debugger App: A Technical Overview

This blog post will give a technical overview of how I built the AirSwap Debugger app. I'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: https://github.com/airswap/airswap-debugger

App: https://check.airswap.eth.limo/

What is the AirSwap Debugger?

The AirSwap trading platform relies on makers to facilitate orders
The AirSwap trading platform relies on makers to facilitate orders

To understand this app, first you must understand what a “maker” is. AirSwap makers are liquidity providers for the trading platform.

Makers run web servers that implement APIs like RFQ and LastLook. To build these APIs, makers provide JSON objects with certain values.

To learn more about makers, RFQ and LastLook, read the official documentation.

airswap-debugger 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.

Tech Stack

This app is built using TypeScript, JavaScript, React, and TailwindCSS, with blockchain connections facilitated by the viem and wagmi libraries. The app was initialized with Vite.

viem and wagmi 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 “Creating Seamless Web3 Wallet Connections in Your DApp Using Wagmi Hooks”.

How The App Works

Displaying errors relies on 2 key functions
Displaying errors relies on 2 key functions

There are 2 main functions in the app: validateJson and check.

  1. validateJson: 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 check function won’t run.

  2. check: this function exists on the AirSwap SwapERC20 smart contract, and requires 11 different inputs to run without errors. This is the function that checks whether or not a JSON is valid.

Validate JSON function

validateJson is a JavaScript function that uses TypeScript to validate the shape and contents of the user’s JSON input. If these validations fail, the check function won’t run.

For non-developers, errors returned by check can be vague if any values are missing. This is why the validateJson was implemented on the front-end app.

validateJson has 3 major checks:

  • JSON must be in valid syntax

  • Specific JSON key-value pairs are present

  • JSON values are in the correct format. For example, some values must be a valid ERC20 addresses. validateJson checks this by using the isAddress function, which is imported into the React component from the viem library

validateJson has several if..else code blocks which run validations. If any part of the JSON is invalid, an error gets pushed into an array called errorsList and returned by the function.

Code snippet from the validateJson function
Code snippet from the validateJson function

If there are no errors, validateJson returns false. Otherwise it returns a list of errors. We’ll revisit this errorsList variable later in the post.

Check Function

The check function lives on the AirSwap SwapERC20 smart contract. airswap-debugger is just a UI that makes it easier to use this function.

check validates the following inputs:

  1. nonce

  2. expiry

  3. signer wallet

  4. signer token

  5. signer amount

  6. sender token

  7. sender amount

  8. sender wallet

  9. transaction signatures (3 separate values: “v”, “r” and “s”)

The AirSwap protocol collects revenue from trading fees, so it’s critical that makers are able to validate their technical setups.

UI Components

File component structure
File component structure

The main React components include: App, Toggle, JsonForm, UrlForm, Errors, Selector and Dialogue. There are some smaller components that aren’t important to go over, for example the header.

App

This is the main entry point of the app. Here’s a breakdown of the different sections of the App component.

Imports:

At the top of the component you’ll see various imports from React, Wagmi, @airswap/libraries, and various hooks and components from other files.

React State Hooks

Code snippet of all the useState hooks used
Code snippet of all the useState hooks used

airswap-debugger utilizes React’s useState hook. Here’s an overview of each state variable:

  • inputType: this can either be json or url. Depending on which value it is, different forms will render, and different functions will run.

  • jsonString: when a user pastes a JSON, it’s a string that gets saved to this variable. Later on, jsonString gets parsed into a valid JSON object, and stored in a different variable.

  • urlString: 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 Toggle component, and used in a click handler.

  • parsedJson: This variable gets set after a user presses the submit input button. The JSON gets parsed in the native JavaScript function JSON.parse.

  • decompressedJson: 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 Dialog component.

  • swapContractAddress: this variable stores the smart contract address of AirSwap’s SwapERC20 address. We need the correct address so that wagmi is able to call the correct contract functions.

  • selectedChainId: 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 Selector component section.

  • errors: this is a list of errors if a JSON or server URL is invalid.

  • isEnabledCheck: this is a boolean value that serves as an on/off switch for the check function. It helps prevents the app from rendering outdated data.

  • isNoErrors: this is a boolean value that gets set to true if no errors were found. If true, and if isEnabledCheck is also true, a special message will be displayed to the user. If isEnabledCheck is false, then the check function hasn’t yet run. In this case we show the user nothing until they run the debugger.

Custom Hooks

Code snippet of useDecompressedOrderFromUrl hook
Code snippet of useDecompressedOrderFromUrl hook

useDecompressedOrderFromUrl: this hook was build to convert a server URL into JSON. The library @airswap/util library has a function called decompressFullOrderERC20. This accepts a server URL string and converts it to human-readable JSON.

useJsonValues: 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 check function require.

Smart Contract Interaction:

useContractRead hook from wagmi is utilized 5 times. The first time is in the check function.

The other 4 return values are: protocolFee, domainName, domainChainId, and domainVersion. These values are specific to the deployed SwapERC20 contract. SwapERC20 is deployed on various chains, including Mainnet, BSC, Avalanche, and others. On different chains, domainChainId would change. Upgraded contract versions might have different domainVersion values.

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.

checkFunctionArgs:

This is a list of inputs that we pass into the args property of useContractRead for the check function.

checkFunctionArgs utilizes TypeScript for validations
checkFunctionArgs utilizes TypeScript for validations

Event Handlers:

There are 2 handlers that control the forms on the app: handleChangeTextAreaJson and handleChangeTextAreaUrl. These function similarly, and get called on each of the textarea HTML elements within the JsonForm, and UrlForm. There’s also the handleSubmit, which will be explained next.

Submit Function (handleSubmit)

Several smaller functions and hooks make up the submit function. The function begins by initializing the following useState hooks to their default values: setParsedJson, setIsEnabledCheck, setErrors, setIsNoErrors.

Clicking this button on the UI runs the handleSubmit function
Clicking this button on the UI runs the handleSubmit function

The following functions make up the handleSubmit function:

validateInputs

This checks for blank inputs. If inputs are blank, handleSubmit returns nothing and ends.

handleJsonSubmission

If input type is json, this function runs and parses a JSON string into valid JSON format. It then sets the parsedJson variable equal to this value. Lastly, it runs the function checkSmartContractError.

checkSmartContractError

This accepts in an argument errorCheck, which gets returned from the check function. This function handles unreadable errors returned from the smart contract, and makes them more human-readable.

handleUrlSubmission

This is similar to handleJsonSubmission, but for server URLs.

validateInputs

This function checks that a user input isn’t blank. Otherwise the smart contract check function won’t run.

If the user tries submitting a blank form, the app will return an error
If the user tries submitting a blank form, the app will return an error

useEffect Hook #1

There are 2 useEffect hooks in the App component. Here’s how the first one works:

  1. The hook first gets triggered when the parsedJson value changes. Recall that running handleSubmit updates parsedJson.

  2. At the start of this useEffect hook, the validateJson function runs. validateJson returns a boolean value. If the value returned is true, there are errors which will get pushed into the errors array.

  3. Next, the function getOutputErrorsList runs. This takes in an argument checkFunctionData, which is an array of errors returned from the check function. These errors are unreadable, in hex format. getOutPutErrorsList maps over this array of hex values, then uses the hexToString function, which is imported from viem, to decode the hex formats into human-readable strings.

  4. Next in the useEffect hook is the function displayErrors. This parses errors returned from the smart contract, and makes them human-readable. For example, if an error contains 'NonceAlreadyUsed', then displayErrors will switch that into the error message: 'Nonce: the nonce entered is invalid'.

  5. Next is the function handleFormattedListErrors. This cleans up any errors returned from the smart contract that have too much blank space. These errors get pushed as strings into the errors array.

  6. Lastly, the useEffect hook checks if there are no errors in the errors array. If no errors, the state variable isNoErrors gets set to true.

If there are no errors, this special message is displayed
If there are no errors, this special message is displayed

useEffect Hook #2

This hook checks for a change in chainId, the blockchain network ID. When a change is detected, the function getAddress from the AirSwap SwapERC20 contract gets called, and sets the correct address in the swapContractAddressvariable. swapContractAddress gets passed into the useContractRead hook from wagmi, which is used to call the check smart contract function.

Forms & Toggle

You can toggle between JSON and URL forms
You can toggle between JSON and URL forms

We have 2 forms on the app: jsonForm and urlForm. Each form lives in separate React components. Both forms correspond to the relevant functions mentioned above. The Toggle component contains 2 buttons, which lets you select the form you want to use.

Errors

When a user enters an invalid JSON, errors are displayed on the right
When a user enters an invalid JSON, errors are displayed on the right

This component’s purpose is to render errors that are returned from the smart contract, or any of the following functions: validateJson, checkSmartContractErrors, or displayErrors.

The Errors component either displays nothing, a list of errors, or a message that reads “🎊 No errors found! 🎊”.

airswap-debugger is mobile responsive. On tablets and larger screen, there are 2 columns: forms on the left, and Errors on the right. On mobile devices, the UI displays 1 column: forms on top, and Errors on the bottom.

On mobile devices, the app displays as a single column
On mobile devices, the app displays as a single column

Selector

The selector has several blockchain networks (chain IDs) a user can choose. Users can choose a chainId from the selector, or optionally hard-code it into their JSON objects. If chainId is hard-coded into a JSON, it’ll override the selector option, but not vice-versa.

The selector lets users choose from a variety of networks
The selector lets users choose from a variety of networks

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 RadixSelect component from Radix-UI. Radix-UI is easy to use, and is compatible with TailwindCSS!

Dialog Component

The dialog is a modal which displays the decompressed URL
The dialog is a modal which displays the decompressed URL

dialog 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.

Challenges I Faced While Building The AirSwap Debugger

Managing state among React components

Getting the errors component to display the correct errors took some trial & error
Getting the errors component to display the correct errors took some trial & error

As the app grew in complexity, it became hard to manage the errors array state variable. Various functions, hooks, and components update errors, 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.

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 console.log a lot.

A common process I use to track down bugs is locate the function that is causing the error. Within that function, I’ll put console.log statements after every line, then hunt down the exact issue. Simple, yet effective!

Error: Buffer: buffer not found

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.

To fix this error, I found an npm called vite-plugin-node-polyfills. I added this to my project, and updated my vite.config.js file like so:

export default defineConfig({
  plugins: [
    react(),
    nodePolyfills({
      include: ['path', 'stream', 'util'],
      exclude: ['http'],
      globals: {
        Buffer: true,
        global: true,
        process: true,
      },
      overrides: {
        fs: 'memfs',
      },
      protocolImports: true,
    }),
  ],
});

Designing the UX

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.

For airswap-debugger, I took inspiration from airswap-voter-rewards
For airswap-debugger, I took inspiration from airswap-voter-rewards

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.

Navigating a project solo, start to finish

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!

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.

My confidence as a software developer has improved because of this. Building the airswap-debugger app from the ground up is my proudest coding achievement so far.

Conclusion

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.

If you’ve read this far, thank you for your time. I wrote this blog post for several reasons:

  • Enhance my writing skills

  • Build my personal brand, showcase my abilities, and build in public

  • Improve my ability to organize thoughts

  • Create valuable resources for others

  • Establish future sentimental value (it'll be enjoyable reading this post in 5 years)

During the writing process, I reviewed each line of code, allowing me to audit my work and identify areas for improvement.

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!)

Check out the GitHub repository for a deeper look into the code: https://github.com/airswap/airswap-debugger

Here’s the deployed app. https://check.airswap.eth.limo/

Subscribe to starrdev
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.