Building a Comet Extension

Comet Extensions give users the ability to take advantage of supplementary tools and third-party apps that integrate with Compound III. One of the many strengths of the DeFi ecosystem is the ability to compose several independent protocols into a single transaction to create a fine-grained position. Extensions simplify common flows that would otherwise require multiple, independent protocol actions, with a simple cohesive interface that is attached to an Ethereum account.

Developers can build their own unique Extensions for Comet that run as stand-alone applications or as embedded apps in the app.compound.finance interface. Open source boilerplate repositories are available on GitHub so you can get started building your own Extension with ease and speed.

A few prerequisites are needed to start building a new Extension today:

  • Knowledge of React.js for front end web.
  • Foundry and Solidity for your Extension’s smart contracts (optional).
  • Your Extension must integrate with an official instance of Compound III.

Note that extensions hosted in the interface at app.compound.finance are curated by the owner of the web domain. Compound Labs reserves the right to reject any extensions from being added to the starter interface. Stand-alone Comet Extension apps are allowed to be hosted anywhere as permitted by the license in the open source code repository.

How do I build an Extension?

To build a Comet Extension a developer needs to clone the boilerplate React.js repository to their machine. Then they can build out their Extension’s front end and smart contracts with the help of Foundry and Vite. These tools spin up a localhost development instance so developers can confirm their code is working as intended in their sandboxed environment.

After the code is complete and ready to be implemented into the starter interface, a developer can create a pull request into the official Extensions repository. There are examples for adding the Extension to the JSON config noted in the README file.

All Extensions are required to be audited and reviewed by the Compound Labs team prior to being merged into the official repository.

Can I Convert an Existing Dapp into an Extension?

Yes. That can be done with a small, new dependency and a few lines of code to shim the app into the Compound starter interface. This will be covered towards the end of this guide in the section Converting an Existing Dapp into a Compound Extension. If you do not need the context involved in making an extension from scratch, you can skip to the conversion section below.

Building an Example Extension From Scratch

In this section we’ll walk through building an example Extension that shows all borrowing accounts in a table, sorted by their health. In this context, account health refers to the measure of the distance from becoming liquidatable. As this percentage approaches 100%, the accounts are borrowing against more of their total collateral value. Accounts with “Percent To Liquidation” that is >=100% can presently be liquidated by the protocol. Accounts that do not have any open borrow position will not appear in the table.

For borrows that have just one asset as collateral, a liquidation price can be calculated based on that asset. For example, with the present borrow balance of an account that is only supplying WETH, the account violates the collateral requirements and becomes liquidatable at a WETH price of $1150 based on the amount of WETH it has supplied to the protocol.

The values for the table are calculated periodically using a Node.js server that is hooked up to an Infura JSON RPC provider. The server exposes a REST API that the Extension front end fetches its data from. The server shows up to date data while implementing rate limiting to sparingly query Infura for cost efficiency.

If we were to propose this as an official Extension, the web API would need to be audited by Compound Labs before being deployed to production.

The front end code of this Extension is a simple React.js interface that builds on the original boilerplate example code.

The smaller panel on the right shows data of the currently connected Ethereum account. The Extensions boilerplate provides hooks to the connected account as well as any smart contracts that are a part of the Extension’s source code. This particular example does not implement any new smart contracts.

The REST API Service

The server and API code is in a separate, independent repository. We’ll begin there.

git clone https://github.com/ajb413/comet-extension-example-api.git
cd comet-extension-example-api/
npm i

The Node.js server uses ethers.js to fetch data from the blockchain, it holds its data ephemerally as a JS object called db, and exposes a REST API for returning public data. Every 20 minutes, the db is updated with fresh data. As users make new GET requests, the API will update on demand but no more frequently than every 90 seconds.

The source code holds metadata for each Comet instance, so as more Comet instances are deployed, this API could theoretically support them with plug-and-play ease.

Every so often, the server application will update the chain state data that it currently holds in memory. This is the data that will be returned to the users of the REST API. There is an application level check to rate limit updates that run on the server side to Infura.

The process asks the Comet contract how many collateral assets there are each time the function is run. That way, when a governance proposal to add a new collateral executes, the server will be able to include it immediately without configuration.

The server pulls asset metadata, like the number of decimals in a token smart contract, and then pulls price data. It then pulls all present borrower’s account data and calculates the health of the accounts.

The server uses the data it fetched to calculate the borrow limit, the liquidation limit, the percent the account is currently away from liquidation, and the liquidation price if there is one type of collateral for the account.

The API has a single getter function for the collection of account data. As more instances of Comet become supported, they can be specified in a request using the URL param in the front end app’s designation of the Comet instance.

If this extension were to be productionized, the API service could be deployed to a hosting provider like AWS, Heroku, or the like.

Your Smart Contracts

The example app that we’re working on here does not have any smart contracts of its own. It simply accesses the existing Comet contracts via off-chain JSON RPC. If your extension project has smart contracts, they can be included in the same project as the front end code of the extension.

