project screenshot 1
project screenshot 2
project screenshot 3
project screenshot 4
project screenshot 5
project screenshot 6

ghooey

ghooey is a versatile drop-in front-end toolkit that helps developers build and integrate Aave widgets in their existing codebase with just HTML markup. The baseline is to help promote the growth the adoption of web3 protocols in web2 products through developer experience.

ghooey

Created At

LFGHO

Winner of

Aave - Integration Prize

Prize Pool

Project Description

ghooey is a versatile toolkit designed for effortlessly constructing interactive websites atop the robust Aave protocol using nothing but HTML markup.

By seamlessly integrating ghooey into their websites, developers gain the ability to create tailor-made widgets, facilitating user onboarding onto the Aave ecosystem. These widgets empower users to:

  • Monitor their Aave portfolio
  • Deposit and borrow liquidity from Aave markets
  • Transfer liquidities and delegate their borrowing powers to other users

Motivations behind ghooey

Tech stack matters, actually

Modern DeFi, Dated TradFi: tooling contrast

Although the usage of blockchain and DeFi by the finance and banking sectors seems to increase year by year, several industries still remain conservative in regards to technological evolution, which makes them slow to adopt new tools and paradigms for their online experiences.

As of January 2024, W3Techs reports that WordPress (still) dominates, being used by 43.1% of all websites worldwide, followed by Shopify (4.2%), Wix, Squarespace, Joomla, and Drupal, which means half of all websites run on PHP.

<div class="mx-auto"> <img src="/cms_usage.png" alt="A chart that display the percentages of websites using various content management systems" /> </div>

Furthermore, jQuery is employed by over 80% of all websites, and continues to be a staple in web developer education. In contrast, web3 protocols like Aave often adopt more modern tech stacks such as Next.js/Nuxt.js, React.js/Vue.js, signaling a shift towards contemporary technologies.

While the topic of tech stacks might appear nerdy or trivial, this discussion holds the key to significantly increasing the adoption of web3 in mainstream industries: if it wants to foster growth and onboard the next billion users, web3 needs to address developers first and offer tools that are easy to integrate and work with within existing products.

What building on web3 means

For neobanks, integrating DeFi features into online products is a straightforward endeavor. However, for established entities and their sometimes dated practices, updating an existing front-end to incorporate new web3 features proves to be quite the challenge:

  • Interacting with smart contracts from the front-end demands a unique skill set, encompassing familiarity with web3 concepts and tools such as smart contracts, wallets, gas fees, signatures, and specific libraries (e.g., ethers v5/ethers v6/wagmi/viem/web3.js).

  • In many pre-built UI kits, components often operate in isolation, making data accessible only through a specific way (e.g., consuming a Context in React) or being confined to the underlying framework, hindering seamless integration with existing products. To be able to use those UI kits, the codebase often needs to be written entirely in that underlying framework.

  • The underlying styling solutions in certain UI kits necessitate learning new libraries of a quite consequent size, present significant technical trade-offs (e.g., heaviness, inaccessibility, non-SSR-friendly), and offer limited customization or are difficult to customize.

  • Some UI kits may require a build/bundling step with a tool incompatible with the underlying tech stack.

This non-exhaustive list highlights the pressing need for web3 building blocks that cater to a broader audience of developers — developers that, despite not using the latest trend, work with libraries and frameworks that power over 80% of all websites. Onboarding these developers - and products they maintain - is crucial for promoting the use and growth of web3 protocols like Aave. As such, the tools and developer products created must satisfy two key requirements:

  • [x] Easy setup and seamless integration with pre-existing websites
  • [x] Quick comprehension and user-friendliness for developers accustomed to fundamentally different front-end frameworks

The future is at the crossroads of boring web2 and edge web3. This is why drop-in developer toolkits like ghooey are necessary.

How it's Made

Overview

