# SDK Guide: Performing a Liquidation

The **@townsq/mm-sdk** is a lightweight JavaScript/TypeScript SDK designed for interacting with the liquidation functionality of the **TownSquare Lending Protocol**.

It enables developers to:

* Identify under-collateralized (liquidatable) loans.
* Compute loan health and repayment requirements.
* Trigger on-chain liquidation transactions.

**Note:** This SDK currently focuses only on liquidation. Support for borrowing, supplying, and other protocol actions will be added in future releases.

Installation

```bash
npm install @townsq/mm-sdk
```

or

```bash
yarn add @townsq/mm-sdk
```

### Understanding TownSquare Liquidation

Before diving into code, it’s important to understand **how liquidation works in TownSquare**.

#### 1. What Makes a Loan Liquidatable?

A loan becomes liquidatable when its **borrowed value (with interest)** is greater than or equal to its **collateral value**.

Formally checked as:

```typescript
dn.gte(
  violatorLoanInfo.totalEffectiveBorrowBalanceValue,
  violatorLoanInfo.totalEffectiveCollateralBalanceValue,
);
```

If the above condition is true → the loan is unsafe → liquidators may step in.

#### 2. Liquidator Requirements

To liquidate, a **liquidator must already have a loan** opened within the protocol.

* This loan provides the funds for repayment.
* The liquidator’s loan must hold enough collateral of the repayment token.
* If insufficient, the liquidation transaction will fail.

#### 3. Loan Types & Efficiencies

TownSquare supports **three loan types**, each with specific efficiency rules:

* **General Loans** → Accept all supported tokens.
* **Stable Efficiency** → Restricted to stable coins related assets
* **MON Efficiency Loans** → You can only borrow or supply MON-related assets with this loan

When computing loan health or preparing a liquidation, always check the `loanTypeId` to apply the correct efficiency rules.

### Reading Loans

When a liquidator service starts, it is recommended to **read all existing loans into memory** (or preferably into a persistent database).

* This gives a **snapshot** of the system’s state.
* From here, you will only need to process incremental updates from events, rather than repeatedly querying the entire chain.
* Without this snapshot, you may miss loans that are already unhealthy at startup.

#### Updating Loan State with Events

The **LoanManager contract** is the central source of truth for loan changes. It emits events for every important action that affects loan health. Liquidators must listen to these events and update their internal state accordingly.

**Events to track:**

* **Deposit**

  ```ts
  {
    loanId: string;
    poolId: number;
    amount: bigint;
    fAmount: bigint;
  }
  ```

  *Indicates collateral was added to the loan.*
* **Borrow**

  ```ts
  {
    loanId: string;
    poolId: number;
    amount: bigint;
    isStableBorrow: boolean;
    stableInterestRate: bigint;
  }
  ```

  *Indicates debt was added to the loan. If `isStableBorrow` is true, the borrow position is fixed-rate.*
* **Withdraw**

  ```ts
  {
    loanId: string;
    poolId: number;
    amount: bigint;
    fAmount: bigint;
  }
  ```

  *Indicates collateral was removed from the loan.*
* **Repay**

  ```ts
  {
    loanId: string;
    poolId: number;
    principalPaid: bigint;
    interestPaid: bigint;
    excessPaid: bigint;
  }
  ```

  *Indicates loan debt was reduced. This directly improves loan health.*
* **Liquidate**

  ```ts
  {
    violatorLoanId: string;
    liquidatorLoanId: string;
    colPoolId: number;
    borPoolId: number;
    repayBorrowBalance: bigint;
    liquidatorCollateralFAmount: bigint;
    reserveCollateralFAmount: bigint;
  }
  ```

  *Indicates a liquidation occurred. The violator’s loan was partially or fully closed, and the liquidator’s loan received seized collateral.*

When a liquidator service starts, it is recommended to **read all existing loans into memory** (or preferably into a persistent database).

* This gives a **snapshot** of the system’s state.
* From here, you will only need to process incremental updates from events, rather than repeatedly querying the entire chain.
* Without this snapshot, you may miss loans that are already unhealthy at startup.

When an event is received (e.g., `Borrow`, `Deposit`, `Repay`, `Withdraw`, `Liquidate`), liquidators should recompute the full loan state before persisting it in their database. This ensures loan health metrics are always accurate.

Example (computing `loanInfo` after receiving a `loanId` from an event):

