# Scallop Market Limits

> Query Scallop per-asset borrow/supply caps and isolated-asset rules. Use when user says "borrow limit", "supply limit", "borrow cap", "supply cap", "supply ceiling", "isolated asset", "isolated collateral", "asset cap", "pool cap", "can I borrow more", or asks why a supply/borrow is rejected despite a healthy obligation.

- Skill: `scallop-io/scallop-market-limits` (Agent Skill)
- Install (CLI): `npx skillmds@latest add scallop-io/scallop-market-limits`
- Raw SKILL.md: https://api.skillmd.com/api/skills/scallop-io/scallop-market-limits/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: scallop-io (https://skillmd.com/u/scallop-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/scallop-io/scallop-market-limits

---


# Market Limits & Isolated Assets

Read the protocol-level caps that gate every supply/borrow op, independent of obligation health.

## Overview

Scallop enforces **two layers** of constraints before a state-changing op succeeds:

1. **Per-obligation health** — collateral factor × USD value of collateral must cover the new debt. Documented in [obligation-manager](../scallop-obligation-manager/SKILL.md).
2. **Per-asset market caps** — global ceilings the protocol places on each pool, plus the "isolated asset" flag on certain collaterals. This is the layer this skill covers.

If a `supply_quick`/`borrow_quick` reverts even though the obligation looks healthy and there's plenty of liquidity, the cause is almost always one of these caps.

## Borrow & Supply Limits

Each asset has independent `borrow_limit` and `supply_limit` values stored on-chain. The TS SDK (verified against `@scallop-io/sui-scallop-sdk` v4.3.0) exposes them two ways:

1. **Normalized, on `MarketPool`** (preferred): `getMarketPool(coinName)` returns `maxSupplyCoin` / `maxBorrowCoin` alongside the current `supplyCoin` / `borrowCoin` — all `number`s already shifted to human coin units, safe for plain arithmetic. See [src/repositories/market/utils.ts](../../../sui-scallop-sdk/src/repositories/market/utils.ts).
2. **Raw, as strings**: `getPoolSupplyLimit` / `getPoolBorrowLimit` return the cap as a decimal string in the asset's **base units**. These can exceed `Number.MAX_SAFE_INTEGER` — use `BigNumber` (or `BigInt`, the values are integers) if you do math on them; don't cast to `number`.

> **Imports**: v4.3.0 exports the package root plus the `./client`, `./query`, `./builder`, `./types`, `./errors`, and `./logger` subpaths. Internal deep paths (e.g. `.../src/repositories/...`) are **not** exported — go through `scallop.client.query.*` or the public subpath entries.

```typescript
const scallopSDK = new Scallop({ networkType: 'mainnet' });
await scallopSDK.init();
const { query, builder } = scallopSDK.client;

// Preferred: normalized coin-unit fields on the market pool
const pool = await query.getMarketPool('usdc');
// pool.maxSupplyCoin / pool.maxBorrowCoin  -> caps in coin units (0 = no cap set)
// pool.supplyCoin   / pool.borrowCoin      -> current usage in coin units

// Raw base-unit strings (never null — '0' when the field is missing/unparseable)
const borrowCapRaw = await query.getPoolBorrowLimit('usdc'); // Promise<string>
const supplyCapRaw = await query.getPoolSupplyLimit('usdc'); // Promise<string>
```

Both raw wrappers read the on-chain `BorrowLimitKey` / `SupplyLimitKey` dynamic field on the market object — see `getSupplyLimit` / `getBorrowLimit` in [src/repositories/market/helpers.ts](../../../sui-scallop-sdk/src/repositories/market/helpers.ts), surfaced through [src/models/scallopQuery/index.ts](../../../sui-scallop-sdk/src/models/scallopQuery/index.ts).

The Python SDK (`sui-scallop-sdk` **>= 0.3.0a1**) exposes the same reads: `query.get_supply_limit(coin_name)` / `query.get_borrow_limit(coin_name)` return `Decimal` in human-readable coin units (`0` when unavailable), plus batched `get_supply_limits()` / `get_borrow_limits()`.

### Pattern: Cap-Aware Supply

```typescript
async function safeSupply(
  scallopSDK: Scallop,
  coinName: string,
  amountInBaseUnits: number
) {
  const { query, builder } = scallopSDK.client;
  // suiKit is a getter on ScallopClient (client.suiKit), not on Scallop itself.
  const sender = scallopSDK.client.suiKit.currentAddress;

  // Compare in normalized coin units — maxSupplyCoin / supplyCoin are already
  // decimal-shifted numbers, so no BigInt/precision pitfalls.
  const pool = await query.getMarketPool(coinName);
  if (!pool) throw new Error(`Unknown pool ${coinName}`);
  const cap = pool.maxSupplyCoin;                 // 0 = no cap configured
  const headroom = Math.max(cap - pool.supplyCoin, 0);
  const amountInCoin = amountInBaseUnits / 10 ** pool.coinDecimal;

  if (cap > 0 && amountInCoin > headroom) {
    throw new Error(
      `Supply ${amountInCoin} ${coinName} exceeds cap headroom ${headroom}`
    );
  }

  const tx = builder.createTxBlock();
  // supplyQuick returns an sCoin by default (returnSCoin = true).
  const sCoin = await tx.supplyQuick(amountInBaseUnits, coinName);
  tx.transferObjects([sCoin], sender);
  return builder.signAndSendTxBlock(tx);
}
```

### Pattern: Cap-Aware Borrow

