npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kunalabs-io/kai

v0.25.0

Published

TypeScript SDK for interacting with the Kai Finance protocol (https://kai.finance).

Downloads

398

Readme

@kunalabs-io/kai

TypeScript SDK for interacting with the Kai Finance protocol (https://kai.finance).

Quick Start

Install:

pnpm add @kunalabs-io/kai

Single Asset Vaults

Deposit

import { Amount, VAULTS } from '@kunalabs-io/kai'
import { Transaction } from '@mysten/sui/transactions'
import * as coin from './gen/sui/coin/functions' // bindings generated with `sui-client-gen`

async function deposit() {
  const vault = VAULTS.suiUSDT // get a vault instance
  const walletAddress = '...'

  // deposit by manually acquiring a `Balance<T>` object
  const tx = new Transaction()
  const balance = '...' // get a `Balance<T>` object from somewhere

  const ytBalance = vault.deposit(tx, balance) // returns balance of yield-bearing tokens (YT)
  const ytCoin = coin.fromBalance(tx, vault.YT.typeName, ytBalance)
  tx.transferObjects([ytCoin], walletAddress)

  // alternatively, you can use the `vault.depositFromWallet()` method
  // which will get the `Balance<T>` object from the wallet and deposit it
  const amount = Amount.fromNum(100, vault.T.decimals) // 100 suiUSDT
  await vault.depositFromWallet(tx, walletAddress, amount)
}

Withdraw

import { Amount, VAULTS } from '@kunalabs-io/kai'
import { Transaction } from '@mysten/sui/transactions'
import { SuiClient } from '@mysten/sui/client'
import * as coin from './gen/sui/coin/functions' // bindings generated with `sui-client-gen`

async function withdraw(client: SuiClient) {
  const vault = VAULTS.suiUSDT
  const walletAddress = '...'

  // withdraw by manually acquiring a `Balance<YT>` object (vault's yield-bearing tokens)
  const tx = new Transaction()
  const ytBalance = '...' // get a `Balance<YT>` object from somewhere

  const tBalance = vault.withdraw(tx, ytBalance, vault.getStrategies())
  const tCoin = coin.fromBalance(tx, vault.T.typeName, tBalance)
  tx.transferObjects([tCoin], walletAddress)

  // alternatively, you can use the `vault.withdrawToWalletYT()` method
  // which will get the `Balance<YT>` object from the wallet and withdraw it
  const amountYT = Amount.fromNum(100, vault.YT.decimals) // 100 YT tokens
  await vault.withdrawToWalletYT(tx, walletAddress, amountYT, vault.getStrategies())

  // there's also a way to withdraw based on the amount of underlying asset (T)
  const amountT = Amount.fromNum(100, vault.T.decimals) // 100 suiUSDT
  await vault.withdrawToWalletT(client, tx, walletAddress, amountT, vault.getStrategies())

  // you can use the `vault.withdrawToWalletAll()` method to withdraw everything from the vault
  await vault.withdrawToWalletAll(client, tx, walletAddress, vault.getStrategies())
}

Info and Stats

import { VAULTS, getVaultStats, getWalletVaultInfo } from '@kunalabs-io/kai'
import { SuiClient } from '@mysten/sui/client'

async function stats(client: SuiClient) {
  const walletAddress = '...'

  // first we fetch the Vault data
  const vaultData = await VAULTS.suiUSDT.fetch(client)

  // you can get the wallet Vault info by calling `getWalletVaultInfo()`
  const walletInfo = await getWalletVaultInfo(client, walletAddress, vaultData)
  console.log({
    vault: VAULTS.suiUSDT.T.symbol,
    ytBalance: walletInfo.ytBalance.toString(),
    equity: walletInfo.equity.toString(),
  })

  // you can get the global Vault stats by calling `getVaultStats()`
  const stats = getVaultStats(vaultData)
  console.log({
    vault: VAULTS.suiUSDT.T.symbol,
    tvl: stats.tvl.toString(),
    apr: stats.apr,
    apy: stats.apy,
  })

  // or this helper function that gets all vault stats
  const allStats = await getAllVaultStats(client)
}

LP Positions

Create Position

import { Amount, POSITION_CONFIG_INFOS, Price, muldiv, pythPrice } from '@kunalabs-io/kai'
import { Transaction } from '@mysten/sui/transactions'
import { SuiClient } from '@mysten/sui/client'

async function create(client: SuiClient) {
  const walletAddress = '...'

  const configInfo = POSITION_CONFIG_INFOS.find(info => info.name === 'Bluefin suiUSDT/USDC')!
  // or e.g. `ALL_POSITION_CONFIG_INFOS['0x888fcd428659608b1adb45790f65dfbac4352150f67d6312f0c0a5f1f9b04692']`

  const config = await configInfo.fetchConfig(client)

  const pool = await configInfo.fetchPool(client)
  const [pioX, pioY] = await Promise.all([
    configInfo.pioInfoX.fetchPioData(client),
    configInfo.pioInfoY.fetchPioData(client),
  ])

  const [, tickA] = pool.priceToClosestInitializablePrice(
    Price.fromHuman(configInfo.X, configInfo.Y, '1')
  )
  const [, tickB] = pool.priceToClosestInitializablePrice(
    Price.fromHuman(configInfo.X, configInfo.Y, '1.0002')
  )

  // principal deposit amounts
  const UX = Amount.fromNum(100, config.info.X.decimals) // 100 suiUSDT
  const UY = Amount.fromNum(100, config.info.Y.decimals) // 100 USDC

  // we find the max liquidity (max leverage) and then adjust it for slippage
  const maxL = config.findMaxPositionLiquidity({
    tickA,
    tickB,
    UX,
    UY,
    poolPrice: pool.currentPrice(),
    pythPrice: pythPrice(pioX, pioY),
  })
  const slippageBps = 50n // 0.5%
  const l = muldiv(maxL, 10000n - slippageBps, 10000n)

  const tx = new Transaction()
  config.createPositionFromWallet(
    tx,
    {
      tickA,
      tickB,
      liquidity: l,
      UX,
      UY,
    },
    walletAddress
  ) // see also `config.createPosition()`
}

Fetch Positions and Info

import { Position, getAllWalletPositions } from '@kunalabs-io/kai'
import { SuiClient } from '@mysten/sui/client'

export async function positionInfo(client: SuiClient) {
  // get all positions for a wallet
  const walletAddress = '...'
  const res = await getAllWalletPositions(client, walletAddress)

  // or get an individual position
  const positionId = '...'
  const position = await Position.fetch(client, positionId)

  // print some position info
  const pool = await position.configInfo.fetchPool(client)
  const [supplyPoolX, supplyPoolY] = await Promise.all([
    position.configInfo.supplyPoolXInfo.fetch(client),
    position.configInfo.supplyPoolYInfo.fetch(client),
  ])
  const configData = await position.configInfo.fetchConfig(client)

  const inRange = position.inRange(pool.currentTick())
  const equity = position.calcEquityAmountsHuman({
    poolPrice: pool.currentPrice(),
    supplyPoolX,
    supplyPoolY,
    timestampMs: Date.now(),
  })
  const debt = position.calcDebtAmounts({
    supplyPoolX,
    supplyPoolY,
    timestampMs: Date.now(),
  })
  const lpAmounts = position.calcLpAmounts(pool.currentPrice())
  const marginLevel = position.calcMarginLevel({
    currentPrice: pool.currentPrice(),
    supplyPoolX,
    supplyPoolY,
    timestampMs: Date.now(),
  })
  const liquidationPrices = position.calcLiquidationPrices({
    config: configData,
    supplyPoolX,
    supplyPoolY,
    timestampMs: Date.now(),
  })
  const deleveragePrices = position.calcDeleveragePrices({
    config: configData,
    supplyPoolX,
    supplyPoolY,
    timestampMs: Date.now(),
  })
  const interestRates = position.getInterestRates({
    supplyPoolX,
    supplyPoolY,
    timestampMs: Date.now(),
  })

  console.log({
    positionId: position.id,
    inRange,
    equity,
    debt: {
      x: debt.x.toDecimal(),
      y: debt.y.toDecimal(),
    },
    lpAmounts: {
      x: lpAmounts.x.toDecimal(),
      y: lpAmounts.y.toDecimal(),
    },
    marginLevel: marginLevel.toDP(4).toString(),
    liquidationPrices: {
      low: liquidationPrices[0].toString(),
      high: liquidationPrices[1].toString(),
    },
    deleveragePrices: {
      low: deleveragePrices[0].toString(),
      high: deleveragePrices[1].toString(),
    },
    interestRates: {
      x: interestRates.x.toString(),
      y: interestRates.y.toString(),
    },
  })
}

Withdraw (reduce) and Close (delete)

Withdraws liquidity from the position.

import { Position, findPositionCapForWalletPosition, USDC } from '@kunalabs-io/kai'
import { SuiClient } from '@mysten/sui/client'

export async function withdraw(client: SuiClient) {
  const walletAddress = '...'

  const positionId = '...'
  const position = await Position.fetch(client, positionId)

  // find the position cap manually (alternatively it can be cached and passed in)
  const positionCap = await findPositionCapForWalletPosition(client, position.id, walletAddress)
  if (!positionCap) {
    throw new Error(`PositionCap not found for position ${position.id}`)
  }

  const router = new AfRouterAdapter()
  // or... `new CetusAggregatorAdapter(new CetusAggregatorClient())`

  // The reduction (withdrawal) process consists of withdrawing the LP ammounts, any extra collateral, and
  // repaying the debt based on the reduction factor. All this needs to happen in the same transaction.
  //
  // When the position is fully reduced, it becomes inactive and it's recommended to delete (close) it
  // to claim the storage rebate. In order to delete it, the position must be fully reduced (factor = 1),
  // and all pending collected fees and rewards must be collected. This can be done manually but it's
  // somewhat intricate so `reduceAndMaybeDelete()` does this for you. See the implementation for more details.
  const tx = await position.reduceAndMaybeDelete(
    client,
    router,
    {
      factor: new Decimal(0.5), // reduce the position by 50%
      positionCapId: positionCap.id,
      routerArgs: {
        swapMethod: 'exact-in', // or 'exact-out'
        slippage: 0.0005, // 0.05%
        overestimationFactor: 0.0005, // 0.05%
      }
      convertRewardsTo: USDC,
    },
    walletAddress
  )
}

Withdraw Pending Rewards

Withdraws pending rewards from the position.

import {
  AfRouterAdapter,
  CetusAggregatorAdapter,
  Position,
  USDC,
  findPositionCapForWalletPosition,
} from '@kunalabs-io/kai'
import { SuiClient } from '@mysten/sui/client'
import { AggregatorClient as CetusAggregatorClient } from '@cetusprotocol/aggregator-sdk'

async function withdrawRewards(client: SuiClient) {
  const walletAddress = '...'

  const positionId = '...'
  const position = await Position.fetch(client, positionId)

  // find the position cap manually (alternatively it can be cached and passed in)
  const positionCap = await findPositionCapForWalletPosition(client, position.id, walletAddress)
  if (!positionCap) {
    throw new Error(`PositionCap not found for position ${position.id}`)
  }

  const router = new AfRouterAdapter()
  // or... `new CetusAggregatorAdapter(new CetusAggregatorClient())`

  // see `position.withdrawAllRewards()` if you need something more custom
  const tx = await position.withdrawAllRewardsConvertAndTransfer(
    client,
    router,
    {
      positionCapId: positionCap.id,
      convertRewardsTo: USDC,
      slippage: 0.01,
    },
    walletAddress
  )
}

Compound

Compounds the position by collecting all rewards, swapping them to the correct ratio, and depositing them back into the position.

async function compound(client: SuiClient) {
  const walletAddress = '...'

  const positionId = '...'
  const position = await Position.fetch(client, positionId)

  // find the position cap manually (alternatively it can be cached and passed in)
  const positionCap = await findPositionCapForWalletPosition(client, position.id, walletAddress)
  if (!positionCap) {
    throw new Error(`PositionCap not found for position ${position.id}`)
  }

  const pool = await position.configInfo.fetchPool(client)

  const tx = await position.compound(
    client,
    {
      pool,
      positionCapId: positionCap.id,
      slippage: 0.01,
    },
    walletAddress
  )
}

Liquidation Framework

The SDK provides functionality for monitoring, controlling, and executing liquidations of LP positions.

Features

  • Real-time position monitoring
  • Automated liquidation execution
  • Flash swap execution for liquidations
  • OpenTelemetry metrics

System Architecture

The SDK liqudation consists of three main components:

  1. Position Monitor (RpcPositionMonitor)

    • Continuously watches for positions that need liquidation
    • Polls the chain at regular intervals
    • Triggers events when positions need liquidation
  2. Liquidation Controller (LiquidationController)

    • Acts as the decision-making component
    • Receives positions from the monitor
    • Determines how to handle liquidations
    • Coordinates with the executor to perform liquidations
  3. Flash Swap Executor (FlashSwapExecutor)

    • Handles the actual execution of liquidations
    • Signs and sends transactions to the network
    • Performs flash swaps for liquidation

The system follows a clear flow:

  1. Monitor detects positions needing liquidation
  2. Controller receives and processes these positions
  3. Executor carries out the actual liquidation transactions

Example

This example repository demonstrates how to implement a liquidation bot for the Kai Finance protocol, including position monitoring, liquidation controller, and flash swap execution. The example should help you understand how to use the SDK's liquidation features in practice.

Kai Liquidation Bot Example