# Liquidation Solvers

### Overview

#### The Path Finding Challenge

RUJI supports **multiple collateral types** within its borrowing system. Each Credit Account can hold various assets (BTC, ETH, XRP, DOGE, etc.) as collateral against different debt positions. When liquidation is needed, each collateral type requires a unique swap path through RUJI Trade to convert it to the debt token.

**The challenge**: Calculating optimal multi-hop swap routes across multiple collateral types is computationally intensive - too expensive for on-chain execution. This creates an opportunity for external solvers.

#### How Solvers Earn

```
┌─────────────────────────────────────────────────────────┐
│                    Solver Workflow                      │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  1. Monitor Credit Accounts for LTV >= 100%             │
│                                                         │
│  2. Analyze underwater position:                        │
│     - Multiple collateral types (BTC, ETH, DOGE, etc.)  │
│     - Debt denominated in USDC, USDT, etc.              │
│     - User liquidation preferences                      │
│                                                         │
│  3. Calculate optimal liquidation path:                 │
│     - Query RUJI Trade pools for liquidity              │
│     - Find best swap routes per collateral              │
│     - Respect preference order constraints              │
│     - Minimize slippage across all swaps                │
│                                                         │
│  4. Execute liquidation on-chain                        │
│     - Submit optimized route via ExecuteMsg::Liquidate  │
│     - Earn 0.5% of repaid debt as fee                   │
│                                                         │
└─────────────────────────────────────────────────────────┘
```

#### Permissionless Participation

Anyone can build and run a liquidation solver. There's no whitelist or approval process, the protocol is open to all participants. Competition drives better execution for the protocol and users.

***

### Fee Structure

| Fee             | Rate                    | Recipient                   |
| --------------- | ----------------------- | --------------------------- |
| Liquidation Fee | 1% of repaid debt       | 50% Protocol, 50% THORChain |
| **Solver Fee**  | **0.5% of repaid debt** | **You (the liquidator)**    |

Example: Liquidating a position with 10,000 USDC debt earns \~50 USDC for the solver.

***

### Contract Addresses

<table><thead><tr><th width="220">Contract</th><th>Address</th></tr></thead><tbody><tr><td>Ghost Credit</td><td><code>thor1ekkt8wfls055t7f7yznj07j0s4mtndkq546swutzv2de7sfcxptq27duyt</code></td></tr><tr><td>RUJI Trade (BTC/USDC)</td><td><code>thor1dwsnlqw3lfhamc5dz3r57hlsppx3a2n2d7kppccxfdhfazjh06rs5077sz</code></td></tr><tr><td>RUJI Trade (ETH/USDC)</td><td><code>thor1tnd06uswj8033d0kzd5d7zre73u3uc44r2vvez26z5m4kr68vtusf2snva</code></td></tr></tbody></table>

> **Note:** For the full list of RUJI Trade pairs, check <https://rujira.network/developer/deployment> and look for `rujira-fin`.

***

### Querying Liquidatable Positions

#### Find All Accounts (Paginated)

```typescript
const queryMsg = {
  all_accounts: {
    cursor: null,  // Start from beginning
    limit: 100
  }
};

const response = await client.queryContractSmart(
  GHOST_CREDIT_ADDRESS,
  queryMsg
);

// Filter for liquidatable positions
const liquidatable = response.accounts.filter(
  acc => parseFloat(acc.ltv) >= 1.0
);
```

#### Check Single Account

```typescript
const queryMsg = {
  account: "thor1creditaccount..."
};

const account = await client.queryContractSmart(
  GHOST_CREDIT_ADDRESS,
  queryMsg
);

if (parseFloat(account.ltv) >= 1.0) {
  console.log("Account is liquidatable");
  console.log("Collaterals:", account.collaterals);
  console.log("Debts:", account.debts);
  console.log("Preferences:", account.liquidation_preferences);
}
```

#### Account Response Structure

```typescript
interface AccountResponse {
  owner: string;
  account: string;
  tag: string;
  ltv: string;  // >= "1.0" means liquidatable
  collaterals: Array<{
    collateral: { coin: { denom: string; amount: string } };
    value_full: string;      // USD value
    value_adjusted: string;  // USD value after collateral factor
  }>;
  debts: Array<{
    debt: {
      addr: string;
      borrower: { denom: string; current: string };
      current: string;  // Current debt amount
      shares: string;
    };
    value: string;  // USD value
  }>;
  liquidation_preferences: LiquidationPreferences;
}
```

