Docs
SDK

Core Concepts

The PerpCity SDK is built around a few key concepts that make it easy to integrate perpetual futures into your application.

PerpCityContext

The PerpCityContext is the central class that holds your wallet connection and contract addresses. All SDK functions accept a context instance as their first parameter.

type PerpCityContextConfig = {
  walletClient: WalletClient
  rpcUrl: string
  deployments: {
    perpManager: Address
    usdc: Address
    feesModule?: Address
    marginRatiosModule?: Address
    lockupPeriodModule?: Address
    sqrtPriceImpactLimitModule?: Address
  }
}

Creating a Context

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

const context = new PerpCityContext({
  walletClient,
  rpcUrl: process.env.RPC_URL!,
  deployments: {
    perpManager: '0x...',
    usdc: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'
  }
})

Config Caching System

The SDK automatically caches perp configurations to improve performance:

// First call fetches from blockchain
const config1 = await context.getPerpConfig('0x...')

// Subsequent calls use cached data
const config2 = await context.getPerpConfig('0x...') // Instant

Cache Management

Configs are cached in memory with a 5-minute TTL and automatically expire. After expiration, the next call fetches fresh data from the blockchain.

Numbers and BigInt Handling

The SDK accepts human-readable values for most parameters and handles all scaling internally:

// Pass values in human units — the SDK scales them for you
const margin = 1000   // 1000 USDC (not 1000000000)
const leverage = 5    // 5x leverage (not 5000000000000000000)

You do not need to manually scale values. The SDK converts human-readable numbers to the correct on-chain format (6-decimal USDC, Q96 prices, etc.) internally.

For cases where you need raw BigInt control (e.g., liquidity for maker positions or unspecifiedAmountLimit), pass a bigint value directly.

Position and Perp Tracking

The SDK does not track positions or perps automatically. You must implement your own tracking system.

Tracking Positions

Monitor the PositionOpened event from the PerpManager contract:

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

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

// Watch for new positions
publicClient.watchContractEvent({
  address: perpManagerAddress,
  abi: perpManagerAbi,
  eventName: 'PositionOpened',
  onLogs(logs) {
    logs.forEach(log => {
      const { user, perpId, positionId, isTaker } = log.args
      // Store in your database or state
    })
  }
})

Tracking Perps

Monitor the PerpCreated event:

publicClient.watchContractEvent({
  address: perpManagerAddress,
  abi: perpManagerAbi,
  eventName: 'PerpCreated',
  onLogs(logs) {
    logs.forEach(log => {
      const { perpId, beacon, creator } = log.args
      // Store perp metadata
    })
  }
})

Data Fetching Patterns

The SDK uses a two-step pattern for querying data:

  1. Fetch data via context methods (async, returns data objects)
  2. Extract values via pure functions (sync, operate on data objects)

Individual Queries

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

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

// Step 2: Extract specific values via pure functions
const mark = getPerpMark(perpData) // No await needed

Batch Queries

For multiple positions, use getUserData to fetch everything efficiently:

const positions = [
  { perpId: '0x...', positionId: 1n, isLong: true, isMaker: false },
  { perpId: '0x...', positionId: 2n, isLong: false, isMaker: true }
]

// Fetch all user data in one call
const userData = await context.getUserData(userAddress, positions)

// Access data directly from the object
console.log(userData.usdcBalance)
console.log(userData.openPositions[0].liveDetails.pnl)
console.log(userData.openPositions[1].liveDetails.effectiveMargin)

Next Steps

Now that you understand the core concepts: