React#

AaveKit React is a collection of React hooks for building decentralized applications on top of the Aave Protocol. It provides a simple and type-safe way to interact with Aave markets, manage user positions, and execute transactions.

Getting Started#

To get started, follow the steps below.

1

Install SDK#

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

npm install @aave/react@latest

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.

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

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#

To send transactions through the connected wallet, use the useSendTransaction action hook.

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

This hook abstracts the wallet interaction and provides a simple interface for submitting transactions.

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.

Then, use it as describe in the specific page.

Sign ERC-20 Permits#

Aave makes use of EIP-2612 to allow users to avoid ERC-20 approval before sending transactions.

Use the useERC20Permit hook to sign ERC-20 permits.

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

Viem
import { useERC20Permit } from "@aave/react/viem";import { useWalletClient } from "wagmi";
function MyComponent() {  const { data: walletClient } = useWalletClient();  const [signPermit, sending] = useERC20Permit(walletClient);
  // Use with transaction hooks…}
The example uses wagmi to get the WalletClient from the connected wallet.

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

Read Hooks#

All read hooks (market data, user positions, etc.) support two ways of operating: loading state and React Suspense.

Loading State#

Handle loading state manually in your component:

Loading States
function ChainsList() {  const { data, loading, error } = useAaveChains();
  if (loading) return <div>Loading…</div>;
  if (error) return <div>Error: {error.message}</div>;
  return (    <div>      {data.map((chain) => (        <div key={chain.id}>{chain.name}</div>      ))}    </div>  );}

React Suspense#

Let React handle loading states automatically through a Suspense boundary.

With Suspense
// Component - no loading states neededfunction ChainsList() {  const { data: chains } = useAaveChains({    suspense: true, // Enable suspense mode  });
  return (    <div>      {chains.map((chain) => (        <div key={chain.id}>{chain.name}</div>      ))}    </div>  );}
// Parent - wrap with Suspense boundaryfunction ChainsPage() {  return (    <Suspense fallback={<div>Loading …</div>}>      <ChainsList />    </Suspense>  );}

You can handle errors in the parent component by wrapping the component with a Error boundary component.

With Error Boundary
import { ErrorBoundary } from "react-error-boundary";
// …
function ChainsPage() {  return (    <ErrorBoundary fallback={<p>⚠️Something went wrong</p>}>      <Suspense fallback={<div>Loading …</div>}>        <ChainsList />      </Suspense>    </ErrorBoundary>  );}

Action Hooks#

AaveKit React provides action hooks that are designed to be triggered manually when you need to perform an operation. Action hooks return an execute function and a state object.

const [execute, state] = useActionHook();

Execute Function#

The execute function returns a ResultAsync<T, E>, a thenable object 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

Awaiting a ResultAsync<T, E> resolves to a Result<T, E>.

With a Result<T, E>, you can use the convenient isOk() and isErr() methods to check the outcome and narrow the type.
If you have a ResultAsync<T, E>, you must first await it to access those methods.

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

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

See the NeverThrow documentation for more information. The NeverThrow library is re-exported for convenience.

State Object#

The state object returned by action hooks provides information about the current operation status:

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

Where:

  • called: true when the operation has been executed at least once, false otherwise

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

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

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

Transaction Hooks#

Transaction hooks are a specific type of action hook 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

Simple Transactions#

To handle a simple transaction hook, follow the steps below.

1

Use Hooks#

First, instantiate the transaction hook and the useSendTransaction hook according to the wallet provider you are using.

Instantiate Hooks
const [prepare, preparing] = useSimpleTransaction();const [sendTransaction, sending] = useSendTransaction(/* … */);

2

Execute the Transaction#

Next, execute the transaction in your app by chaining the prepare and send operations.

const result = await prepare(/* args */).andThen(sendTransaction);

Optionally, combine the state objects to drive your UI components.

const loading = preparing.loading && sending.loading;const error = preparing.error || sending.error;

3

Handle the Result#

Finally, handle the result of the operation.

if (result.isErr()) {  switch (result.error.name) {    case "SigningError":      // Most likely the user rejected the transaction      console.error(`Failed to sign the transaction: ${error.message}`);      break;
    case "TimeoutError":      console.error(`Transaction timed out: ${error.message}`);      break;
    case "TransactionError":      console.error(`Transaction failed: ${error.message}`);      break;
    case "UnexpectedError":      console.error(error.message);      break;  }} else {  console.log("Transaction sent with hash:", result.value);}

Complex Transactions#

To handle a complex transaction hook, follow the steps below.

1

Use Hooks#

First, instantiate the transaction hook and the useSendTransaction hook according to the wallet provider you are using.

Instantiate Hooks
const [prepare, preparing] = useComplexTransaction();const [sendTransaction, sending] = useSendTransaction(/* … */);

Optionally, combine the state objects to drive your UI components.

const loading = preparing.loading && sending.loading;const error = preparing.error || sending.error;

2

Process the Execution Plan#

Next, handle the execution plan in your transaction flow. Some operations may require token approval before executing the main transaction.

import { errAsync } from "@aave/react";
const result = await prepare(/* args */).andThen((plan) => {  switch (plan.__typename) {    case "TransactionRequest":      return sendTransaction(plan);
    case "ApprovalRequired":      return sendTransaction(plan.approval).andThen(() =>        sendTransaction(plan.originalTransaction)      );
    case "InsufficientBalanceError":      return errAsync(        new Error(`Insufficient balance: ${plan.required.value} required.`)      );  }});
// …
If you wish to ask the user for confirmation before sending the transaction, you can do so by integrating your app's confirmation UI at this point.

3

Handle the Result#

Finally, handle the result of the operation.

if (result.isErr()) {  switch (result.error.name) {    case "SigningError":      // Most likely the user rejected the transaction      console.error(`Failed to sign the transaction: ${error.message}`);      break;
    case "TimeoutError":      console.error(`Transaction timed out: ${error.message}`);      break;
    case "TransactionError":      console.error(`Transaction failed: ${error.message}`);      break;
    case "UnexpectedError":      console.error(error.message);      break;  }} else {  console.log("Transaction sent with hash:", result.value);}

Next Steps#

  • Aave Markets - Learn how to interact with Aave markets.

  • Aave Earn - Learn how to interact with Aave Earn Vaults.