React

Get started with AaveKit React


AaveKit React for Aave v4 is a collection of React hooks for building decentralized applications on top of the Aave Protocol v4.

Getting Started

To get started, follow the steps below.

1

Install Packages

First, install the latest AaveKit React packages using your package manager of choice.

npm install @aave/react@next

2

Setup Client

Then, create an AaveClient instance that will be used to interact with the protocol.

client.ts
import { AaveClient } from "@aave/react";
export const client = AaveClient.create();

You don't need to install the @aave/client package as it's already included and re-exported by the @aave/react package.

3

Setup Provider

Next, wrap your app with the <AaveProvider> component and pass the client instance.

App.tsx
import { AaveProvider } from "@aave/react";
import { client } from "./client";
export function App() {  return (    <AaveProvider client={client}>      {/* Your application components */}    </AaveProvider>  );}

4

Start Building

That's it—you can now start using AaveKit React. Here's a basic example to fetch supported chains:

Example
import { useChains } from "@aave/react";
export function SupportedChains() {  const { data: chains } = useChains();
  return (    <div>      <h2>Supported Chains</h2>      {chains?.map((chain) => (        <div key={chain.chainId}>          <h3>{chain.name}</h3>          <p>Chain ID: {chain.chainId}</p>        </div>      ))}    </div>  );}

Declarative Hooks

AaveKit React declarative hooks execute on render with the given inputs, refresh when inputs change, and update after successful write operations; declarative hooks support two modes: loading state and React Suspense.

Handle loading state manually in your component:

Loading State
import { useChains } from "@aave/react";
function ChainsList() {  const { data, loading, error } = useChains();
  if (loading) return <div>Loading…</div>;
  if (error) return <div>Error: {error.message}</div>;
  return (    <div>      {data.map((chain) => (        <div key={chain.chainId}>{chain.name}</div>      ))}    </div>  );}

Pausable Reads

AaveKit React read hooks support pausing—a powerful pattern that lets you defer data fetching until certain conditions are met. This is particularly useful when:

  • Waiting for user input (e.g., wallet connection, user address)

  • Conditional data loading (e.g., only fetch when a user is authenticated)

  • Dependent data (e.g., fetch user data only after chain selection)

Type safety is maintained by allowing nullish request parameters and returning a paused state, which explicitly signals when a hook is inactive and lets TypeScript precisely narrow types between paused and active states.