```typescript

const poolsInfo: Partial<Record<TSTokenId, PoolInfo>> = {};
await Promise.all(
  Object.values(TESTNET_TS_TOKEN_ID).map(async (tsTokenId) => {
    const poolInfo = await TSPool.read.poolInfo(tsTokenId);
    poolsInfo[tsTokenId] = poolInfo;
  }),
);

const loanId = event.args.violatorLoanId as LoanId;

const loanTypeMap = Object.entries(TESTNET_LOAN_TYPE_ID).reduce(
  (acc, [key, value]) => {
    acc[key as any] = value;
    return acc;
  },
  {} as Record<number, LoanTypeId>,
);

try {
  const oraclePrices = await TSOracle.read.oraclePrices();
  const userLoans = await TSLoan.read.userLoans([loanId]);
  const loan = userLoans?.get(loanId);

  if (!loan) return;

  const loanType = Object.values(loanTypeMap).find((type) => type === loan.loanTypeId);

  if (!loanType || loanType === TESTNET_LOAN_TYPE_ID.DEPOSIT) return;

  const loanTypeInfo = {
    [loanType]: await TSLoan.read.loanTypeInfo(loanType),
  };

  const loanInfo = TSLoan.util.userLoansInfo(
    userLoans,
    poolsInfo,
    loanTypeInfo,
    oraclePrices,
  )[loanId];

  if (!loanInfo) return;

  // At this point, `loanInfo` contains all computed fields needed
  // to evaluate liquidation eligibility and can be stored in your DB.
} catch (e) {
  console.error("Failed to compute loan info", e);
}
```

#### Updating Loan State with Events

The **LoanManager contract** is the central source of truth for loan changes. It emits events for every important action that affects loan health. Liquidators must listen to these events and update their internal state accordingly.

### **Step-by-Step: Liquidate a Loan**

**1. Initialize the SDK and set network**

<pre class="language-typescript"><code class="lang-typescript"><strong>import { TSCore, NetworkType, TS_CHAIN_ID } from 'townsq-mm-sdk';
</strong>
TSCore.init({ network: NetworkType.TESTNET, provider: { evm: {} } });
TSCore.setNetwork(NetworkType.TESTNET);
</code></pre>

2. **Set up your signer**

```typescript
import { createWalletClient, http } from 'viem';
import { CHAIN_VIEM } from '@townsq/mm-sdk';

const signer = createWalletClient({
  chain: CHAIN_VIEM[TS_CHAIN_ID.MONAD_TESTNET],
  transport: http(), // You can pass your rpc (optional)
});

TSCore.setTSsSigner({
  signer,
  tsChainId: TS_CHAIN_ID.MONAD_TESTNET,
});

```

3. **Resolve Account ID from Address**

```typescript
import { TSAccount } from '@townsq/mm-sdk';

const accountId = await TSAccount.read.getAccountIdOfAddressOnChain(
  '0xYourEvmAddressHere'
);

```

**4. Check if the Loan is Liquidatable**

```typescript
import { TSLoan, TSPool, TSOracle, TESTNET_TS_TOKEN_ID, TESTNET_LOAN_TYPE_ID } from 'townsq-mm-sdk';
import * as dn from 'dnum';

const violatorLoanId = '0x...'; // loan ID to liquidate

// Fetch oracle price and violator loan
const [oraclePrices, userLoans] = await Promise.all([
  TSOracle.read.oraclePrices(),
  TSLoan.read.userLoans([violatorLoanId]),
]);

const poolsInfo = await Promise.all(
  Object.values(TESTNET_TS_TOKEN_ID).map(async (tokenId) => ({
    tokenId,
    pool: await TSPool.read.poolInfo(tokenId),
  }))
);

// Prepare loan info
const userGeneralLoansInfo = TSLoan.util.userLoansInfo(
  userLoans,
  Object.fromEntries(poolsInfo.map(({ tokenId, pool }) => [tokenId, pool])),
  {
    [TESTNET_LOAN_TYPE_ID.GENERAL]: await TSLoan.read.loanTypeInfo(TESTNET_LOAN_TYPE_ID.GENERAL),
  },
  oraclePrices
);

const loanInfo = userGeneralLoansInfo[violatorLoanId];

if (dn.lt(
  loanInfo.totalEffectiveBorrowBalanceValue,
  loanInfo.totalEffectiveCollateralBalanceValue
)) {
  console.log("Loan is healthy — not eligible for liquidation.");
  return;
}

```

**5. Prepare Liquidation Transaction**

**Important:** The liquidator’s loan must have sufficient collateral\
If the liquidator's loan doesn’t hold sufficient collateral to cover for the amount the liquidator wants to pay, the transaction will fail.

```typescript
import { TSLoan, convertToGenericAddress, ChainType, parseUnits } from '@townsq/mm-sdk'

const prepareLiquidationCall = await TSLoan.prepare.liquidate(
  accountId,
  '0xYourLiquidatorLoanId',
  violatorLoanId,
  TESTNET_TS_TOKEN_ID.USDC,  // Repay in USDC
  TESTNET_TS_TOKEN_ID.WETH,   // Receive WETH as collateral
  parseUnits('100', 18),      // Amount to repay
  parseUnits('0', 18),        // Minimum collateral to seize
  convertToGenericAddress('0xYourAddressAssociatedToYourAccount', ChainType.EVM)
);

```

**6. Execute the Liquidation**

```typescript
const result = await TSLoan.write.liquidate(accountId, prepareLiquidationCall);
console.log(`Transaction hash: ${result}`);
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.townsq.xyz/technical-details/liquidation/sdk-guide-performing-a-liquidation.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
