Skip to content

Commit

Permalink
Feat: Aave integration (#133)
Browse files Browse the repository at this point in the history
* feat: initial integration structure

* feat(ui): add supplied and borrowed assets lists

* feat(ui): initial components

* feat(abi): update to v3 abi

* feat(ui): improve ui

* fix: atoken and debttoken balances

* feat(aave-integration): add apy data

* feat(aave-integration): add modals

* feat(aave-integration): health factor calculation

* feat(aave-integration): apy calculation

* feat(aave-integration): add contract writes

* refactor(aave-integration): improve useAave hook code

* feat(aave-integration): responsive lists

* feat(aave-integration): repay with aTokens

* feat(aave-integration): switch borrow rate

* docs(aave-integration): Create README.md

* docs(aave-integration): Update README.md

* fix(aave-integration): build failing because of isolated module

* fix(aave-integration): build failing because of isolated module

* fix: format errors

* fix: config networks

* chore: hide supply/borrow select on bigger screens

* chore: hide supply/borrow select on bigger screens

* feat: handle unsupported chains

* feat: add checkbox to hide assets with 0 balance

* chore(ui): align cards on left side of the dashboard

* refactor: use single state as an object in useAave

* fix: useAave broken

* feat: use ContractWriteButton for write operations

* feat: add max button to inputs

* fix: minor UI improvements

* feat(ui): hide ~0 balances on user supplies and borrows

* fix: margin cards UI con smaller screens

* refactor: remove useAave unused code

* fix: call revert on stable rate borrow

* feat: toast after supply/borrow/withdraw/repay success

* fix: negative big int if decimals < 5

* fix: remove console logs

* fix: format errors

* fix: add removed files

* fix: add removed files

* fix: format errors

* fix: minor UI improvements

* fix: small layout updates

* fix: usage as collateral always seems to be enabled

* fix: usage as collateral always seems to be enabled

---------

Co-authored-by: AlanRacciatti <[email protected]>
Co-authored-by: Chiin <[email protected]>
Co-authored-by: Kames <[email protected]>
  • Loading branch information
4 people authored Aug 23, 2023
1 parent 1b1e190 commit 893bd18
Show file tree
Hide file tree
Showing 48 changed files with 9,940 additions and 3,552 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,4 @@ ETHERSCAN_API_KEY_POLYGON=
OPENAI_API_KEY=

# Moralis API Key: https://admin.moralis.io/settings#secret-keys
MORALIS_API_KEY=
MORALIS_API_KEY=
63 changes: 63 additions & 0 deletions app/(general)/integration/aave/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use client'
import { ReactNode } from 'react'

import { motion } from 'framer-motion'
import Image from 'next/image'
import Balancer from 'react-wrap-balancer'

import { WalletConnect } from '@/components/blockchain/wallet-connect'
import { IsWalletConnected } from '@/components/shared/is-wallet-connected'
import { IsWalletDisconnected } from '@/components/shared/is-wallet-disconnected'
import { LinkComponent } from '@/components/shared/link-component'
import { FADE_DOWN_ANIMATION_VARIANTS } from '@/config/design'
import { turboIntegrations } from '@/data/turbo-integrations'

export default function AaveLayout({ children }: { children: ReactNode }) {
return (
<>
<div className="flex w-full flex-col pb-10 lg:pb-12">
<div className="relative flex flex-1">
<div className="flex-center flex h-full flex-1 flex-col items-center justify-center text-center">
<motion.div
animate="show"
className="w-full max-w-3xl px-5 xl:px-0"
initial="hidden"
viewport={{ once: true }}
whileInView="show"
variants={{
hidden: {},
show: {
transition: {
staggerChildren: 0.15,
},
},
}}>
<Image alt="Aave Icon" className="mx-auto mb-5" height={100} src={turboIntegrations.aave.imgDark} width={100} />
<motion.h1
className="pb-5 text-center text-2xl font-bold tracking-[-0.02em] drop-shadow-sm md:text-8xl md:leading-[6rem]"
variants={FADE_DOWN_ANIMATION_VARIANTS}>
Aave
</motion.h1>
<motion.p className=" mb-8 text-center text-gray-500 dark:text-gray-200 md:text-xl" variants={FADE_DOWN_ANIMATION_VARIANTS}>
<Balancer className="w-full text-xl font-semibold">Borrow and lend assets seamlessly</Balancer>
</motion.p>
<motion.div className="my-4 text-xl" variants={FADE_DOWN_ANIMATION_VARIANTS}>
<LinkComponent isExternal href={turboIntegrations.aave.url}>
<button className="btn btn-primary">Documentation</button>
</LinkComponent>
</motion.div>
</motion.div>
</div>
</div>
</div>
<main className="w-full">
<IsWalletDisconnected>
<div className="mx-auto mt-10 w-fit">
<WalletConnect />
</div>
</IsWalletDisconnected>
<IsWalletConnected>{children}</IsWalletConnected>
</main>
</>
)
}
9 changes: 9 additions & 0 deletions app/(general)/integration/aave/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { IntegrationOgImage } from '@/components/ui/social/og-image-integrations'

export const runtime = 'edge'
export const size = {
width: 1200,
height: 630,
}

export default IntegrationOgImage('aave')
64 changes: 64 additions & 0 deletions app/(general)/integration/aave/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use client'

import { useState } from 'react'

import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { GeneralInfo } from '@/integrations/aave/components/general-info'
import { ListAssetsToBorrow } from '@/integrations/aave/components/list-assets-to-borrow'
import { ListAssetsToSupply } from '@/integrations/aave/components/list-assets-to-supply'
import { ListBorrowedAssets } from '@/integrations/aave/components/list-borrowed-assets'
import { ListSuppliedAssets } from '@/integrations/aave/components/list-supplied-assets'
import { useAave } from '@/integrations/aave/hooks/use-aave'

export default function AaveHome() {
const [actionSelected, setActionSelected] = useState('supply')
const { chainSupported } = useAave()

return chainSupported ? (
<section className="w-full lg:mt-10">
<div className="mx-auto max-w-screen-xl">
<GeneralInfo />
<div className="m-2 mb-5 w-40 xl:hidden">
<Select value={actionSelected} onValueChange={(action) => setActionSelected(action)}>
<SelectTrigger className="input mt-2 bg-white text-gray-600 placeholder:text-neutral-400 dark:bg-gray-700 dark:text-slate-300 dark:placeholder:text-neutral-400">
<SelectValue placeholder="Select market" />
</SelectTrigger>
<SelectContent className="w-56 bg-white dark:bg-gray-700">
<SelectItem value="supply">
<div className="flex items-center justify-between">Supply</div>
</SelectItem>
<SelectItem value="borrow">
<div className="flex items-center justify-between">Borrow</div>
</SelectItem>
</SelectContent>
</Select>
</div>

<div className="mb-4 flex justify-between dark:text-white">
<div className={`${actionSelected === 'supply' ? '' : 'hidden'} m-2 w-full xl:block`}>
<ListSuppliedAssets />
</div>
<div className={`${actionSelected === 'borrow' ? '' : 'hidden'} m-2 w-full xl:block`}>
<ListBorrowedAssets />
</div>
</div>

<div className="flex justify-between dark:text-white ">
<div className={`${actionSelected === 'supply' ? '' : 'hidden'} m-2 w-full xl:block`}>
<ListAssetsToSupply />
</div>
<div className={`${actionSelected === 'borrow' ? '' : 'hidden'} m-2 w-full xl:block`}>
<ListAssetsToBorrow />
</div>
</div>
</div>
</section>
) : (
<section className="w-full lg:mt-10">
<div className="mx-auto max-w-screen-xl">
<h1 className=" mt-5 text-center text-2xl">Chain not supported</h1>
<GeneralInfo />
</div>
</section>
)
}
9 changes: 9 additions & 0 deletions app/(general)/integration/aave/twitter-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Image from './opengraph-image'

export const runtime = 'edge'
export const size = {
width: 1200,
height: 630,
}

export default Image
17 changes: 16 additions & 1 deletion app/(general)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export default function Home() {
</motion.div>
<CopyToClipboard text="pnpm create turbo-eth@latest" onCopy={() => setCopied(true)}>
<motion.div
className="group mx-auto mt-8 flex max-w-fit cursor-pointer items-center justify-between gap-x-2 rounded-xl border border-gray-200 bg-white py-4 px-3 text-sm font-medium shadow-md transition-colors dark:border-gray-800 dark:bg-neutral-800 dark:text-white hover:dark:border-gray-600/70 hover:dark:bg-neutral-700/70 md:px-6 md:text-lg"
className="group mx-auto mt-8 flex max-w-fit cursor-pointer items-center justify-between gap-x-2 rounded-xl border border-gray-200 bg-white px-3 py-4 text-sm font-medium shadow-md transition-colors dark:border-gray-800 dark:bg-neutral-800 dark:text-white hover:dark:border-gray-600/70 hover:dark:bg-neutral-700/70 md:px-6 md:text-lg"
variants={FADE_DOWN_ANIMATION_VARIANTS}>
<pre>pnpm create turbo-eth@latest</pre>
<span className="flex-center flex h-4 w-4 cursor-pointer rounded-md text-neutral-600 dark:text-neutral-100 md:h-7 md:w-7">
Expand Down Expand Up @@ -379,6 +379,21 @@ const features = [
</div>
),
},
{
title: turboIntegrations.aave.name,
description: turboIntegrations.aave.description,
href: turboIntegrations.aave.href,
demo: (
<div className="flex items-center justify-center space-x-20">
<IsLightTheme>
<Image alt="Aave logo" height={100} src={turboIntegrations.aave.imgDark} width={100} />
</IsLightTheme>
<IsDarkTheme>
<Image alt="Aave logo" height={100} src={turboIntegrations.aave.imgLight} width={100} />
</IsDarkTheme>
</div>
),
},
{
title: turboIntegrations.starter.name,
description: turboIntegrations.starter.description,
Expand Down
2 changes: 1 addition & 1 deletion components/ui/accordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const AccordionContent = forwardRef<ElementRef<typeof AccordionPrimitive.Content
className
)}
{...props}>
<div className="pt-0 pb-4">{children}</div>
<div className="pb-4 pt-0">{children}</div>
</AccordionPrimitive.Content>
)
)
Expand Down
14 changes: 7 additions & 7 deletions components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@ import { VariantProps, cva } from 'class-variance-authority'
import { cn } from '@/lib/utils'

