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
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:
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.
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:
The future is at the crossroads of boring web2 and edge web3. This is why drop-in developer toolkits like ghooey
are necessary.
This submission includes 3 elements:
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 :
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 :
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, 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.
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).
erc20Transfer
, to enable connected users to transfer an Aave featured ERC20 token to another Ethereum addresserc20BalanceOf
, to get the balance of a specific ERC20 token for a specific Ethereum addressghooey
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.
$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>
$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:
<p x-text="$formatNumber('0.23', { style: 'percent'})"></p>
<p x-text="$formatNumber(summary.availableBorrowsUSD, { style: 'currency', currency: 'USD'})"></p>
<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()or
supplyWithPermit()). 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.
Currently, ghooey
doesn't feature credit delegation yet.
Currently, ghooey
relies on injected wallet and the API and events they expose to perform any task that requires a wallet signature.
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 :
viem
instead of ethers
v5, which would also require to stop using aave-utilities
;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)
})
}
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.