Skip to main content
Several Kimia operations are permissionless cranks. Running a keeper bot requires no whitelist, and you can earn by being first to liquidate or by layering your own fee structure on funding updates.

Cranks on offer

ProgramInstructionPays what
kimia-perpupdate_funding_rateNothing direct; rate-setter sees the fresh cumulative
kimia-perpsettle_fundingNothing direct
kimia-perpliquidate2.5% of closed notional
delta-vaultrebalanceNothing direct (you keep the protocol healthy)
delta-vaultclaim_fundingNothing direct
split-engineupdate_rewardsNothing direct

Minimal Node bot

import { createSolanaRpc, createSolanaRpcSubscriptions } from '@solana/kit';

const rpc = createSolanaRpc(process.env.RPC_URL!);

async function tickFunding() {
  const market = await fetchMarket(rpc, marketPda);
  const now = Math.floor(Date.now() / 1000);
  if (now - Number(market.lastFundingUpdate) >= Number(market.fundingPeriod)) {
    const postIxs = await fetchAndBuildOraclePost(SOL_USD_FEED_ID);
    const ix = getUpdateFundingRateInstruction({
      keeper: wallet,
      market: marketPda,
      oracle: priceUpdate,
      orderbook: orderbookPda,
    });
    await sendTransaction([...postIxs, ix]);
  }
}

setInterval(tickFunding, 60_000);

Finding liquidatable users

Subscribe to program accounts for kimia-perp::UserAccount, read base_amount, collateral, market_index, and compute health off-chain with the perps-math model. Then call liquidate:
const ix = getLiquidateInstruction({
  liquidator: wallet,
  liquidatorTokenAccount: wsolAta,     // receives fee
  userAccount: victim,
  market: marketPda,
  oracle: priceUpdate,
  collateralVault: market.collateralVault,
  insuranceVault: market.insuranceVault,
  orderbook: orderbookPda,
});
liquidate touches the orderbook (to cancel the victim’s resting orders) and reads the oracle. Always prepend a fresh oracle post, a stale price will revert with StaleOracle.

Rebalance the vault

import { getRebalanceInstruction } from '@/app/generated/delta-vault/instructions';

const vaultState = await fetchVault(rpc, vault);
if (abs(spotLegValue - perpLegValue) * 10_000 / totalValue >
    vaultState.rebalanceThresholdBps) {
  await sendTransaction([
    ...oraclePostIxs,
    getRebalanceInstruction({ /* ... */ }),
  ]);
}

Operational tips

  • RPC quality matters. Liquidation races reward low-latency RPCs and tightly-controlled transaction priority fees.
  • Sync reward_per_share before claiming. For vault integrators, calling update_rewards before claim_yield maximizes the user’s payout.
  • Don’t assume idempotency. update_funding_rate enforces elapsed ≥ funding_period; wasted calls just fail silently with FundingTooEarly.