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...') // InstantCache 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:
- Fetch data via context methods (async, returns data objects)
- 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 neededBatch 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: