# Aave V4 Liquidations

Liquidate unhealthy positions to earn liquidation bonuses and help maintain protocol stability.

## Identify Target Position

First, identify a position with a health factor below 1.0 that is eligible for liquidation.

```ts title="User Position"
const position: UserPosition = {
  __typename: "UserPosition",
  id: "aGVsbG8",
  user: "0x456…",
  healthFactor: {
    value: BigDecimal(0.85), // Below 1.0 - eligible for liquidation
    // …
  },
  spoke: {
    address: "0x123…",
    chain: {
      chainId: 1,
      name: "Ethereum",
    },
    // …
  },
  // …
};
```

> [!NOTE]
> Finding positions with a health factor below 1.0 is out of scope here. See
> [Health Factor](../positions#health-factor).

## Select Liquidation Targets

A liquidation targets one _debt_ reserve to repay and one _collateral_ reserve to seize, including the liquidation bonus.

> [!NOTE]
> Only liquidate when the liquidation bonus exceeds gas costs.

### 1. Choose the Debt Position

First, identify which debt position to target by looking at the user’s borrow positions. Consider targeting debts with larger amounts.

Fetch borrow positions from [User Borrows](./fetch#fetching-user-borrows), then pick the specific position you want to liquidate:

```ts title="Borrow Position"
const debtPosition: UserBorrowItem = {
  reserve: {
    id: "SGVsbG8h",
    onChainId: "42",
    asset: {
      underlying: { address: "0xa0b8…", info: { symbol: "USDC", name: "USD Coin" } },
    },
    // …
  },
  debt: {
    amount: {
      value: BigDecimal(2000),
    },
    exchange: {
      value: BigDecimal(2000),
      // …
    },
    // …
  },
  // …
};
```

> [!NOTE]
> If less than $1,000 USD of debt would remain in the target reserve, you must
> liquidate all of it.

### 2. Choose the Collateral Position

Next, identify which collateral position to target by examining the user’s supply positions that are enabled as collateral. Only supplies with `isCollateral: true` can be targeted for liquidation.

Choose collateral in the token you prefer to receive as your liquidation bonus by fetching the supply positions from [User Supplies](./fetch#fetching-user-supplies):

```ts title="Supply Position"
const collateral: UserSupplyItem = {
  reserve: {
    id: "V29ybGQ=",
    onChainId: "43",
    asset: {
      underlying: { address: "0xc02a…", info: { symbol: "WETH", name: "Wrapped Ether" } },
    },
    // …
  },
  isCollateral: true, // Can be liquidated
  withdrawable: {
    amount: {
      value: BigDecimal(1.5),
      // …
    },
    // …
  },
  // …
};
```

> [!NOTE]
> In Aave V4, liquidation bonuses follow a Dutch auction where lower health
> factors result in higher liquidation bonuses.

## Liquidate the Position

### Step-by-Step with AaveKit

After selecting the debt and collateral reserves, choose the repayment amount. You can send an exact value or let the protocol determine the needed amount by using max, restoring the position to a healthy state (HF ≥ 1.0).

To liquidate an unhealthy position with AaveKit, follow these steps.

#### 1. Configure Wallet Integration

In React, first instantiate the hooks for the [wallet library of your choice](../getting-started/react#integrations). This wallet will pay the debt tokens and receive the seized collateral.

- `useSendTransaction`: used to send ERC-20 approval and liquidation transactions
- `useSignTypedData`: used to sign ERC-20 permits when available

<tabgroup>
<tab label="React">

```tsx title="Viem"
import { useWalletClient } from "wagmi";
import { useSendTransaction, useSignTypedData } from "@aave/react/viem";

// …

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

</tab>
</tabgroup>
#### 2. Define the Liquidation Flow

Then, in React, use the `useLiquidatePosition` hook to prepare the liquidation operation.

<tabgroup>
<tab label="React">

```tsx title="Basic Usage"
import { useLiquidatePosition } from "@aave/react";

const [liquidatePosition, { loading, error }] = useLiquidatePosition(
  (plan, { cancel }) => {
    switch (plan.__typename) {
      case "TransactionRequest":
        return sendTransaction(plan);
      case "Erc20Approval":
        // If token supports EIP-2612 permits, sign permit (recommended)
        if (plan.bySignature) {
          return signTypedData(plan.bySignature);
        }
        // Otherwise use traditional approval transaction
        return sendTransaction(plan.byTransaction);

      case "PreContractActionRequired":
        return sendTransaction(plan.transaction);
    }
  },
);
```

```tsx title="Update Component State"
import { useLiquidatePosition } from "@aave/react";

const [status, setStatus] = useState("");

const [liquidatePosition, { loading, error }] = useLiquidatePosition(
  (plan, { cancel }) => {
    switch (plan.__typename) {
      case "TransactionRequest":
        setStatus("Please sign the Liquidation transaction in your wallet");
        return sendTransaction(plan).andTee(() => setStatus("Liquidating…"));

      case "Erc20Approval":
        if (plan.bySignature) {
          setStatus("Please sign the Permit in your wallet");
          return signTypedData(plan.bySignature).andTee(() =>
            setStatus("Approving…"),
          );
        }
        setStatus("Please sign the Approval transaction in your wallet");
        return sendTransaction(plan.byTransaction).andTee(() =>
          setStatus("Approving…"),
        );

      case "PreContractActionRequired":
        setStatus(
          "Please sign the Pre-Contract Action transaction in your wallet",
        );
        return sendTransaction(plan.transaction);
    }
  },
);
```

```tsx title="Cancel Flow"
import { useLiquidatePosition } from "@aave/react";

const [liquidatePosition, { loading, error }] = useLiquidatePosition(
  (plan, { cancel }) => {
    if (
      window.confirm("Are you sure you want to liquidate this position?") ===
      false
    ) {
      return cancel("User cancelled the liquidation");
    }

    switch (plan.__typename) {
      case "TransactionRequest":
        return sendTransaction(plan);

      case "Erc20Approval":
        if (plan.bySignature) {
          return signTypedData(plan.bySignature);
        }
        return sendTransaction(plan.byTransaction);

      case "PreContractActionRequired":
        return sendTransaction(plan.transaction);
    }
  },
);
```

</tab>
</tabgroup>
In the `Erc20Approval` case, the `bySignature` field is only available if the token supports EIP-2612 permits.

> [!NOTE]
> For tokens that need an allowance reset, like [USDT on Ethereum
> Mainnet](https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7),
> the hook calls your callback twice: reset to 0, then set the new value.
> `bySignature` is `null` for these.

#### 3. Execute the Liquidation

Then, execute the liquidation operation.

- **TypeScript**: Use the `liquidatePosition` action to create the transaction request for liquidating the unhealthy position.
- **GraphQL**: Use the `liquidatePosition` query to prepare the liquidation execution plan. Specify an exact debt amount to cover, or use `amount: { max: true }` to bring the position back to a healthy state.

Set `receiveShares: true` to receive the seized collateral as supply shares instead of the underlying asset (defaults to `false`).

<tabgroup>
<tab label="React">

```tsx title="Liquidate Specific Amount"
import {
  bigDecimal,
  evmAddress,
  type UserBorrowItem,
  type UserPosition,
  type UserSupplyItem,
} from "@aave/react";

const execute = async (
  position: UserPosition,
  debt: UserBorrowItem,
  collateral: UserSupplyItem,
) => {
  const result = await liquidatePosition({
    collateral: collateral.reserve.id,
    debt: debt.reserve.id,
    amount: {
      exact: { value: bigDecimal("1000") }, // 1000 USDC
    },
    liquidator: evmAddress(wallet.account.address), // User's address
    user: position.user,
  });

  // …
};
```

```tsx title="Liquidate Maximum Amount"
import {
  evmAddress,
  type UserPosition,
  type UserBorrowItem,
  type UserSupplyItem,
} from "@aave/react";

const execute = async (
  position: UserPosition,
  debt: UserBorrowItem,
  collateral: UserSupplyItem,
) => {
  const result = await liquidatePosition({
    collateral: collateral.reserve.id,
    debt: debt.reserve.id,
    amount: {
      max: true,
    },
    liquidator: evmAddress(wallet.account.address), // User's address
    user: position.user,
  });

  // …
};
```

</tab>
<tab label="TypeScript">

```ts title="Liquidate Specific Amount"
import { liquidatePosition } from "@aave/client/actions";
import { bigDecimal, evmAddress } from "@aave/client";

import { client } from "./client";

const result = await liquidatePosition(client, {
  collateral: collateral.reserve.id,
  debt: debtPosition.reserve.id,
  amount: {
    exact: { value: bigDecimal("1000") }, // 1000 USDC
  },
  liquidator: evmAddress(wallet.account.address),
  user: position.user,
});

// …
```

```ts title="Liquidate Maximum Amount"
import { liquidatePosition } from "@aave/client/actions";
import { evmAddress } from "@aave/client";

import { client } from "./client";

const result = await liquidatePosition(client, {
  collateral: collateral.reserve.id,
  debt: debtPosition.reserve.id,
  amount: { max: true },
  liquidator: evmAddress(wallet.account.address),
  user: position.user,
});

// …
```

```ts title="client.ts"
import { AaveClient } from "@aave/client";

export const client = AaveClient.create();
```

</tab>
<tab label="GraphQL">

```graphql title="Query"
query ($request: LiquidatePositionRequest!) {
  liquidatePosition(request: $request) {
    __typename

    ... on TransactionRequest {
      ...TransactionRequest
    }

    ... on Erc20ApprovalRequired {
      reason
      requiredAmount {
        ...DecimalNumber
      }
      currentAllowance {
        ...DecimalNumber
      }
      approvals {
        byTransaction {
          ...TransactionRequest
        }
        bySignature {
          ...PermitTypedData
        }
      }
      originalTransaction {
        ...TransactionRequest
      }
    }

    ... on InsufficientBalanceError {
      required {
        ...DecimalNumber
      }
      available {
        ...DecimalNumber
      }
    }
  }
}
```

```graphql title="TransactionRequest"
fragment TransactionRequest on TransactionRequest {
  to
  from
  data
  value
  chainId
  operations
}
```

```graphql title="PermitTypedData"
fragment PermitTypedData on PermitTypedData {
  types
  primaryType
  domain {
    name
    version
    chainId
    verifyingContract
  }
  message
}
```

</tab>
</tabgroup>
In GraphQL, below is an example of a `LiquidatePositionRequest` object.

<tabgroup>
<tab label="GraphQL">

```json title="Liquidate Specific Amount"
{
  "request": {
    "collateral": "V29ybGQ=",
    "debt": "SGVsbG8h",
    "amount": {
      "exact": {
        "value": "1000"
      }
    },
    "liquidator": "0x789…",
    "user": "0x456…"
  }
}
```

```json title="Liquidate Maximum Amount"
{
  "request": {
    "collateral": "V29ybGQ=",
    "debt": "SGVsbG8h",
    "amount": {
      "max": true
    },
    "liquidator": "0x789…",
    "user": "0x456…",
    "receiveShares": true
  }
}
```

```json title="Permit-based Approval"
{
  "request": {
    "collateral": "V29ybGQ=",
    "debt": "SGVsbG8h",
    "amount": {
      "exact": {
        "value": "1000",
        "permitSig": {
          "deadline": 1700000000,
          "value": "0xabcdef…"
        }
      }
    },
    "liquidator": "0x789…",
    "user": "0x456…"
  }
}
```

</tab>
</tabgroup>
In TypeScript, to handle ERC-20 permit-based approval, wrap the `liquidatePosition` call with `permitWith` for the [wallet library of your choice](../getting-started/typescript#integrations).

<tabgroup>
<tab label="TypeScript">

```ts title="Permit-based (Viem)"
import { liquidatePosition } from "@aave/client/actions";
import { permitWith } from "@aave/client/viem";
import { bigDecimal, evmAddress } from "@aave/client";

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

const result = await permitWith(wallet, (permitSig) =>
  liquidatePosition(client, {
    collateral: collateral.reserve.id,
    debt: debtPosition.reserve.id,
    amount: {
      exact: { value: bigDecimal("1000"), permitSig },
    },
    liquidator: evmAddress(wallet.account.address),
    user: position.user,
  }),
);

// …
```

```ts title="wallet.ts"
import { createWalletClient, http, type Hex } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";

export const wallet = createWalletClient({
  account: privateKeyToAccount(process.env.PRIVATE_KEY as Hex),
  chain: mainnet,
  transport: http(),
});
```

</tab>
</tabgroup>
The `permitWith` helper:

1. Calls the `liquidatePosition` action without a permit signature
2. If the response is `Erc20ApprovalRequired` with permit signature available, signs the permit and calls `liquidatePosition` again with the signature to get an updated execution plan
3. Otherwise, returns the execution plan unchanged (no permit needed or token doesn’t support permits)

> [!NOTE]
> For tokens that need an allowance reset, like [USDT on Ethereum
> Mainnet](https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7),
> `permitWith` skips the permit and the next step handles both approvals.

#### 4. Process the Execution Plan

In TypeScript, use `sendWith` for the [wallet library of your choice](../getting-started/typescript#integrations) to send the transactions in the execution plan, and await indexing with `client.waitForTransaction`. This wallet will pay the debt tokens and receive the seized collateral.

<tabgroup>
<tab label="TypeScript">

```ts title="Transaction-based"
import { liquidatePosition } from "@aave/client/actions";
import { sendWith } from "@aave/client/viem";

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

const result = await liquidatePosition(client, {
  // …
})
  .andThen(sendWith(wallet))
  .andThen(client.waitForTransaction);
```

```ts title="Permit-based"
import { liquidatePosition } from "@aave/client/actions";
import { bigDecimal, evmAddress } from "@aave/client";
import { permitWith, sendWith } from "@aave/client/viem";

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

const result = await permitWith(wallet, (permitSig) =>
  liquidatePosition(client, {
    collateral: collateral.reserve.id,
    debt: debtPosition.reserve.id,
    amount: {
      exact: { value: bigDecimal("1000"), permitSig },
    },
    liquidator: evmAddress(wallet.account.address),
    user: position.user,
  }),
)
  .andThen(sendWith(wallet))
  .andThen(client.waitForTransaction);
```

```ts title="wallet.ts"
import { createWalletClient, http, type Hex } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";

export const wallet = createWalletClient({
  account: privateKeyToAccount(process.env.PRIVATE_KEY as Hex),
  chain: mainnet,
  transport: http(),
});
```

</tab>
</tabgroup>
> [!NOTE]
> `sendWith` handles all required approvals, including the allowance reset for
> tokens like USDT.

In GraphQL, if the response is a `TransactionRequest`, you can send it directly using the wallet associated with the from address.

<tabgroup>
<tab label="GraphQL">

```json title="TransactionRequest"
{
  "data": {
    "liquidatePosition": {
      "__typename": "TransactionRequest",
      "to": "0x123…",
      "from": "0x789…",
      "data": "0xc2fa746c…",
      "value": "0",
      "chainId": 1,
      "operations": ["LIQUIDATION"]
    }
  }
}
```

</tab>
</tabgroup>
If the response is `Erc20ApprovalRequired`, you have two options:

**Transaction-based approval**

1. Send each transaction in the `approvals` array in sequence
2. Then send `originalTransaction`

**Permit-based approval**

> [!NOTE]
> This is available only when `approvals` contains a single `Erc20Approval`
> item.

1. Sign the `approvals[0].bySignature` typed data with the sender’s account.
2. Call the `liquidatePosition` query again with the permit signature
3. The response will be a `TransactionRequest` you can then send directly

<tabgroup>
<tab label="GraphQL">

```jsonc title="Single Approval"
{
  "data": {
    "liquidatePosition": {
      "__typename": "Erc20ApprovalRequired",
      "reason": "Insufficient allowance for ERC-20 token transfer",
      "requiredAmount": {
        "value": "1000.000000",
        "decimals": 6
      },
      "currentAllowance": {
        "value": "0.000000",
        "decimals": 6
      },
      "approvals": [
        {
          "byTransaction": {
            "to": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
            "from": "0x789…",
            "data": "0x095ea7b3000000000000000000000000878…",
            "value": "0",
            "chainId": 1,
            "operations": null
          },
          "bySignature": {
            "types": ,
            "primaryType": "Permit",
            "domain": ,
            "message": 
          }
        }
      ],
      "originalTransaction": {
        "to": "0x123…",
        "from": "0x789…",
        "data": "0xc2fa746c…",
        "value": "0",
        "chainId": 1,
        "operations": ["LIQUIDATION"]
      }
    }
  }
}
```

```json title="Multiple Approvals (USDT-like)"
{
  "data": {
    "liquidatePosition": {
      "__typename": "Erc20ApprovalRequired",
      "reason": "Token requires allowance reset before new approval",
      "requiredAmount": {
        "value": "1000.000000",
        "decimals": 6
      },
      "currentAllowance": {
        "value": "500.000000",
        "decimals": 6
      },
      "approvals": [
        {
          "byTransaction": {
            "to": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
            "from": "0x789…",
            "data": "0x095ea7b3000000000000000000000000878…0",
            "value": "0",
            "chainId": 1,
            "operations": null
          },
          "bySignature": null
        },
        {
          "byTransaction": {
            "to": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
            "from": "0x789…",
            "data": "0x095ea7b3000000000000000000000000878…1000",
            "value": "0",
            "chainId": 1,
            "operations": null
          },
          "bySignature": null
        }
      ],
      "originalTransaction": {
        "to": "0x123…",
        "from": "0x789…",
        "data": "0xc2fa746c…",
        "value": "0",
        "chainId": 1,
        "operations": ["LIQUIDATION"]
      }
    }
  }
}
```

</tab>
</tabgroup>
> [!NOTE]
> With multiple approvals in `Erc20ApprovalRequired`, send each `byTransaction`
> in order, then `originalTransaction`. For [USDT on Ethereum
> Mainnet](https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7),
> the first resets the allowance to 0 and the second sets the required amount.

If the response is an `InsufficientBalanceError`, inform the user that they don’t have enough funds to perform the liquidation.

<tabgroup>
<tab label="GraphQL">

```json title="InsufficientBalanceError"
{
  "data": {
    "liquidatePosition": {
      "__typename": "InsufficientBalanceError",
      "required": {
        "value": "1000.000000",
        "decimals": 6
      },
      "available": {
        "value": "500.000000",
        "decimals": 6
      }
    }
  }
}
```

</tab>
</tabgroup>
#### 5. Handle the Result

Finally, in React and TypeScript, handle the result.

<tabgroup>
<tab label="React">

```tsx title="Example"
const execute = async (/* … */) => {
  const result = await liquidatePosition(/* … */);

  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 "ValidationError":
        console.error(
          "Insufficient balance:",
          `required: ${result.error.cause.required.value.toDisplayString(2)}`,
          `available: ${result.error.cause.available.value.toDisplayString(2)}`,
        );
        break;

      case "UnexpectedError":
        console.error(result.error.message);
        break;
    }
    return;
  }

  console.log("Liquidation successful with hash:", result.value.txHash);
};
```

</tab>
<tab label="TypeScript">

```ts title="Example"
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 "ValidationError":
      console.error(
        "Insufficient balance:",
        `required: ${result.error.cause.required.value.toDisplayString(2)}`,
        `available: ${result.error.cause.available.value.toDisplayString(2)}`,
      );
      break;

    case "UnexpectedError":
      console.error(result.error.message);
      break;
  }
} else {
  console.log("Liquidation successful with hash:", result.value.txHash);
}
```

</tab>
</tabgroup>
### Step-by-Step with Solidity

Start with the spoke address and target user identifying the position:

<tabgroup>
<tab label="Solidity">

```solidity title="Target User Position"
address spokeAddress = 0x123…;
address targetUser = 0x456…;
```

</tab>
</tabgroup>
And with the on-chain reserve IDs for the collateral and debt:

<tabgroup>
<tab label="Solidity">

```solidity title="Target Reserves"
uint256 collateralReserveId = 43;
uint256 debtReserveId = 42;
```

</tab>
</tabgroup>
Choose the amount of debt to cover:

<tabgroup>
<tab label="Solidity">

```solidity title="Liquidate Specific Amount"
uint256 debtToCover = 1000e6; // 1000 USDC
```

```solidity title="Liquidate Maximum Amount"
uint256 debtToCover = type(uint256).max;
```

</tab>
</tabgroup>
And follow these steps to liquidate the position onchain:

#### 1. Import the Spoke Interface

First, create an instance of the `ISpoke` interface.

<tabgroup>
<tab label="Solidity">

```solidity title="Import the Spoke Interface"
import { ISpoke } from "aave-v4/src/spoke/interfaces/ISpoke.sol";