```typescript
async function safeBorrow(
  scallopSDK: Scallop,
  obligationId: string,
  obligationKey: string,
  coinName: string,
  amountInBaseUnits: number
) {
  const { query, builder } = scallopSDK.client;
  const sender = scallopSDK.client.suiKit.currentAddress;

  const pool = await query.getMarketPool(coinName);
  if (!pool) throw new Error(`Unknown pool ${coinName}`);
  const cap = pool.maxBorrowCoin;                 // coin units; 0 = no cap
  const headroom = Math.max(cap - pool.borrowCoin, 0);
  const amountInCoin = amountInBaseUnits / 10 ** pool.coinDecimal;

  if (cap > 0 && amountInCoin > headroom) {
    throw new Error(
      `Borrow ${amountInCoin} ${coinName} exceeds cap headroom ${headroom}`
    );
  }

  const tx = builder.createTxBlock();
  const coin = await tx.borrowQuick(amountInBaseUnits, coinName, obligationId, obligationKey);
  tx.transferObjects([coin], sender);
  return builder.signAndSendTxBlock(tx);
}
```

## Isolated Assets

An **isolated asset** can only be used as collateral in an obligation that has no other collateral. This protects the protocol from cross-correlated risk on long-tail assets — but it means a strategy that tries to top up an existing multi-collateral obligation with an isolated coin will revert.

```typescript
const allIsolated = await query.getIsolatedAssets();
const flag        = await query.isIsolatedAsset('musd');
```

The SDK-side implementation lives in [src/repositories/isolatedAssets/](../../../sui-scallop-sdk/src/repositories/isolatedAssets/); the on-chain flag is the `IsolatedAssetKey` dynamic field on the market object. Python equivalents (`sui-scallop-sdk` >= 0.3.0a1): `query.get_isolated_assets()` / `query.is_isolated_asset(coin_name)`. The `MarketPool` object also carries an `isIsolated` boolean.

### Pattern: Validate Before Depositing Collateral

```typescript
async function addCollateralChecked(
  scallopSDK: Scallop,
  obligationId: string,
  coinName: string,
  amount: number
) {
  const { query, builder } = scallopSDK.client;
  const sender = scallopSDK.client.suiKit.currentAddress;

  if (await query.isIsolatedAsset(coinName)) {
    const account = await query.getObligationAccount(obligationId);
    const collaterals = Object.values(account?.collaterals ?? {}).filter(Boolean);
    if (collaterals.length > 0) {
      throw new Error(
        `${coinName} is an isolated asset; obligation already has collateral`
      );
    }
  }

  const tx = builder.createTxBlock();
  await tx.depositCollateralQuick(amount, coinName, obligationId);
  return builder.signAndSendTxBlock(tx);
}
```

> The TS builder method for adding collateral is **`depositCollateralQuick`** — it mirrors the protocol's `deposit_collateral` Move call. The pre-v4 names `depositQuick` / `addCollateralQuick` were removed in SDK v4; use `supplyQuick` / `depositCollateralQuick`.

### When to Open a New Obligation

If you want exposure to an isolated asset alongside existing positions, open a **separate obligation** for it:

```typescript
// v4.3.0 returns the new SuiTransactionBlockResponse union — there is no
// `objectChanges` field. Pull created ids from effects.changedObjects and
// resolve their types via objectTypes:
const result = await scallopSDK.client.openObligation();
if (result.$kind !== 'Transaction') throw new Error('tx failed');
const created = (result.Transaction.effects?.changedObjects ?? []).filter(
  (o) => o.idOperation === 'Created'
);
const types = result.Transaction.objectTypes;
const obligationId = created.find((o) =>
  types?.[o.objectId]?.endsWith('::obligation::Obligation')
)?.objectId;
const obligationKeyId = created.find((o) =>
  types?.[o.objectId]?.endsWith('::obligation::ObligationKey')
)?.objectId;
// ...then call depositCollateralQuick against the new obligation.
```

See [obligation-manager](../scallop-obligation-manager/SKILL.md) for the obligation lifecycle.

## Composite Health Check

Combine cap + isolation + health into a single pre-flight check used by liquidator/strategist code:

```typescript
type Preflight =
  | { ok: true }
  | { ok: false; reason: 'cap' | 'isolated' | 'health'; detail: string };

async function preflightBorrow(
  scallopSDK: Scallop,
  obligationId: string,
  coinName: string,
  amountInCoin: number // human coin units
): Promise<Preflight> {
  const { query } = scallopSDK.client;

  const pool = await query.getMarketPool(coinName);
  if (!pool) return { ok: false, reason: 'cap', detail: 'unknown pool' };
  const headroom = Math.max(pool.maxBorrowCoin - pool.borrowCoin, 0);
  if (pool.maxBorrowCoin > 0 && amountInCoin > headroom) {
    return { ok: false, reason: 'cap', detail: `headroom ${headroom}` };
  }

  // Health: see obligation-manager for the full collateral-factor formula.
  // Use query.getObligationAccount(obligationId) to drive the math.

  return { ok: true };
}
```

## Error Handling

| Error | Code | Likely Cause |
|-------|------|--------------|
| `BorrowCapReached` (`borrow_limit_reached_error`) | 104 / `0x0014005` | New borrow would push the pool's total borrow above its cap |
| `SupplyCapReached` (`supply_limit_reached`) | 103 / `0x0014002` | New supply would push the pool's total supply above its cap |
| `unable_to_borrow_other_coin_with_isolated_asset` | `0x0000505` | Borrowing another coin while an isolated asset is used as collateral (or vice versa) |

See [error-codes.md](../../references/error-codes.md) for the full table.

These are protocol-level reverts — they fire regardless of obligation health.

## References

- [query-data Skill](../scallop-query-data/SKILL.md) - Other on-chain reads
- [obligation-manager Skill](../scallop-obligation-manager/SKILL.md) - Per-obligation health
- [lend-integration Skill](../scallop-lend-integration/SKILL.md) - Supply/borrow ops
- [supported-coins.md](../../references/supported-coins.md) - Asset list

