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:
priceLower<priceUpper- Prices that correspond to valid ticks (multiples of tick spacing)
- 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 skewLockup 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 durationYou cannot withdraw liquidity during the lockup period. Make sure you understand the lockup duration before depositing.
Common Errors
Invalid Tick Range
Error: Invalid tick rangeSolution: Ensure lower < upper and both are multiples of tick spacing.
Ticks Out of Bounds
Error: Ticks exceed market boundsSolution: Query bounds with getPerpBounds() and choose ticks within range.
Insufficient Liquidity
Error: Insufficient USDC balanceSolution: Reduce liquidity amount or deposit more USDC.