ISpoke spoke = ISpoke(spokeAddress);
```

</tab>
</tabgroup>
#### 2. Approve Token Transfer

Then, retrieve the debt reserve’s underlying token and approve the Spoke to transfer the debt tokens.

<tabgroup>
<tab label="Solidity">

```solidity title="Retrieve Reserve and Approve"
import { IERC20 } from "aave-v4/src/dependencies/openzeppelin/IERC20.sol";

ISpoke.Reserve memory debtReserve = spoke.getReserve(debtReserveId);
IERC20 token = IERC20(debtReserve.underlying);
token.approve(address(spoke), debtToCover);
```

</tab>
</tabgroup>
#### 3. Execute Liquidation

Finally, call the `liquidationCall` function on the Spoke contract, specifying the liquidation parameters.

<tabgroup>
<tab label="Solidity">

```solidity title="Liquidate Position"
spoke.liquidationCall(
  collateralReserveId,
  debtReserveId,
  targetUser,
  debtToCover,
  false
);
```

</tab>
</tabgroup>
#### Complete Example

Here are complete Solidity examples for checking position health and executing liquidation:

<tabgroup>
<tab label="Solidity">

```solidity title="Check Position Health"
import { ISpoke } from "aave-v4/src/spoke/interfaces/ISpoke.sol";