***

### Path Finding Strategy

#### Multi-Collateral Accounts

A typical underwater account might look like:

```
Collaterals:
  - 0.5 BTC  ($20,000)
  - 2.0 ETH  ($6,000)
  - 50,000 DOGE ($5,000)

Debt:
  - 32,000 USDC

LTV: 103% (liquidatable)

Preferences:
  - Liquidate DOGE before ETH
  - Liquidate ETH before BTC
```

#### Route Optimization Goals

1. **Minimize total slippage** across all swaps
2. **Respect user preferences** (mandatory)
3. **Maximize your profit** (fee - gas costs)
4. **Ensure LTV drops** below liquidation threshold

#### Querying RUJI Trade Liquidity

For each collateral type, query the corresponding RUJI Trade pool:

```typescript
async function getPoolLiquidity(finAddress: string) {
  const status = await client.queryContractSmart(finAddress, { status: {} });
  const book = await client.queryContractSmart(finAddress, { book: {} });

  return {
    baseReserve: status.base_reserve,
    quoteReserve: status.quote_reserve,
    orderbook: book.orders
  };
}
```

#### Building Multi-Hop Routes

For exotic collateral types, you may need multi-hop swaps:

```
DOGE → BTC → USDC  (if DOGE/USDC pool has low liquidity)
```

```typescript
function buildMultiHopRoute(
  collateralDenom: string,
  debtDenom: string,
  pools: Map<string, PoolInfo>
): SwapRoute {
  // Direct route
  const directPool = pools.get(`${collateralDenom}/${debtDenom}`);
  if (directPool && directPool.liquidity > MIN_LIQUIDITY) {
    return { hops: [{ pool: directPool.address, offer: collateralDenom }] };
  }

  // Multi-hop via BTC
  const toBtc = pools.get(`${collateralDenom}/btc`);
  const btcToDebt = pools.get(`btc/${debtDenom}`);
  if (toBtc && btcToDebt) {
    return {
      hops: [
        { pool: toBtc.address, offer: collateralDenom },
        { pool: btcToDebt.address, offer: "btc" }
      ]
    };
  }

  throw new Error(`No route found for ${collateralDenom} → ${debtDenom}`);
}
```

***

### Executing Liquidations

#### Liquidate Message Structure

```typescript
const executeMsg = {
  liquidate: {
    addr: "thor1creditaccount...",
    msgs: [
      // LiquidateMsg array - your optimized route
    ]
  }
};
```

#### LiquidateMsg Types

```typescript
// Execute a swap on RUJI Trade
{
  execute: {
    contract_addr: "thor1finpool...",
    msg: "<base64-encoded-swap-msg>",
    funds: [{ denom: "btc", amount: "10000000" }]
  }
}

// Repay debt with tokens now in the account
{
  repay: "usdc"
}
```

#### Example: Multi-Collateral Liquidation

```typescript
import { toBase64, toUtf8 } from "@cosmjs/encoding";

async function liquidateMultiCollateral(
  client: SigningCosmWasmClient,
  sender: string,
  account: AccountResponse
) {
  const msgs: LiquidateMsg[] = [];

  // Process collaterals in preference order
  const orderedCollaterals = sortByPreference(
    account.collaterals,
    account.liquidation_preferences.order
  );

  for (const collateral of orderedCollaterals) {
    const denom = collateral.collateral.coin.denom;
    const amount = collateral.collateral.coin.amount;

    // Find best swap route for this collateral
    const route = await findBestRoute(denom, "usdc", amount);

    // Build swap message
    const swapMsg = {
      swap: { min_return: route.minReturn }
    };

    msgs.push({
      execute: {
        contract_addr: route.poolAddress,
        msg: toBase64(toUtf8(JSON.stringify(swapMsg))),
        funds: [{ denom, amount }]
      }
    });
  }

  // Final step: repay debt
  msgs.push({ repay: "usdc" });

  return client.execute(
    sender,
    GHOST_CREDIT_ADDRESS,
    { liquidate: { addr: account.account, msgs } },
    "auto"
  );
}
```

***

### Handling User Preferences

#### Preference Order

Users can specify liquidation order constraints:

```typescript
// If user set: "liquidate DOGE only after ETH is exhausted"
// Your route MUST liquidate ETH before DOGE

function sortByPreference(
  collaterals: Collateral[],
  preferenceOrder: PreferenceOrder
): Collateral[] {
  // Build dependency graph from preferences
  // Topological sort to get valid liquidation order
  // Throw if constraints cannot be satisfied
}
```

#### Preference Messages

Users can pre-define swap routes. These execute BEFORE your messages:

```typescript
// User's preference messages run first (with error tolerance)
// Then your liquidator messages run
// If preference succeeds, it may reduce work needed
```

**Important**: User preference messages use `reply_always` - if they fail, liquidation continues with your messages. Don't assume preferences will succeed.

***

### Validation Rules

Your liquidation must satisfy:

#### 1. Position Must Be Underwater

```
ltv >= liquidation_threshold (1.0)
```

#### 2. No Over-Liquidation

After liquidation:

```
ltv >= adjustment_threshold (0.9)
```

Liquidate only enough to bring LTV below 100%, not all the way to 0%.

#### 3. Slippage Limit

```
(collateral_spent_usd - debt_repaid_usd) / collateral_spent_usd <= liquidation_max_slip
```

Default `liquidation_max_slip` is 30%. Bad routes that exceed this will fail.

#### 4. Preference Order Respected

Violating user preference order causes immediate failure.

***

### Building a Liquidation Bot

#### Architecture

```
┌─────────────────────────────────────────────────────────┐
│                   Liquidation Bot                       │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐ │
│  │  Monitor    │───▶│   Analyze   │───▶│  Execute    │ │
│  │  Service    │    │   Service   │    │  Service    │ │
│  └─────────────┘    └─────────────┘    └─────────────┘ │
│        │                  │                  │         │
│        ▼                  ▼                  ▼         │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐ │
│  │ Ghost Credit│    │ RUJI Trade  │    │  Broadcast  │ │
│  │  Queries    │    │  Queries    │    │     Tx      │ │
│  └─────────────┘    └─────────────┘    └─────────────┘ │
│                                                         │
└─────────────────────────────────────────────────────────┘
```

#### Monitor Service

```typescript
class LiquidationMonitor {
  private cursor: string | null = null;

  async scan(): Promise<AccountResponse[]> {
    const liquidatable: AccountResponse[] = [];

    while (true) {
      const { accounts } = await this.client.queryContractSmart(
        GHOST_CREDIT_ADDRESS,
        { all_accounts: { cursor: this.cursor, limit: 100 } }
      );

      for (const account of accounts) {
        if (parseFloat(account.ltv) >= 1.0) {
          liquidatable.push(account);
        }
      }

      if (accounts.length < 100) {
        this.cursor = null;
        break;
      }
      this.cursor = accounts[accounts.length - 1].account;
    }

    return liquidatable;
  }
}
```

#### Analyze Service

```typescript
class RouteAnalyzer {
  async analyze(account: AccountResponse): Promise<LiquidationPlan | null> {
    // 1. Parse collaterals and debts
    const collaterals = this.parseCollaterals(account);
    const debts = this.parseDebts(account);

    // 2. Sort by preference order
    const ordered = this.sortByPreference(collaterals, account.liquidation_preferences);

    // 3. Calculate minimum liquidation needed
    const targetLtv = 0.95;  // Target 95% LTV (below 100% threshold)
    const minRepay = this.calculateMinRepay(account, targetLtv);

    // 4. Find optimal routes for each collateral
    const routes: SwapRoute[] = [];
    let totalRepay = 0;

    for (const collateral of ordered) {
      if (totalRepay >= minRepay) break;

      const route = await this.findRoute(collateral, debts[0].denom);
      routes.push(route);
      totalRepay += route.expectedOutput;
    }

    // 5. Estimate profitability
    const gasEstimate = this.estimateGas(routes);
    const fee = totalRepay * 0.005;
    const profit = fee - gasEstimate;

    if (profit <= 0) return null;

    return { routes, expectedProfit: profit };
  }
}
```

#### Execute Service

```typescript
class LiquidationExecutor {
  async execute(account: AccountResponse, plan: LiquidationPlan) {
    const msgs = this.buildMessages(plan);

    try {
      const result = await this.client.execute(
        this.sender,
        GHOST_CREDIT_ADDRESS,
        { liquidate: { addr: account.account, msgs } },
        "auto"
      );

      console.log(`Liquidation successful: ${result.transactionHash}`);
      return result;
    } catch (error) {
      console.error(`Liquidation failed: ${error.message}`);
      throw error;
    }
  }
}
```