const buttonVariants = cva(
'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 dark:hover:bg-slate-800 dark:hover:text-slate-100 disabled:opacity-50 dark:focus:ring-slate-400 disabled:pointer-events-none dark:focus:ring-offset-slate-900 data-[state=open]:bg-slate-100 dark:data-[state=open]:bg-slate-800',
'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=open]:bg-slate-100 dark:hover:bg-slate-800 dark:hover:text-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800',
{
variants: {
variant: {
default: 'bg-slate-900 text-white hover:bg-slate-700 dark:bg-slate-50 dark:text-slate-900',
outline: 'bg-transparent border border-slate-200 hover:bg-slate-100 dark:border-slate-700 dark:text-slate-100',
outline: 'border border-slate-200 bg-transparent hover:bg-slate-100 dark:border-slate-700 dark:text-slate-100',
subtle: 'bg-slate-100 text-slate-900 hover:bg-slate-200 dark:bg-slate-700 dark:text-slate-100',
ghost:
'bg-transparent hover:bg-slate-100 dark:hover:bg-slate-800 dark:text-slate-100 dark:hover:text-slate-100 data-[state=open]:bg-transparent dark:data-[state=open]:bg-transparent',
link: 'bg-transparent underline-offset-4 hover:underline text-slate-900 dark:text-slate-100 hover:bg-transparent dark:hover:bg-transparent',
'bg-transparent hover:bg-slate-100 data-[state=open]:bg-transparent dark:text-slate-100 dark:hover:bg-slate-800 dark:hover:text-slate-100 dark:data-[state=open]:bg-transparent',
link: 'bg-transparent text-slate-900 underline-offset-4 hover:bg-transparent hover:underline dark:text-slate-100 dark:hover:bg-transparent',
},
size: {
default: 'h-10 py-2 px-4',
sm: 'h-9 px-2 rounded-md',
lg: 'h-11 px-8 rounded-md',
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-2',
lg: 'h-11 rounded-md px-8',
},
},
defaultVariants: {
Expand Down
4 changes: 2 additions & 2 deletions components/ui/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const DialogContent = forwardRef<ElementRef<typeof DialogPrimitive.Content>, Com
)}
{...props}>
{children}
<DialogPrimitive.Close className="absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800">
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800">
<LuX className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
Expand Down Expand Up @@ -74,4 +74,4 @@ const DialogDescription = forwardRef<ElementRef<typeof DialogPrimitive.Descripti
)
DialogDescription.displayName = DialogPrimitive.Description.displayName

export { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription }
export { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription, DialogPortal }
2 changes: 1 addition & 1 deletion components/ui/navigation-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
const NavigationMenuItem = NavigationMenuPrimitive.Item

const navigationMenuTriggerStyle = cva(
'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:bg-slate-100 disabled:opacity-50 dark:focus:bg-slate-800 disabled:pointer-events-none bg-transparent hover:bg-slate-100 dark:hover:bg-slate-800 dark:text-slate-100 dark:hover:text-slate-100 data-[state=open]:bg-slate-50 dark:data-[state=open]:bg-slate-800 h-10 py-2 px-4 group'
'group inline-flex h-10 items-center justify-center rounded-md bg-transparent px-4 py-2 text-sm font-medium transition-colors hover:bg-slate-100 focus:bg-slate-100 focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=open]:bg-slate-50 dark:text-slate-100 dark:hover:bg-slate-800 dark:hover:text-slate-100 dark:focus:bg-slate-800 dark:data-[state=open]:bg-slate-800'
)

const NavigationMenuTrigger = forwardRef<
Expand Down
6 changes: 3 additions & 3 deletions components/ui/toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ const ToastViewport = forwardRef<ElementRef<typeof ToastPrimitives.Viewport>, Co
ToastViewport.displayName = ToastPrimitives.Viewport.displayName