function canLiquidate(
  address spokeAddress,
  address user
) external view returns (bool, uint256 healthFactor) {
  ISpoke spoke = ISpoke(spokeAddress);
  ISpoke.UserAccountData memory accountData = spoke.getUserAccountData(user);

  healthFactor = accountData.healthFactor;

  if (accountData.totalDebtValueRay == 0) {
    return (false, type(uint256).max);
  }

  return (healthFactor < 1e18, healthFactor);
}
```

```solidity title="Execute Liquidation"
import { ISpoke } from "aave-v4/src/spoke/interfaces/ISpoke.sol";
import { IERC20 } from "aave-v4/src/dependencies/openzeppelin/IERC20.sol";

function liquidatePosition(
  address spokeAddress,
  uint256 collateralReserveId,
  uint256 debtReserveId,
  address user,
  uint256 debtToCover
) external {
  ISpoke spoke = ISpoke(spokeAddress);

  ISpoke.Reserve memory debtReserve = spoke.getReserve(debtReserveId);

  IERC20 debtToken = IERC20(debtReserve.underlying);
  debtToken.approve(spokeAddress, debtToCover);

  spoke.liquidationCall(
    collateralReserveId,
    debtReserveId,
    user,
    debtToCover,
    false
  );
}
```

</tab>
</tabgroup>
