Documentation
Technical reference and integration guides
Comprehensive documentation for market creators, capital providers, keeper operators, and protocol integrators.
Deployed Programs
Live on Solana devnet. Click any address to view on explorer.
LP deposits, share accounting, reserve management
Four-factor composite risk scoring, on-chain
Epoch-based fee routing and yield distribution
Deterministic waterfall payout on market insolvency
Introduction to PercoSec
PercoSec is a decentralized insurance infrastructure layer for permissionless perpetual markets on Solana. It solves a structural problem inherent to on-chain perp protocols: when a liquidation event results in a deficit - where a trader's losses exceed the available margin - someone must absorb that shortfall. In most protocols today, that burden falls on all liquidity providers through a mechanism called "socialized loss". PercoSec eliminates socialized loss by introducing a dedicated, risk-priced insurance backstop.
Core Concepts
Insurance Pools - Each perpetual market has a dedicated, isolated pool of USDC capital provided by liquidity providers (LPs). This capital acts as a backstop: if a market becomes insolvent, the pool covers the deficit. LPs earn yield (denominated in basis points APY) in exchange for providing this capital.
Risk Scoring - A four-factor composite risk score is computed on-chain for each market every epoch. The score reflects the current risk state of the market and drives premium rates. Higher-risk markets pay more to the insurance pool. The four factors are: leverage ratio, open interest to liquidity ratio, price volatility, and funding rate variance.
Premium Routing - A portion of each perpetual market's trading fees is routed to PercoSec as insurance premiums. The Premium Manager program collects these fees and distributes them to LP shareholders at the end of each epoch proportional to their share of the pool.
Insolvency Handling - When a market reports a deficit that exceeds its margin buffer, the Insolvency Handler is invoked. It executes a deterministic waterfall: first drawing from the market's isolated pool, then (in future phases) from shared and reinsurance layers. Payouts are on-chain and auditable.
Capital Flow
LP deposits USDC → InsurancePool mints LP shares to depositor → USDC held in pool PDA vault Perp market generates trading fees → PremiumManager collects protocol fee share → At epoch end: fees distributed pro-rata to LP shareholders Market insolvency event → InsolvencyHandler receives deficit report → Waterfall draws from InsurancePool vault → LP NAV decreases proportionally → Event emitted on-chain for transparency
Why Solana
PercoSec is built on Solana for three reasons: transaction throughput (400ms finality for keeper operations), low fees (keeper bots remain economically viable at scale), and Anchor's account model which makes per-market PDA isolation clean and auditable. The entire risk pipeline - from oracle read to premium settlement - executes on-chain.
Insurance Pool Program
The Insurance Pool Program is the capital custody and accounting layer of PercoSec. It manages LP deposits, mints share tokens, tracks pool utilization, and enforces reserve ratios and epoch lock periods. Each market has exactly one pool, derived as a PDA from the market address.
Account: InsurancePool
| Field | Type | Description |
|---|---|---|
| market | Pubkey | The perp market this pool covers |
| vault | Pubkey | Token account holding deposited USDC |
| total_deposits | u64 | Total USDC deposited (in lamports, 6 decimals) |
| total_shares | u64 | Total LP share tokens outstanding |
| utilization_rate_bps | u16 | Current utilization as basis points (0–10000) |
| min_reserve_bps | u16 | Minimum reserve ratio that must remain liquid |
| insolvency_threshold | u64 | USDC amount that triggers insolvency claim |
| epoch_duration | u64 | Number of slots per epoch |
| apy_bps | u16 | Current annualized yield in basis points |
| is_active | bool | Whether the pool is accepting new deposits |
Instructions
deposit(amount: u64) - Transfers USDC from the caller's wallet into the pool vault. Mints LP shares proportional to current NAV. NAV per share = (total_deposits - pending_claims) / total_shares. If this is the first deposit, 1 share = 1 USDC.
withdraw(shares: u64) - Burns the specified LP shares and returns proportional USDC. Enforces epoch lock: withdrawals are only permitted if the depositor's lock period has elapsed. If utilization is above min_reserve_bps, partial withdrawals up to the available liquid reserve are permitted.
cover_deficit(amount: u64) - Called only by the Insolvency Handler via CPI. Transfers USDC from the vault to cover a market deficit. Reduces total_deposits and recalculates NAV.
update_apy(apy_bps: u16) - Called by the Premium Manager after epoch settlement to update the recorded APY for UI display.
PDA Derivation
// Pool PDA seeds seeds = [ b"insurance_pool", market_address.as_ref() ] bump = canonical bump // Vault PDA (token account) seeds = [ b"pool_vault", insurance_pool.key().as_ref() ]
Reserve Enforcement
The pool always maintains a minimum liquid reserve defined by min_reserve_bps. For example, at 800 bps (8%), a pool with $100,000 USDC must keep $8,000 unlocked at all times. Withdrawals that would breach this floor are rejected. This protects against a bank-run scenario where LPs attempt to exit before an insolvency event.
Risk Engine Program
The Risk Engine Program computes a composite risk score for each market on-chain. The score ranges from 0.00 (minimal risk) to 1.00 (extreme risk) and is expressed internally as basis points (0–10000). It is the single source of truth for premium pricing and pool configuration.
Scoring Model
The composite score is a weighted sum of four normalized factors:
| Field | Type | Description |
|---|---|---|
| leverage_bps | u16 (weight 35%) | Average leverage of open positions. Higher leverage = higher liquidation cascade risk. |
| oi_ratio_bps | u16 (weight 25%) | Open interest divided by available liquidity. High OI/liquidity = thin buffer before insolvency. |
| volatility_bps | u16 (weight 20%) | Price volatility derived from oracle price history. Higher volatility = larger potential gaps. |
| funding_variance_bps | u16 (weight 20%) | Variance of the funding rate. Erratic funding signals imbalanced OI and stressed markets. |
composite_score = ( leverage_bps * 0.35 + oi_ratio_bps * 0.25 + volatility_bps * 0.20 + funding_variance_bps * 0.20 ) Grade bands: 0 – 3000 bps → Low (green) 3001 – 5500 bps → Moderate (yellow) 5501 – 7500 bps → High (orange) 7501 – 10000 bps → Extreme (red)
Account: RiskMetrics
| Field | Type | Description |
|---|---|---|
| market | Pubkey | Market this score belongs to |
| composite_score_bps | u16 | Final weighted composite score (0–10000) |
| leverage_bps | u16 | Raw leverage factor score |
| oi_ratio_bps | u16 | Raw OI/liquidity factor score |
| volatility_bps | u16 | Raw volatility factor score |
| funding_variance_bps | u16 | Raw funding variance factor score |
| grade | String | Human-readable grade: Low / Moderate / High / Extreme |
| open_interest | u64 | Total open interest in USDC at time of scoring |
| liquidity | u64 | Total available liquidity at time of scoring |
Instructions
update_score(market, oracle_data) - Reads current oracle prices and market state, recomputes all four factors, updates the RiskMetrics account, and emits a ScoreUpdated event. Called by keepers every epoch.
set_premium_rate(market) - Reads the latest composite score and writes the new premium rate to the PremiumManager. Called immediately after update_score by the same keeper transaction.
Insolvency Handler Program
The Insolvency Handler is the last line of defense. When a perpetual market reports a deficit - meaning liquidation proceeds were insufficient to cover a position's losses - this program executes a deterministic payout waterfall to make the market whole.
Trigger Conditions
An insolvency event is triggered when a keeper calls report_deficit with a deficit amount that exceeds the market's insolvency_threshold. Below the threshold, small deficits are absorbed by the market's own margin buffer. Above the threshold, the insurance pool is tapped.
Payout Waterfall (Phase 1)
1. Market margin buffer (held by perp market itself) ↓ if exhausted 2. Isolated Insurance Pool (PercoSec - per market) ↓ [Phase 2+] if exhausted 3. Cross-market shared pool ↓ [Phase 3+] if exhausted 4. Reinsurance tier (institutional backstop) ↓ if exhausted 5. Socialized loss (last resort - prevented in normal operation)
In Phase 1 (current), only the isolated pool exists. The handler calls cover_deficit on the InsurancePool via CPI, transferring USDC from the pool vault to the market. If the pool vault has insufficient funds, the remaining deficit is flagged as an UncoveredDeficit event - it does not silently socialize.
Instructions
report_deficit(market, deficit_amount) - Called by a keeper or the perp market itself. Validates the deficit against the threshold, then initiates the waterfall. Emits InsolvencyEvent with full breakdown.
verify_deficit(market) - Read-only instruction that keepers use to check whether a market currently has a reportable deficit before submitting the full transaction.
Account: InsolvencyRecord
| Field | Type | Description |
|---|---|---|
| market | Pubkey | Market where insolvency occurred |
| deficit_amount | u64 | Total deficit amount reported |
| covered_amount | u64 | Amount covered by insurance pool |
| uncovered_amount | u64 | Residual deficit not covered (should be 0) |
| timestamp | i64 | Unix timestamp of the event |
| slot | u64 | Slot at which payout executed |
Market Creation Guide
Creating a new insured perpetual market with PercoSec requires initializing an insurance pool, configuring the risk engine, and linking premium routing. This guide walks through each step.
Prerequisites
- A deployed perp market program on Solana devnet or mainnet
- A funded deployer keypair with SOL for account rent
- The PercoSec program IDs set in your environment
- Anchor CLI and
@coral-xyz/anchorinstalled
Step 1 - Initialize the Insurance Pool
import { Program, AnchorProvider } from "@coral-xyz/anchor";
import { PublicKey } from "@solana/web3.js";
const INSURANCE_POOL_PROGRAM_ID = new PublicKey(
"6qaEQCxNtrJLjPE8tepcsJykC1KWzoiGeo3UqdYEs8Bk"
);
// Derive the pool PDA
const [poolPda] = PublicKey.findProgramAddressSync(
[Buffer.from("insurance_pool"), marketAddress.toBuffer()],
INSURANCE_POOL_PROGRAM_ID
);
await program.methods
.initializePool({
minReserveBps: 800, // 8% minimum reserve
insolvencyThreshold: 50_000_000_000, // 50,000 USDC (6 decimals)
epochDuration: 100, // epochs in slots
})
.accounts({
pool: poolPda,
market: marketAddress,
authority: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
})
.rpc();Step 2 - Initialize Risk Metrics
const RISK_ENGINE_PROGRAM_ID = new PublicKey(
"9Kg5ThbWTJh4UqGRB2aW9Ru6B6EpbA7B5SxGsB4hvcC3"
);
const [riskPda] = PublicKey.findProgramAddressSync(
[Buffer.from("risk_metrics"), marketAddress.toBuffer()],
RISK_ENGINE_PROGRAM_ID
);
await riskEngineProgram.methods
.initializeRiskMetrics()
.accounts({
riskMetrics: riskPda,
market: marketAddress,
authority: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
})
.rpc();Step 3 - Configure Premium Routing
const PREMIUM_MANAGER_ID = new PublicKey(
"ucnc5EhhEUDjztqUdfLhb3bCrP3MWFUVZKYZULzYYRE"
);
await premiumManagerProgram.methods
.initializePremiumState({
epochDuration: 100,
})
.accounts({
premiumState: premiumStatePda,
pool: poolPda,
market: marketAddress,
authority: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
})
.rpc();Step 4 - Verify Setup
After initialization, confirm all accounts are live on explorer:
- Pool PDA account exists and
is_active = true - RiskMetrics account exists with initial scores at 0
- PremiumState account linked to correct pool
- Run the first keeper tick to compute an initial risk score
Capital Provider Integration
Capital providers deposit USDC into insurance pools and earn yield denominated as APY. This guide covers depositing, tracking your position, and managing withdrawals.
Depositing Capital
import { getAssociatedTokenAddress } from "@solana/spl-token";
const USDC_MINT = new PublicKey(process.env.NEXT_PUBLIC_COLLATERAL_MINT);
const amount = 1_000_000_000; // 1,000 USDC (6 decimals)
// Get LP's USDC token account
const depositorUsdcAccount = await getAssociatedTokenAddress(
USDC_MINT,
wallet.publicKey
);
// Get pool vault address
const [poolVault] = PublicKey.findProgramAddressSync(
[Buffer.from("pool_vault"), poolPda.toBuffer()],
INSURANCE_POOL_PROGRAM_ID
);
await insurancePoolProgram.methods
.deposit(new BN(amount))
.accounts({
pool: poolPda,
vault: poolVault,
depositorTokenAccount: depositorUsdcAccount,
depositor: wallet.publicKey,
tokenProgram: TOKEN_PROGRAM_ID,
})
.rpc();Reading Your Position
// Fetch pool state
const pool = await insurancePoolProgram.account.insurancePool.fetch(poolPda);
// Your LP share balance (from your share token account)
const shareBalance = await connection.getTokenAccountBalance(yourShareAccount);
// NAV per share calculation
const navPerShare = pool.totalDeposits.toNumber() / pool.totalShares.toNumber();
// Your USDC value
const yourUsdcValue = shareBalance.value.uiAmount * navPerShare;
console.log({
shares: shareBalance.value.uiAmount,
navPerShare,
usdcValue: yourUsdcValue,
apyBps: pool.apyBps,
utilizationBps: pool.utilizationRateBps,
});Withdrawing Capital
Withdrawals are subject to the epoch lock period. If you deposited in epoch N, you can withdraw starting epoch N + lock_duration. The UI shows your unlock timestamp. Partial withdrawals are allowed as long as the pool maintains its minimum reserve ratio after the withdrawal.
const sharesToRedeem = new BN(500_000_000); // 500 shares
await insurancePoolProgram.methods
.withdraw(sharesToRedeem)
.accounts({
pool: poolPda,
vault: poolVault,
depositorShareAccount: yourShareAccount,
depositorTokenAccount: depositorUsdcAccount,
depositor: wallet.publicKey,
tokenProgram: TOKEN_PROGRAM_ID,
})
.rpc();Risk Considerations
- LP capital can be reduced by insolvency events - NAV decreases proportionally
- High utilization markets carry higher risk of a claim reducing your position
- Lock periods prevent immediate exit - plan your liquidity accordingly
- Higher risk grade markets offer higher APY to compensate for higher expected loss
Keeper Bot Setup
Keeper bots are permissionless off-chain processes that trigger on-chain instructions at the right time. PercoSec requires keepers for: epoch settlement, risk score updates, and deficit reporting. Anyone can run a keeper - the first bot to submit a valid keeper instruction earns the keeper fee.
Keeper Responsibilities
| Field | Type | Description |
|---|---|---|
| update_score | Every epoch | Call RiskEngine.update_score for each active market. Reads oracle feeds and recomputes the composite score. |
| set_premium_rate | After update_score | Call PremiumManager.set_premium_rate to update the active premium rate based on the new score. |
| settle_epoch | End of each epoch | Call PremiumManager.settle_epoch to distribute accumulated premiums to LP shareholders. |
| report_deficit | When deficit detected | Monitor market accounts for deficit conditions. Call InsolvencyHandler.report_deficit if threshold exceeded. |
Environment Setup
# Install dependencies npm install @coral-xyz/anchor @solana/web3.js @solana/spl-token dotenv # .env for keeper SOLANA_RPC_URL=https://api.devnet.solana.com KEEPER_KEYPAIR_PATH=/path/to/keeper.json INSURANCE_POOL_PROGRAM_ID=6qaEQCxNtrJLjPE8tepcsJykC1KWzoiGeo3UqdYEs8Bk RISK_ENGINE_PROGRAM_ID=9Kg5ThbWTJh4UqGRB2aW9Ru6B6EpbA7B5SxGsB4hvcC3 PREMIUM_MANAGER_PROGRAM_ID=ucnc5EhhEUDjztqUdfLhb3bCrP3MWFUVZKYZULzYYRE INSOLVENCY_HANDLER_PROGRAM_ID=5Gy6MyuJ8uyyK2ySsHQ8e8k27BMr848tY8iC71ymW13T
Keeper Loop (TypeScript)
import { Connection, Keypair } from "@solana/web3.js";
import * as fs from "fs";
const EPOCH_SLOTS = 100;
async function keeperLoop() {
const connection = new Connection(process.env.SOLANA_RPC_URL!);
const keypair = Keypair.fromSecretKey(
Uint8Array.from(JSON.parse(fs.readFileSync(process.env.KEEPER_KEYPAIR_PATH!).toString()))
);
let lastEpoch = 0;
while (true) {
const slot = await connection.getSlot();
const currentEpoch = Math.floor(slot / EPOCH_SLOTS);
if (currentEpoch > lastEpoch) {
console.log(`Epoch ${currentEpoch} - running keeper tasks`);
for (const market of ACTIVE_MARKETS) {
await updateRiskScore(market, keypair);
await setPremiumRate(market, keypair);
if (currentEpoch % 1 === 0) {
await settleEpoch(market, keypair);
}
await checkAndReportDeficit(market, keypair);
}
lastEpoch = currentEpoch;
}
await sleep(2000); // poll every 2 seconds
}
}
keeperLoop().catch(console.error);Keeper Economics
Each keeper instruction that successfully executes earns a small fee from the protocol treasury. The fee is sized to cover transaction costs plus a margin. Running a keeper is permissionless - no registration required. If your keeper is too slow and another bot submits first, the transaction will fail (account state already updated) and you pay only the failed transaction fee (~0.000005 SOL).
Indexer API Reference
The PercoSec indexer processes on-chain events and exposes them via a REST API and WebSocket subscriptions. The frontend uses Supabase as the indexed data store. Direct Supabase queries are the primary integration method.
Markets Endpoint
// GET /rest/v1/markets
// Returns all active markets with pool and risk data
SELECT
markets.*,
insurance_pools!market_id(*),
risk_metrics!market_id(*),
apy_snapshots!market_id(apy_bps, recorded_at)
FROM markets
WHERE active = true
ORDER BY created_at DESC;
// Response shape
{
id: string,
name: string, // "BTC-PERP"
base_token: string, // "BTC"
quote_token: string, // "USDC"
oracle_feed: string, // "BTC/USD"
max_leverage: number,
insurance_pools: [{
total_deposits_usdc: number,
utilization_rate_bps: number,
apy_bps: number,
is_active: boolean,
}],
risk_metrics: [{
composite_score_bps: number,
grade: "Low" | "Moderate" | "High" | "Extreme",
open_interest: number,
liquidity: number,
}]
}Oracle Snapshots
// GET /rest/v1/oracle_snapshots?market_id=eq.{id}
{
market_id: string,
price_usd: number,
confidence_interval_usd: number,
slot: number,
is_stale: boolean,
recorded_at: string // ISO timestamp
}APY History
// GET /rest/v1/apy_snapshots?pool_id=eq.{id}&order=recorded_at.desc&limit=30
{
pool_id: string,
market_id: string,
apy_bps: number,
premiums_collected_7d: number,
recorded_at: string
}WebSocket - Real-time Updates
import { createClient } from "@supabase/supabase-js";
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
// Subscribe to risk metric changes
const channel = supabase
.channel("risk-updates")
.on(
"postgres_changes",
{ event: "UPDATE", schema: "public", table: "risk_metrics" },
(payload) => {
console.log("Risk score updated:", payload.new);
}
)
.subscribe();Audit Reports
PercoSec is currently in active development on devnet. Formal third-party security audits are scheduled prior to mainnet deployment. The audit process will cover all four Anchor programs, the premium calculation logic, and the insolvency waterfall.
Audit Status
| Field | Type | Description |
|---|---|---|
| Insurance Pool Program | Pending | Scheduled before mainnet. Scope: deposit/withdraw logic, share accounting, PDA custody. |
| Risk Engine Program | Pending | Scheduled before mainnet. Scope: score computation, oracle integration, overflow safety. |
| Premium Manager Program | Pending | Scheduled before mainnet. Scope: epoch settlement, fee distribution, rounding safety. |
| Insolvency Handler Program | Pending | Scheduled before mainnet. Scope: waterfall logic, CPI permissions, deficit accounting. |
Security Principles
- All programs are written in Anchor with full account constraint validation
- No unchecked arithmetic - all math uses checked operations
- CPI calls are authority-gated - only InsolvencyHandler can call cover_deficit
- No admin upgrade key on mainnet - programs will be immutable post-audit
- All state transitions emit events for off-chain monitoring
Bug Bounty Program
PercoSec runs a bug bounty program for responsible disclosure of security vulnerabilities. Rewards are paid in USDC based on severity. The bounty is active for devnet and will expand to cover mainnet contracts upon deployment.
Severity Tiers
| Field | Type | Description |
|---|---|---|
| Critical | Up to $50,000 USDC | Direct loss of LP funds, unauthorized pool withdrawal, insolvency handler bypass, arithmetic overflow leading to fund extraction. |
| High | Up to $10,000 USDC | Incorrect premium distribution, share accounting errors, unauthorized keeper instruction execution, PDA collision. |
| Medium | Up to $2,500 USDC | Logic errors in risk scoring that materially misrepresent market risk, epoch settlement inconsistencies. |
| Low | Up to $500 USDC | Minor UI inconsistencies with on-chain data, edge case rounding in fee calculations, informational findings. |
Scope
In scope:
- All four Anchor programs (Insurance Pool, Risk Engine, Premium Manager, Insolvency Handler)
- PDA derivation and account constraint logic
- Cross-program invocation authority checks
- Premium calculation and epoch settlement math
Out of scope:
- Frontend UI bugs (unless they expose a smart contract vulnerability)
- Denial of service via transaction spam
- Issues requiring a compromised admin keypair
- Third-party oracle failures (Pyth)
Submission Process
To submit a vulnerability, contact the PercoSec team through the official communication channels listed in the documentation. Include a clear description of the vulnerability, steps to reproduce, proof-of-concept code if applicable, and your assessment of impact and severity. Do not publicly disclose vulnerabilities before the team has had 7 days to respond. Responsible disclosures that follow this process are eligible for the full bounty reward.