const toastVariants = cva(
'data-[swipe=move]:transition-none grow-1 group relative pointer-events-auto flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full mt-4 data-[state=closed]:slide-out-to-right-full dark:border-slate-700 last:mt-0 sm:last:mt-4',
'grow-1 data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full data-[state=closed]:slide-out-to-right-full group pointer-events-auto relative mt-4 flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all last:mt-0 data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none dark:border-slate-700 sm:last:mt-4',
{
variants: {
variant: {
default: 'bg-white border-slate-200 dark:bg-slate-800 dark:border-slate-700',
destructive: 'group destructive bg-red-600 text-white border-red-600 dark:border-red-600',
default: 'border-slate-200 bg-white dark:border-slate-700 dark:bg-slate-800',
destructive: 'destructive group border-red-600 bg-red-600 text-white dark:border-red-600',
},
},
defaultVariants: {
Expand Down
8 changes: 8 additions & 0 deletions data/turbo-integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,14 @@ export const turboIntegrations = {
imgLight: '/integrations/moralis.png',
imgDark: '/integrations/moralis.png',
},
aave: {
name: 'Aave',
href: '/integration/aave',
url: 'https://docs.aave.com/hub/',
description: 'Aave is a decentralized non-custodial liquidity protocol.',
imgLight: '/integrations/aave.png',
imgDark: '/integrations/aave.png',
},
starter: {
name: 'Starter Template',
href: '/integration/starter',
Expand Down
89 changes: 89 additions & 0 deletions integrations/aave/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Aave - TurboETH Integration

This React Hook integrates with [Aave](https://aave.com/), enabling users to supply and borrow assets. The integration is built using [Wagmi CLI](https://wagmi.sh/cli/commands/generate), interacting with the Aave [UiPoolDataProvider](https://docs.aave.com/developers/periphery-contracts/uipooldataproviderv3) and [Pool](https://docs.aave.com/developers/core-contracts/pool) contracts.

## Features

- Get pools and users data on Aave with the `useAave` hook
- Interact with the pool contract using Wagmi's generated hooks

## Hooks

### `useAave()`

The `useAave()` hook retrieves and computes various data related to the user's activities on the Aave lending protocol.

**Parameters:**

The hook doesn't take any parameters.

**Returns:**

The hook returns an object `data` with the following properties:

- `reservesData`: An array of data on the reserves fetched from the Aave protocol, with information about each asset in the pool, such as total liquidity, total borrowings, liquidity rate, variable borrow rate, and more.

- `userReservesData`: An array of data about the user's reserves in the Aave protocol. This data includes information about each asset the user has in the pool.

- `usdData`: An array derived from `userReservesData`, where each element is enriched with additional data like `reserveData`, `tokenPriceInUsd`, `amountInUsd`, and `debtInUsd`.

- `balanceInUsd`: The user's total balance in USD, as calculated based on the user's reserves data.

- `totalDebtInUsd`: The user's total debt in USD, as calculated based on the user's reserves data.

- `collateralInUsd`: The total collateral in USD, as calculated based on the user's reserves data.

- `maxBorrowableInUsd`: The maximum amount the user can borrow in USD, calculated based on the user's reserves data.

- `healthFactor`: A metric calculated based on the user's collateral and debt that indicates the health of the user's position in the protocol.

- `averageSupplyApy`: The average annual percentage yield (APY) for supplying assets, calculated based on the user's reserves data.

- `averageBorrowApy`: The average APY for borrowing assets, calculated based on the user's reserves data.

- `averageNetApy`: The average net APY, calculated based on the user's reserves data.

- `poolAddress`: The address of the Aave lending pool.

**Behavior:**

- On initial render, and whenever there's a change in the user's reserves data, reserves data, market, or user, the hook calculates and updates multiple financial parameters.

**Example:**

```javascript
const { data } = useAave()
```

Also check the hooks generated at `integrations/aave/generated/aave-wagmi.ts`

## File Structure

```
integrations/aave
├── abis
│ ├── pool-abi.ts
│ └── ui-pool-data-provider-abi.ts
├── components
│ ├── asset-to-borrow-item.tsx
│ ├── asset-to-supply-item.tsx
│ ├── borrowed-assets-item.tsx
│ ├── general-info.tsx
│ ├── health-factor.tsx
│ ├── list-assets-to-borrow.tsx
│ ├── list-assets-to-supply.tsx
│ ├── list-borrowed-assets.tsx
│ ├── list-supplied-assets.tsx
│ ├── spinner.tsx
│ └── supplied-assets-item.tsx
├── generated
│ └── aave-wagmi.ts
├── hooks
│ └── use-aave.ts
├── utils
│ ├── index.ts
│ ├── market-config.ts
│ └── types.ts
│── wagmi.config.ts
└── README.md
```
Loading

1 comment on commit 893bd18

@vercel
Copy link

@vercel vercel bot commented on 893bd18 Aug 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.