This submission includes 3 elements:

  • the ghooey Javascript library (code available here: https://github.com/naomiHauret/lfgho-hackathon-2024/tree/main/src/scripts )
  • the ghooey documentation (available here: https://ghooey.netlify.app/00-overview/), built with Astro
  • integration examples (available here: https://ghooey.netlify.app/examples/) with markup available here (https://github.com/naomiHauret/lfgho-hackathon-2024/tree/main/src/components)

How does ghooey work ?

the ghooey library is built on top of Alpine.js, a lightweight, minimal and extensible JavaScript framework that allows to write declarative code directly from our markup to build interactive websites.

A simple yet powerful library, Alpine can be learned in a couple of hours and doesn't require any specific setup besides adding a script to the page. As such, its the perfect base to build upon. Essentially, ghooey is a single JavaScript file that, by leveraging Alpine stores (global state), contexts (local state) and magics (inline functions), let developers build widgets that can interact with the Aave ecosystem via aave-utilities under the hood.

The markup and style of those widgets are completely customizable: the developers just need to use Alpine directives (like x-data, x-init...) and reference the proper stores and states.

For instance, the snippet below can showcase a "Pay" button that will :

  • Display a "Connect your wallet" message if they aren't connected through an injected web3 wallet ;
  • Display the GHO balance of the connected user
  • Display a button they can use to transfer 0.5 GHO
  • Perform the transfer operation and update the UI to reflect the various steps
  • Automatically update their balance after the transfer

All this with just HTML markup and no additional JavaScript.

<template x-if="!$store.currentUser.account">
  <p>Connect your wallet to pay</p>
</template>
<template x-if="$store.currentUser.account">
  <section x-data="{...erc20Transfer(), token: $aaveAssetBySymbol('GHO') }">
    <button
      x-text="status === 'signaturePending' ? 'Sign transfer to continue' : status === 'transactionPending' ? 'Transferring...' : 'Pay ' + $formatERC20Balance(amount, 'GHO')"
      x-data="{ amount: 0.5, sendTo: '0xe90406d09418C4EdBD7735c62F9FED7294954905'}"
      :aria-disabled="['signaturePending','transactionPending'].includes(status) || $store.currentUser.assets.fetchStatus === 'pending' || parseFloat($store.currentUser.assets?.balances?.GHO?.formatted ?? 0) <= 0 ? true : false"
      class="aria-[disabled='true']:opacity-50 aria-[disabled='true']:pointer-events-none"
      @click="transferTokens({ token, amount, recipientAddress: sendTo})"
    ></button>
    <template x-if="$store.currentUser.assets?.balances?.GHO?.formatted">
      <p
        :class="$store.currentUser.assets?.balances?.GHO?.fetchStatus === 'refreshing' && 'animate-pulse'"
        x-text="'Balance: ' + $formatERC20Balance($store.currentUser.assets?.balances?.GHO?.formatted, 'GHO')"
      ></p>
    </template>
    <template
      x-if="['pending', 'refreshing'].includes($store.currentUser.assets?.fetchStatus) || ($store.currentUser.assets?.balances?.GHO?.fetchStatus === 'pending' && !store.currentUser.assets?.balances?.GHO?.formatted)"
    >
      <p x-text="'Fetching GHO balance...'"></p>
    </template>
  </section>
</template>

Being built on top of Alpine, ghooey can be easily added into a codebase and does not require any build step.

To get started, you need to :

  1. Install Alpine via a CDN
  2. Add ghooey like so if you installed Alpine through a CDN :
<html>
  <!-- ... -->

  <!-- mandatory: add ghooey -->
  <script src="/path/to/public-folder/ghooey.js" defer></script>

  <!-- optional: add your own alpine extensions -->
  <script src="/path/public-folder/my-alpine-custom-directives-and-extensions.js" defer></script>

  <!-- mandatory: add Alpine -->
  <script src="/path/to/alpine.js" defer></script>

  <!-- ... -->
  <body x-data></body>
</html>

You can start building widgets to help users borrow/lend on Aave or transfer tokens like GHO with Alpine directives and predefined methods.

Alpine.js basics

Alpine.js, the foundation on which ghooey is built, provides a lightweight, easy-to-use solution for adding dynamic behavior to your web pages via your HTML markup. Getting started with Alpine only takes a few hours, thanks to a straightforward documentation and abundance of learning resources (videos, articles, podcasts, conferences...).

To use ghooey, it's essential to be familiar with some of Alpine's key concepts.

We will go through those concepts below, but advise you to review Alpine documentation as well for more information.

  • Declarative Syntax : Alpine.js leverages a declarative approach, allowing you to define data and behavior directly in your HTML.

  • Directives: Directives are specific data-attributes set in the markup and prefixed with x-. They provide a way to apply behavior to elements. There are 15 attributes in Alpine.

<div x-data="{ greeting: 'Hello, Alpine!' }">
  <p x-text="greeting"></p>
  <button x-on:click="greeting = 'Hi there!'">Change Greeting</button>
</div>

In this example, the paragraph's text content is dynamically bound to the property greeting, defined in the directive x-data and initialized with the value 'Hello, Alpine!'. Clicking the button triggers a change in the greeting and will display 'Hi there!' instead of 'Hello, Alpine!'.

  • Global stores: Alpine makes it easy to create make data available to every component on the page thanks to global stores.. Data and functions defined in a Alpine.store(...) method can be referenced and used easily anywhere in the markup using the magic $store() property. ghooey defines and exposes 2 global stores: currentUser and aaveMarkets.

  • Local states: Alpine provides re-usable local data and contexts. Data and functions defined in a Alpine.data(...) method can be passed and used easily in HTML block thanks to the x-data directive. ghooey defines and exposes 7 re-usable data slices.

Aave specific re-usable states

  • walletAavePortfolio, to get the detailed summary of the Aave portfolio of a given Ethereum wallet ;

  • aaveSupply enable connected users to supply a ERC20 token that's ERC-2612 compatible to an Aave pool ;

  • aaveBorrowReserveAsset enable connected users to borrow a reserve asset from an Aave pool (requires the user to have supplied to a pool) ;

  • aaveRepayDebt enable connected users to repay their Aave loan using their balance of the same token they borrowed ;

  • aaveRepayDebt enable connected users to repay their Aave loan using their balance of the same token they borrowed ;

  • aaveWithdrawAsset enable connected users to withdraw the assets they supplied to an Aave pool ;

  • aaveCredit - WIP - will enable connected users to delegate their borrowing power to other Ethereum addresses (which can mean other users or smart contracts).

ERC-20 re-usable states

  • erc20Transfer, to enable connected users to transfer an Aave featured ERC20 token to another Ethereum address
  • erc20BalanceOf, to get the balance of a specific ERC20 token for a specific Ethereum address

Events

ghooey watches multiple onchain events to refresh the displayed data (user portfolio and Aave markets data). It also dispatches custom events on window that you can leverage in your own application (to perform a request to your API for instance).

Here is a list of all custom events :

  • "ERC20_TRANSFER" - dispatched when a "Transfer" onchain event involving the currently connected wallet address occurs ;

  • "USER_SUPPLY_POOL" - dispatched when a "Supply" onchain event involving the currently connected wallet address occurs ;

  • "USER_BORROW_FROM_RESERVE" - dispatched when a "Borrow" onchain event involving the currently connected wallet address occurs ;

  • "USER_REPAY_DEBT" - dispatched when a "Repay" onchain event involving the currently connected wallet address occurs ;

  • "USER_WITHDRAW_ASSETS" - dispatched when a "Withdraw" onchain event involving the currently connected wallet address occurs ;

4 magic properties to expose essential utilities functions that can be used in the markup of your widgets.

Aave-specific helper functions and utilities

$aaveAssetsDictionary()

Use this custom property in your markup to get complete the list of assets (ERC20 tokens) supported by Aave V3 along with their metadata.

Example :

<ul>
  <template x-for="assetSymbol in Object.keys($aaveAssetsDictionary)">
    <li x-text="assetSymbol"></li>
  </template>
</ul>

$aaveAssetBySymbol()

Use this custom property in your markup to expose and access an asset by its symbol from the markup more easily

Example: <span x-text="$aaveAssetBySymbol('GHO')?.UNDERLYING"></span>

Regular helper functions and utilities

$formatERC20Balance()

Use this custom property in your markup to localize and format an Aave supported ERC-20 token amount

Example: <p x-text="$formatERC20Balance("183983.23289329", 'DAI')"></p>

$formatNumber()

Use this custom property in your markup to localize and format numbers in a specific format.

Examples:

  • Display as formatted percentage: <p x-text="$formatNumber('0.23', { style: 'percent'})"></p>
  • Display as formatted currency with currency code <p x-text="$formatNumber(summary.availableBorrowsUSD, { style: 'currency', currency: 'USD'})"></p>
  • Display as a regular formatted number <p x-text="$formatNumber(summary.healthFactor)"></p>

By extending Alpine with global stores and reusable states, ghooey gives to developers access to custom properties and attributes which, under the hood and with the help of the aave-utilities package, unlock performing onchain operations (like ``borrow()orsupplyWithPermit()). Typically, one reusable exposes an Aave functionality, along with useful user indicators, like fetchStatus` for instance.

This overview should allow you to quickly build a widget for your product, from a simple button that will send a tip with a fixed amount of a specific ERC20 token to a complete stable-rate loan provider.


Current limitations

Unsupported features

Currently, ghooey doesn't feature credit delegation yet.

Wallet connectors

Currently, ghooey relies on injected wallet and the API and events they expose to perform any task that requires a wallet signature.

Library size

Due to using libraries that are quite heavy under the hood themselves, ghooey.min.js is quite heavy (roughly 1.8MB). Possible iterations to improve its size would be :

  • Use viem instead of ethers v5, which would also require to stop using aave-utilities ;
  • Let developers decide which data slices, helpers and global stores to use instead of importing them all. This would be a fairly straightforward improvement, as ghooey main file looks like this :
import {
  registerStoreAaveMarkets,
  registerStoreCurrentUser,
  registerMagic$formatERC20Balance,
  registerMagic$assetsDictionary,
  registerMagic$assetBySymbol,
  registerDataAaveSupplyPool,
  registerDataERC20BalanceOf,
  registerDataERC20Transfer,
  registerDataWalletAavePortfolio,
  registerMagic$formatNumber,
  registerDataAaveBorrowReserveAsset,
  registerDataAaveRepayDebt,
  registerDataAaveWithdrawAsset,
} from './alpinejs'

// Stores ("$store.<store-name>.<key>")
const STORE_CURRENT_USER = 'currentUser'
const STORE_AAVE_MARKET = 'aaveMarkets'

// Data slices (x-data="<slice-name>")
const DATA_SLICE_WALLET_AAVE_PORTFOLIO = 'walletAavePortfolio'
const DATA_SLICE_AAVE_SUPPLY = 'aaveSupply'
const DATA_SLICE_ERC20_TRANSFER = 'erc20Transfer'
const DATA_SLICE_ERC20_BALANCE_OF = 'erc20BalanceOf'
const DATA_SLICE_AAVE_BORROW_RESERVE_ASSET = 'aaveBorrowReserveAsset'
const DATA_SLICE_AAVE_REPAY_DEBT = 'aaveRepayDebt'
const DATA_SLICE_AAVE_WITHDRAW_ASSET = 'aaveWithdrawAsset'

// Magic custom directives ("$<directive name>")
const MAGIC_FORMAT_ERC20_BALANCE = 'formatERC20Balance'
const MAGIC_FORMAT_NUMBER = 'formatNumber'
const MAGIC_AAVE_ASSETS_DICTIONARY = 'aaveAssetsDictionary'
const MAGIC_AAVE_ASSET_BY_SYMBOL = 'aaveAssetBySymbol'

/**
 * Initialize ghooey primitives
 */
export function setupGhooey() {
  document.addEventListener('alpine:init', async () => {
    registerStoreCurrentUser(STORE_CURRENT_USER)
    registerStoreAaveMarkets(STORE_AAVE_MARKET)
    registerDataWalletAavePortfolio(DATA_SLICE_WALLET_AAVE_PORTFOLIO)
    registerDataAaveSupplyPool(DATA_SLICE_AAVE_SUPPLY)
    registerDataAaveBorrowReserveAsset(DATA_SLICE_AAVE_BORROW_RESERVE_ASSET)
    registerDataERC20Transfer(DATA_SLICE_ERC20_TRANSFER)
    registerDataERC20BalanceOf(DATA_SLICE_ERC20_BALANCE_OF)
    registerDataAaveRepayDebt(DATA_SLICE_AAVE_REPAY_DEBT)
    registerDataAaveWithdrawAsset(DATA_SLICE_AAVE_WITHDRAW_ASSET)
    registerMagic$formatERC20Balance(MAGIC_FORMAT_ERC20_BALANCE)
    registerMagic$formatNumber(MAGIC_FORMAT_NUMBER)
    registerMagic$assetsDictionary(MAGIC_AAVE_ASSETS_DICTIONARY)
    registerMagic$assetBySymbol(MAGIC_AAVE_ASSET_BY_SYMBOL)
  })
}

Compatibility with other front-end frameworks

In theory, Alpine can work with any existing markup. It might however require slight modifications to function within a React/Next application for instance, but it should be possible.

background image mobile

Join the mailing list

Get the latest news and updates