Docs

PerpCity Rust SDK

High-performance Rust SDK for HFT bots and latency-sensitive trading on PerpCity perpetual futures.

The PerpCity Rust SDK is a high-performance Rust library for the PerpCity perpetual futures protocol on Base L2. Built for HFT bots and latency-sensitive trading systems with lock-free nonce management, read/write endpoint splitting, Multicall3 batching, and cached gas estimation.

Why Rust?

  • Zero-cost abstractions -- Async/await with Tokio, no runtime overhead
  • Lock-free nonce management -- AtomicU64 for O(1) nonce acquisition on the hot path
  • Read/write endpoint splitting -- Route reads to free public RPCs, writes to paid endpoints, with automatic circuit-breaker failover
  • Multicall3 batching -- Fetch N balances or a full market snapshot in a single RPC call (1 CU instead of N)
  • Cached gas estimation -- eth_estimateGas on first call, cached by function selector
  • 2-tier state caching -- Fast (2s) and slow (60s) TTL layers tuned for Base L2 block times
  • Human-readable API -- f64 prices and margins, SDK handles on-chain 6-decimal conversion

Architecture

LayerDescriptionNetwork Required
Pure MathPrice/tick conversions, position calculations, liquidity mathNo
HFT InfrastructureNonce management, multi-RPC failover, fee/gas caching, latency tracking, tx pipelineNo
Contract InteractionPerpClient, trading functions, Multicall3 batching, USDC approvalYes

Quick Start

use alloy::primitives::{B256, U256};
use alloy::signers::local::PrivateKeySigner;
use perpcity_sdk::{
    PerpClient, Deployments, HftTransport, TransportConfig,
    OpenTakerParams, Urgency,
};

#[tokio::main]
async fn main() -> perpcity_sdk::Result<()> {
    let signer: PrivateKeySigner = "0xYOUR_PRIVATE_KEY".parse()?;

    let transport = HftTransport::new(
        TransportConfig::builder()
            .shared_endpoint("https://base.g.alchemy.com/v2/KEY")
            .read_endpoint("https://base-rpc.publicnode.com")
            .build()?,
    )?;

    let deployments = Deployments {
        perp_manager: "0xPERP_MANAGER".parse()?,
        usdc: "0xUSDC".parse()?,
        ..Default::default()
    };

    let client = PerpClient::new_base_mainnet(transport, signer, deployments)?;
    client.sync_nonce().await?;
    client.refresh_gas().await?;
    client.ensure_approval(U256::MAX).await?;

    let perp_id: B256 = "0xYOUR_PERP_ID".parse()?;

    // Fetch config + live market data in 2 multicalls
    let (config, snapshot) = client.get_perp_snapshot(perp_id).await?;
    println!("Mark: {:.2}, Index: {:.2}", snapshot.mark_price, snapshot.index_price);

    // Open a 10x long
    let result = client.open_taker(perp_id, &OpenTakerParams {
        is_long: true,
        margin: 100.0,
        leverage: 10.0,
        unspecified_amount_limit: 0,
    }, Urgency::Normal).await?;

    println!("Position opened: {}", result.pos_id);

    // Check live details
    let details = client.get_live_details(result.pos_id).await?;
    println!("PnL: {:.2}", details.pnl);

    Ok(())
}

Guides

Resources