Docs
SDK

How to Provide Liquidity

This guide shows you how to provide liquidity (open maker positions) and earn fees from traders.

Basic Liquidity Provision

Provide liquidity across a price range:

import { openMakerPosition } from '@strobelabs/perpcity-sdk'

await openMakerPosition(
  context,
  '0x...', // perpId (Hex)
  {
    margin: 10000,        // Margin in USDC
    priceLower: 2900,     // Lower price bound
    priceUpper: 3100,     // Upper price bound
    liquidity: 1000000n,  // Liquidity amount
    maxAmt0In: 1000000,   // Max token0 input
    maxAmt1In: 1000000    // Max token1 input
  }
)

Getting Market Information

import { getPerpTickSpacing, getPerpBounds } from '@strobelabs/perpcity-sdk'

// Step 1: Fetch perp data
const perpData = await context.getPerpData('0x...')

// Step 2: Extract tick spacing
const tickSpacing = getPerpTickSpacing(perpData)
console.log(`Tick spacing: ${tickSpacing}`)

// Step 3: Extract bounds
const bounds = getPerpBounds(perpData)
console.log(`Bounds:`, bounds)

Choosing Price Ranges

Your price range must be:

  1. priceLower < priceUpper
  2. Prices that correspond to valid ticks (multiples of tick spacing)
  3. Within the market's bounds
import { priceToTick } from '@strobelabs/perpcity-sdk'

// Example: Convert prices to ticks to validate
const priceLower = 2900
const priceUpper = 3100

// Check that prices convert to valid ticks
const lowerTick = priceToTick(priceLower, true)
const upperTick = priceToTick(priceUpper, false)

// Ensure they're multiples of tick spacing
const isValid = (lowerTick % tickSpacing === 0) && (upperTick % tickSpacing === 0)

Full Liquidity Example

import {
  PerpCityContext,
  openMakerPosition,
  getPerpTickSpacing,
  getPerpMark
} from '@strobelabs/perpcity-sdk'
import { createWalletClient, http } from 'viem'
import { base } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'

async function provideLiquidity() {
  // Setup wallet
  const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
  const walletClient = createWalletClient({
    account,
    chain: base,
    transport: http(process.env.RPC_URL)
  })

  // Initialize context
  const context = new PerpCityContext({
    walletClient,
    rpcUrl: process.env.RPC_URL!,
    deployments: {
      perpManager: process.env.PERP_MANAGER_ADDRESS as `0x${string}`,
      usdc: process.env.USDC_ADDRESS as `0x${string}`
    }
  })

  const perpId = '0x...'

  // Get market info
  const perpData = await context.getPerpData(perpId)
  const tickSpacing = getPerpTickSpacing(perpData)
  const currentMark = getPerpMark(perpData)

  console.log(`Current mark: ${currentMark}`)
  console.log(`Tick spacing: ${tickSpacing}`)

  // Provide liquidity around current price (±5%)
  const priceLower = currentMark * 0.95
  const priceUpper = currentMark * 1.05

  console.log(`Providing liquidity from ${priceLower} to ${priceUpper}`)

  await openMakerPosition(context, perpId, {
    margin: 10000,        // 10,000 USDC margin
    priceLower,
    priceUpper,
    liquidity: 1000000n,  // Liquidity amount
    maxAmt0In: 1000000,
    maxAmt1In: 1000000
  })

  console.log('Liquidity provided successfully!')
}

provideLiquidity()

Tracking Your Position

Like taker positions, you must track your position ID:

import { createPublicClient, http } from 'viem'
import { base } from 'viem/chains'

const publicClient = createPublicClient({
  chain: base,
  transport: http(process.env.RPC_URL)
})

const blockNumber = await publicClient.getBlockNumber()

// Open position
await openMakerPosition(context, perpId, { /* ... */ })

// Get position ID from event
const logs = await publicClient.getContractEvents({
  address: context.deployments().perpManager,
  abi: perpManagerAbi,
  eventName: 'PositionOpened',
  fromBlock: blockNumber,
  toBlock: 'latest'
})

const myPosition = logs.find(log =>
  log.args.user === context.walletClient.account.address &&
  log.args.isTaker === false // Maker position
)

console.log(`Your maker position ID: ${myPosition.args.positionId}`)

Calculating Expected Returns

Estimate your fee earnings:

import { getPerpFees } from '@strobelabs/perpcity-sdk'

// Fetch perp data
const perpData = await context.getPerpData('0x...')

// Extract fees
const fees = getPerpFees(perpData)

console.log({
  creatorFee: fees.creatorFee,
  insuranceFee: fees.insuranceFee,
  lpFee: fees.lpFee,           // Your share of fees as LP
  liquidationFee: fees.liquidationFee
})

// Your fee earnings = (Trading volume in your range) × lpFee
// Plus/minus funding payments based on market skew

Lockup Periods

Liquidity may be locked for a minimum period:

// Check lockup period before providing
const config = await context.getPerpConfig('0x...')
console.log('Lockup period module:', config.lockupPeriod)

// Plan to keep liquidity for at least the lockup duration

You cannot withdraw liquidity during the lockup period. Make sure you understand the lockup duration before depositing.

Common Errors

Invalid Tick Range

Error: Invalid tick range

Solution: Ensure lower < upper and both are multiples of tick spacing.

Ticks Out of Bounds

Error: Ticks exceed market bounds

Solution: Query bounds with getPerpBounds() and choose ticks within range.

Insufficient Liquidity

Error: Insufficient USDC balance

Solution: Reduce liquidity amount or deposit more USDC.

Next Steps