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
| Program | Instruction | Pays what |
|---|
| kimia-perp | update_funding_rate | Nothing direct; rate-setter sees the fresh cumulative |
| kimia-perp | settle_funding | Nothing direct |
| kimia-perp | liquidate | 2.5% of closed notional |
| delta-vault | rebalance | Nothing direct (you keep the protocol healthy) |
| delta-vault | claim_funding | Nothing direct |
| split-engine | update_rewards | Nothing 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.