import { useChains } from "@aave/react";
const { data, loading, error, paused } = useChains({  pause: !connected,});
// …
// data: Chain[] | undefined// loading: boolean// error: UnexpectedError | undefined// paused: boolean
if (paused) {  // data: undefined  // error: undefined  // loading: boolean  return null;}
if (loading) {  // data: undefined  // error: undefined  // loading: true  return null;}
if (error) {  // data: undefined  // error: UnexpectedError  // loading: false  return null;}
// data: Chain[] (guaranteed to be defined)

Reloading State

Declarative hooks also expose a reloading state that indicates when data is being refreshed after the initial load. This is different from loading, which is only true during the first fetch.

The reloading state becomes true when:

  • Variables change and the hook refetches with new parameters

  • Polling triggers a background refresh (if the hook is designed to poll)

This lets you show a subtle refresh indicator while keeping the previous data visible, rather than replacing content with a loading spinner.

import { useUserPositions } from "@aave/react";
const { data, loading, error, reloading } = useUserPositions({  user: userAddress,});
if (loading) {  return <Spinner />;}
if (error) {  return <ErrorMessage error={error} />;}
// Show refresh indicator while keeping data visiblereturn (  <div>    {reloading && <RefreshIndicator />}    <PositionsList positions={data} />  </div>);

Imperative Hooks

AaveKit React imperative hooks return an execute function and a state object. These are tailored for when you need explicit control—like event handlers or multi‑step flows.

const [execute, state] = useImperativeHook();

Read hooks that follow this pattern do not watch for updates. Prefer the imperative variant over the declarative one when you need on-demand, fresh data (e.g., in an event handler).

Execute Function

The execute function takes the hook arguments and, when awaited, yields a Result<T, E>.

Use isOk() and isErr() to check the outcome and narrow the type:

const result = await execute();
if (result.isOk()) {  console.log("Result:", result.value);} else {  console.error("Error:", result.error);}

See the Result Objects section for more information about the Result object.

State Object

The state object provides information about the operation status:

const { called, data, error, loading } = state;

Where:

  • called: true once the hook has run at least once.

  • data: last successful result of type T, otherwise undefined.

  • error: error of type E if the operation failed, otherwise undefined.

  • loading: true while the operation is in progress, otherwise false.

Integrations

AaveKit React includes first-class support for viem, ethers v6, Privy, Dynamic, thirdweb and Turnkey.

Ensure you have viem package installed in your project.

npm install viem@2

Send Aave Transactions

AaveKit React provides transaction hooks that handle protocol interactions like supply, borrow, withdraw, repay, and more.

There are two types of transaction hooks:

  • Simple transactions - single transactions that can be sent directly to the wallet

  • Complex transactions - transactions that could require approval beforehand

To send transactions, follow the steps below.

1

useSendTransaction

First, use the provider‑specific useSendTransaction hook to send transactions with the connected wallet.

const [sendTransaction, { loading, error }] = useSendTransaction(/* args */);

It abstracts wallet differences and provides a consistent interface that interoperates with the other AaveKit React hooks.

Import the useSendTransaction hook from the @aave/react/viem entry point and wire it up with the viem's WalletClient.

Viem
import { useSendTransaction } from "@aave/react/viem";import { useWalletClient } from "wagmi";
function MyComponent() {  const { data: walletClient } = useWalletClient();  const [sendTransaction, sending] = useSendTransaction(walletClient);
  // Use with transaction hooks…}

The example uses wagmi to get the WalletClient from the connected wallet.

2

Transaction Hook

Then, instantiate the specific transaction hook for the operation you want to perform.

Call sendTransaction (from useSendTransaction) inside the simple‑transaction hook callback, as shown below.

Simple Transactions
const [sendTransaction, sending] = useSendTransaction(/* … */);const [prepare, preparing] = useSimpleTransaction((transaction) =>  sendTransaction(transaction),);

You can bail out of the operation at any point by calling the cancel(message) function.

Bail Out
const [execute, { loading, error }] = useSimpleTransaction(  (transaction, { cancel }) => {    if (window.confirm("Are you sure you want to continue?") === false) {      return cancel("User cancelled the operation");    }    return sendTransaction(transaction);  },);

3

Execute the Hook

Finally, execute the hook.

In your callback, call the execute function with the operation arguments and handle the result accordingly.

Simple Transactions
const result = await execute(/* args */);
if (result.isErr()) {  switch (result.error.name) {    case "CancelError":      // The user cancelled the operation      return;
    case "SigningError":      console.error(`Failed to sign the transaction: ${result.error.message}`);      break;
    case "TimeoutError":      console.error(`Transaction timed out: ${result.error.message}`);      break;
    case "TransactionError":      console.error(`Transaction failed: ${result.error.message}`);      break;
    case "UnexpectedError":      console.error(result.error.message);      break;  }  return;}
console.log(result.value); // TxHash

You can also take a declarative approach by using the loading and error state objects to drive your UI.

function MyComponent() {  // …
  return (    <div>      <button disabled={loading}>        {loading ? "Sending…" : "Send Transaction"}      </button>
      {error && <p style={{ color: "red" }}>{error.message}</p>}    </div>  );}

That's it—you now know the standard approach for handling Aave transactions with AaveKit React

Sign Typed Data

Some Aave operations require EIP-712 typed data signatures for the following:

  • ERC-20 Permits: Authorize exact amount transfers without separate approval transactions

  • Swap Intents: Sign swap orders for intent-based swaps

Permits are available for ERC-20 tokens that implement EIP-2612.

As with useSendTransaction, instantiate the provider-specific useSignTypedData hook to sign typed data.

Import the useSignTypedData hook from the @aave/react/viem entry point and wire it up with the viem's WalletClient.

Viem
import { useSignTypedData } from "@aave/react/viem";import { useWalletClient } from "wagmi";
function MyComponent() {  const { data: walletClient } = useWalletClient();  const [signTypedData, sending] = useSignTypedData(walletClient);
  // Use with transaction hooks…}

The example uses wagmi to get the WalletClient from the connected wallet.

Then, use the signTypedData function as described in the specific operation guide.


Appendix

Result Objects

AaveKit uses a functional approach to error handling, it's based on Result<T, E> that represents one of two states:

  • Ok<T>: A successful result containing a value of type T

  • Err<E>: A failure containing an error of type E

Example
import { ok, err, Result } from "@aave/react";
function parseNumber(input: string): Result<number, string> {  return isNaN(Number(input))    ? err(new Error("Invalid number"))    : ok(Number(input));}

With a Result<T, E>, you can use the convenient isOk() and isErr() methods to check the outcome and narrow the type.

const result = parseNumber("123");
if (result.isOk()) {  console.log(result.value); // 123} else {  console.error(result.error); // Error}

This approach avoids reliance on try/catch blocks and promotes predictable, type-safe code by ensuring errors are handled explicitly.

AaveKit uses the NeverThrow library as underlying implementation for the Result object.

ResultAsync<T, E> is the async, thenable variant of Result<T, E>. Awaiting it resolves to a Result<T, E>.

import { ResultAsync } from "@aave/react";
function fetchUser(id: number): ResultAsync<{ name: string }, Error> {  return ResultAsync.fromPromise(    fetch(`/api/users/${id}`).then((r) => r.json()),    () => new Error("Not found"),  );}
const result = await fetchUser(1);
if (result.isOk()) {  console.log(result.value); // { name: "John" }} else {  console.error(result.error); // Error}