Skip to Content
docs

Aave V4 Incentive Programs

Aave V4 reserves may offer additional incentives beyond base lending rates. These incentives are distributed through Merkl, a decentralized incentive distribution platform, or through Points programs that reward users with points multipliers.

Reward Types

Potential rewards are available in the rewards array of the Reserve Summary.

Reserve
interface Reserve {
  // …
  summary: {
    rewards: Reward[];
    // …
  };
}

type Reward =
  | MerklSupplyReward
  | MerklBorrowReward
  | SupplyPointsReward
  | BorrowPointsReward;

Supply Rewards

Supply rewards can be present on suppliable reserves.

Merkl Supply Rewards

Merkl supply rewards represent an extra APY on top of the base reserve supply APY. The accrued extra interest is paid in the specified payout token when the incentive campaign reaches its maturity.

MerklSupplyReward
interface MerklSupplyReward {
  __typename: "MerklSupplyReward";
  id: string;
  startDate: Date;
  endDate: Date;
  extraApy: PercentNumber;
  payoutToken: Erc20Token;
  criteria: MerklCriteria[];
  userEligible: boolean;
}

Where:

  • extraApy: the additional APY earned on top of the base supply APY
  • payoutToken: the token used to pay the extra interest accrued from the reward
  • criteria: eligibility requirements for earning the reward

Points Supply Rewards

Some reserves participate in Points programs. Users who supply into these reserves accrue points over time proportional to their position size.

SupplyPointsReward
interface SupplyPointsReward {
  __typename: "SupplyPointsReward";
  id: string;
  program: PointsProgram;
  name: string;
  startDate: Date;
  endDate: Date | null;
  multiplier: number;
  criteria: PointsCriteria[];
  userEligible: boolean;
}

Where:

Borrow Rewards

Borrow rewards can be present on borrowable reserves.

Merkl Borrow Rewards

Merkl borrow rewards represent an APY discount on the user’s borrow rate (which includes their Risk Premium). The accrued interest discount is paid in the specified payout token when the incentive campaign reaches its maturity.

MerklBorrowReward
interface MerklBorrowReward {
  __typename: "MerklBorrowReward";
  id: string;
  startDate: Date;
  endDate: Date;
  discountApy: PercentNumber;
  payoutToken: Erc20Token;
  criteria: MerklCriteria[];
  userEligible: boolean;
}

Where:

  • discountApy: the APY discount applied to the user’s borrow rate
  • payoutToken: the token used to pay the reward
  • criteria: eligibility requirements for earning the reward

Points Borrow Rewards

Similarly, borrowing from certain reserves can accrue points in a Points program.

BorrowPointsReward
interface BorrowPointsReward {
  __typename: "BorrowPointsReward";
  id: string;
  program: PointsProgram;
  name: string;
  startDate: Date;
  endDate: Date | null;
  multiplier: number;
  criteria: PointsCriteria[];
  userEligible: boolean;
}

Where:

Points Program

A PointsProgram represents a loyalty or incentive system. Users accumulate points over time based on their supply or borrow position. The externalUrl links to the program’s website where users can view their accumulated points.

PointsProgram
interface PointsProgram {
  __typename: "PointsProgram";
  id: string;
  name: string;
  externalUrl: string | null;
  iconUrl: string | null;
}

Eligibility Criteria

Each reward may have eligibility criteria that users must meet. Both Merkl and Points criteria share the same shape (id, text, userPassed) but use distinct GraphQL types.

MerklCriteria
// Merkl rewards use MerklCriteria
interface MerklCriteria {
  __typename: "MerklGenericCriteria";
  id: string;
  text: string;
  userPassed: boolean;
}

// Points rewards use PointsCriteria
interface PointsCriteria {
  __typename: "PointsGenericCriteria";
  id: string;
  text: string;
  userPassed: boolean;
}

Matured Rewards

Rewards become claimable when their incentive campaign reaches maturity. Campaigns are often renewed upon reaching their end date, so users should check for new reward opportunities periodically.

Claimable Rewards

Fetch the user’s matured rewards that are ready to claim.

Use the useUserClaimableRewards hook (or the imperative useUserClaimableRewardsAction variant) in React, the userClaimableRewards action in TypeScript, or the userClaimableRewards query in GraphQL to fetch all rewards the user can claim.

import {
  chainId,
  useUserClaimableRewards,
  type EvmAddress,
} from "@aave/react";