The Foundry toolkit is included in the project template as a dependency. You can follow the instructions in the readme file to spin up your own playground and connect your localhost front end app to the smart contracts on your localhost node. That way, everything is running locally during development.

In the next section, we’ll review how to access the built ABIs and contract address from the React.js app instantly, and interact with the forked Ethereum node using Ethers.js.

The Front End Code

The React.js app for the extension is copied from the template repository for Comet Extensions. Use the GitHub template repository page to copy the template to your own account or organization. Once you have a copy of the template, you can clone it to your own machine to begin development.

If you haven’t installed Node.js and Yarn please do so before continuing.

git clone __Git_URL_for_your_Extenstion_repository__
cd my_extension/
yarn install

To run the web app locally with live hot reloading as you edit the source code files, run the following command and begin development in a text editor or IDE.

yarn web:dev

Next navigate to http://127.0.0.1:5183/ in a web browser to see the extension interface running live on your local machine. Once the instance is running, we can begin adding React.js code and also Sass styles to our Extension.

This app will appear as a whole page, standalone app for now. If it were to go into production, it would be put into an iFrame in the official Extensions interface. More on testing that later (see Extension Sandbox for Testing below).

First we’ll review the changes made to web/App.tsx that interact with the REST API and render a table with all of the relevant account health data.

At the bottom of the file, we have an async code block that fetches the public data from the API based on the selected Comet instance in the Extensions app interface. The app uses a unique URL parameter value for each Comet instance.

We pass that value to the API in order to get the data for the proper instance that the user has already selected. The source code defaults to the Mainnet USDC base instance in scenarios where the URL parameters is not present, like in the standalone app.

Once the app has loaded all of the relevant data, it can render it as HTML in the DOM. Notice that the React app has several parameters that are passed to it including the browser wallet’s selected user account, borrower data, and also a network config object. More on the network config later.

The HTML table rows are rendered in a helper function using the data returned from the API. The function includes parameters for the indexes and range of rows rendered in case we want to add pagination to the Extension later on.

Lastly, the panel on the right side of the page contains account data specific to the account connected through the Web3 browser wallet, like MetaMask. The page makes a request through the local Web3 provider to find the borrow balance of the connected account. The app uses the injected network config to make these JSON RPC requests.

The networkConfig object contains mappings pertaining to contract interaction for Compound v2 and v3. It also imports the ABIs and addresses for your own Comet Extension smart contracts.

See the Network.ts file and its imports to learn more about how your Extension smart contract code addresses and ABIs can be injected into the React app for easy interfacing from the front end code.

A few styles are needed for the updated Extension interface. Add the following Sass to the styles/main.scss file to finish up the changes. These styles make the HTML table and font look clean.

Extension Sandbox for Testing

To try out the Extension in the iFrame, go to app.compound.finance/extensions/sandbox. Copy the above React.js app URL with the port and paste it into the sandbox source URL input. Then click the “update source” button, then the “allow this third-party extension” button above the iFrame. If the API app and Vite app are both running in the background, the iFrame on the page should load the Extension as it would look in production.

Message Passing

The parent Compound app window can pass messages to the child iframe in which the Extension is hosted.

Local storage get and set are enabled as well as market selection, theme selection, and Comet state.

More bidirectional message types can be found in the Comet Extension repository Message Types file.

Converting an Existing Dapp into a Compound Extension

This section is optional to read; it applies strictly to those that want to convert their existing dapp into a Comet Extension. Already built your own JavaScript Web3 app? That is all that is needed to proceed. A shim can be added to an existing app such that it will seamlessly fit into the Compound starter app as an Extension. This example uses React.js but other frameworks will work as well.

React

The first step to adding the shim to your app is to include the simple dependency for Comet Extensions. Install this package using NPM or Yarn in your existing dapp folder.

yarn add @compound-finance/comet-extension

The shim is needed to apply the bindings when creating the Web3 provider object. We can add some logic that creates the special provider object only when the dapp is included as an embedded Comet Extension. With this implementation, the minimum possible code changes are implemented to your existing codebase.

Now the Compound starter app has a way of passing data to the embedded extension application. To see an example of this integration in an existing dapp that was converted into an Extension, check out the Comp.Vote codebase on GitHub.

Vanilla JS

This example does not require any frameworks like React to enable the Extension shim.

Simply import a library like Web3.js or Ethers.js, along with the Comet Extension library, and enable the injected provider. In this example we can use the CometExtension object to create the provider. If the URL includes the embedded parameter, the Comet Extension library will create a provider object.

Wrapping Up

In this guide we went over building an example Comet Extension that accesses a back end API. These apps can run as standalone applications or inside the starter app at app.compound.finance. To get your extension approved, drop a line in Discord and share the code with the community before reviewing the pull request instructions in the central Comet Extension repository.

Thanks for reading and be sure to get in touch in the #development room of the very active Compound Discord server. Also explore the community forums at comp.xyz. There are community developers there and they are excited to help out and to see what you build next.

6 Likes