#### Main Loop

```typescript
async function main() {
  const monitor = new LiquidationMonitor(client);
  const analyzer = new RouteAnalyzer(client);
  const executor = new LiquidationExecutor(client, senderAddress);

  while (true) {
    try {
      const liquidatable = await monitor.scan();

      for (const account of liquidatable) {
        const plan = await analyzer.analyze(account);

        if (plan && plan.expectedProfit > MIN_PROFIT) {
          await executor.execute(account, plan);
        }
      }

      await sleep(5000);  // Poll interval
    } catch (error) {
      console.error("Error in main loop:", error);
      await sleep(10000);
    }
  }
}
```

***

### Profitability Calculation

```typescript
function calculateProfitability(
  account: AccountResponse,
  routes: SwapRoute[],
  gasPriceUsd: number
): ProfitAnalysis {
  // Total debt being repaid
  const totalRepay = routes.reduce((sum, r) => sum + r.expectedOutput, 0);

  // Solver fee (0.5%)
  const solverFee = totalRepay * 0.005;

  // Estimated gas cost
  const gasUnits = routes.length * 250_000;  // ~250k per swap
  const gasCost = gasUnits * gasPriceUsd;

  // Net profit
  const netProfit = solverFee - gasCost;

  // Slippage impact
  const totalCollateralValue = routes.reduce((sum, r) => sum + r.inputValueUsd, 0);
  const slippage = (totalCollateralValue - totalRepay) / totalCollateralValue;

  return {
    totalRepay,
    solverFee,
    gasCost,
    netProfit,
    slippage,
    profitable: netProfit > 0 && slippage <= 0.3
  };
}
```

***

### Error Handling

#### Common Errors

| Error                                                  | Cause                   | Solution                        |
| ------------------------------------------------------ | ----------------------- | ------------------------------- |
| `Safe`                                                 | Account LTV < 100%      | Position not liquidatable (yet) |
| `Unsafe`                                               | Final LTV still >= 100% | Liquidation route insufficient  |
| `LiquidationMaxSlipExceeded`                           | Slippage > 30%          | Find better swap routes         |
| `Invalid liquidation attempted {coin} before {before}` | Preference violated     | Reorder collateral liquidation  |

#### Retry Strategy

```typescript
async function attemptWithRetry(
  account: AccountResponse,
  maxRetries: number = 3
) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      // Re-analyze with fresh liquidity data
      const plan = await analyzer.analyze(account);
      if (!plan) return null;

      return await executor.execute(account, plan);
    } catch (error) {
      if (error.message.includes("Safe")) {
        // Position recovered, stop trying
        return null;
      }
      if (i === maxRetries - 1) throw error;

      await sleep(1000 * (i + 1));  // Backoff
    }
  }
}
```

***

### Message Reference

#### Liquidate (Execute)

```json
{
  "liquidate": {
    "addr": "thor1creditaccount...",
    "msgs": [
      {
        "execute": {
          "contract_addr": "thor1finpool...",
          "msg": "eyJzd2FwIjp7fX0=",
          "funds": [{ "denom": "btc", "amount": "50000000" }]
        }
      },
      {
        "execute": {
          "contract_addr": "thor1finpool2...",
          "msg": "eyJzd2FwIjp7fX0=",
          "funds": [{ "denom": "eth", "amount": "200000000" }]
        }
      },
      {
        "repay": "usdc"
      }
    ]
  }
}
```

***

### Configuration Parameters

Query the Ghost Credit config for current parameters:

```typescript
const config = await client.queryContractSmart(
  GHOST_CREDIT_ADDRESS,
  { config: {} }
);
```

| Parameter               | Description                | Typical Value |
| ----------------------- | -------------------------- | ------------- |
| `liquidation_threshold` | LTV triggering liquidation | 1.0 (100%)    |
| `adjustment_threshold`  | Min LTV after liquidation  | 0.9 (90%)     |
| `liquidation_max_slip`  | Max allowed slippage       | 0.3 (30%)     |
| `fee_liquidation`       | Protocol fee               | 0.01 (1%)     |
| `fee_liquidator`        | Your fee                   | 0.005 (0.5%)  |
| `collateral_ratios`     | Collateral factors         | varies        |


---

# 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.rujira.network/developers/ruji-product-integration-guides/ruji-liquidations/liquidation-solvers.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.