function ClaimableRewards({ user }: { user: EvmAddress }) {
  const { data, loading, error } = useUserClaimableRewards({
    user,
    chainIds: [chainId(1)],
  });

  if (loading) return <div>Loading…</div>;

  if (error) return <div>Error: {error.message}</div>;

  if (data.length === 0) return <div>No claimable rewards</div>;

  return (
    <div>
      {data.map((reward) => (
        <div key={reward.id}>
          <p>Amount: {reward.claimable.amount.value.toFixed(2)}</p>
          <p>Claim until: {reward.claimUntil.toLocaleDateString()}</p>
        </div>
      ))}
    </div>
  );
}

useUserClaimableRewardsAction doesn’t watch for updates. Use it for on-demand fresh data, such as in an event handler.

An empty array means the user has no claimable rewards.

The UserMerklClaimableReward type contains details about each claimable reward:

UserMerklClaimableReward
interface UserMerklClaimableReward {
  __typename: "UserMerklClaimableReward";
  id: RewardId;
  claimChainId: ChainId;
  claimable: Erc20Amount;
  startDate: Date;
  endDate: Date;
  claimUntil: Date;
}

Where:

  • id: unique identifier for the reward (used when claiming)
  • claimChainId: the chain on which the reward is claimed
  • claimable: the claimable token amount
  • startDate: when the reward period started
  • endDate: when the reward period ended
  • claimUntil: deadline to claim the reward

Claim Rewards

Once you have claimable rewards, collect them individually or all at once in a single transaction.

Claimed rewards can stay in the claimable list until Merkl reports them as claimed, which can take a while.

To claim rewards with AaveKit, follow these steps.

1. Configure Wallet Integration

In React, first instantiate the useSendTransaction hook for the wallet library of your choice.

Viem
import { useWalletClient } from "wagmi";
import { useSendTransaction } from "@aave/react/viem";

// …

const { data: wallet } = useWalletClient();
const [sendTransaction] = useSendTransaction(wallet);

2. Define the Claim Flow

Then, in React, use the useClaimRewards hook to prepare the claim operation.

import { useClaimRewards } from "@aave/react";

const [claim, { loading, error }] = useClaimRewards((transaction) =>
  sendTransaction(transaction),
);

3. Execute the Claim Operation

Then, execute the claim operation with the reward IDs from the claimable rewards.

  • TypeScript: Use the claimRewards action.
  • GraphQL: Use the claimRewards query.
Claim Rewards
import {
  chainId,
  useUserClaimableRewards,
  evmAddress,
} from "@aave/react";

const { data: claimableRewards } = useUserClaimableRewards({
  user: evmAddress(wallet.account.address),
  chainIds: [chainId(1)],
  suspense: true,
});

const execute = async () => {
  if (claimableRewards.length > 0) {
    const result = await claim({
      ids: claimableRewards.map((reward) => reward.id),
      user: evmAddress(wallet.account.address),
      chainId: chainId(1),
    });

    // …
  }
};

4. Process the Transaction Request

In TypeScript, use sendWith for the wallet library of your choice to send the transaction, and await indexing with client.waitForTransaction.

import { userClaimableRewards, claimRewards } from "@aave/client/actions";
import { sendWith } from "@aave/client/viem";
import { chainId, evmAddress } from "@aave/client";

import { client } from "./client";
import { wallet } from "./wallet";

const result = await userClaimableRewards(client, {
  user: evmAddress(wallet.account.address),
  chainIds: [chainId(1)],
})
  .andThen((rewards) =>
    claimRewards(client, {
      ids: rewards.map((reward) => reward.id),
      user: evmAddress(wallet.account.address),
      chainId: chainId(1),
    }),
  )
  .andThen(sendWith(wallet))
  .andThen(client.waitForTransaction);

In GraphQL, send the returned transaction using the wallet associated with the from address.

Response
{
  "data": {
    "claimRewards": {
      "to": "0x1234567890abcdef1234567890abcdef12345678",
      "from": "0x742d35cc6e5c4ce3b69a2a8c7c8e5f7e9a0b1234",
      "data": "0x…",
      "value": "0",
      "chainId": 1
    }
  }
}

5. Handle the Result

Finally, in React and TypeScript, handle the result.

Example
const execute = async () => {
  const result = await claim(/* … */);

  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("Rewards claimed successfully with hash:", result.value.txHash);
};