Keeper Execution
The keeper system enables permissionless execution of limit order fills, stop-loss/take-profit triggers, and liquidations. Any address can call the execute function and earn fees for performing these actions.
Batch Processing
execute(caller: Address, market_id: u32, position_ids: Vec<u32>, price: Bytes)
The execute function processes a batch of position IDs for a single market in one transaction. The price payload is verified once via the price verifier, and a Context is loaded for the specified market_id, which accrues borrowing and funding indices to the current timestamp. The contract must not be Frozen.
All positions in the batch must belong to the same market_id. If any position fails its action (not actionable, too new, wrong market), the entire batch aborts with a hard panic. There is no partial success — either all positions are processed or none are.
Context
The Context bundles the verified price (single feed), price scalar, market config and data, global trading config, vault balance, token/vault/treasury addresses, and total notional. It is loaded once at the start of the batch and auto-accrues indices on construction. After all positions are processed, mutated state is written back via ctx.store().
Auto-Detection
The execute function auto-detects the action for each position based on its state:
- Not filled (pending limit order) → attempt fill. For longs, fills when
price <= entry_price; for shorts, fills whenprice >= entry_price. If the fill condition is not met, panics withNotActionable. - Filled (active position) → checks in priority order:
- Liquidation: equity < liquidation threshold (
notional * liq_fee). BypassesMIN_OPEN_TIME. - Stop-loss: trigger price hit. Requires
MIN_OPEN_TIME. - Take-profit: trigger price hit. Requires
MIN_OPEN_TIME. - If none apply, panics with
NotActionable.
- Liquidation: equity < liquidation threshold (
This simplifies keeper logic — keepers only need to submit position IDs and price data for a single market.
Error Handling
All errors are hard panics that abort the entire batch. There is no soft error or per-position result vector.
| Error | Code | Meaning |
|---|---|---|
NotActionable | 731 | No valid action for this position (limit not fillable, not liquidatable, no SL/TP triggered) |
PositionTooNew | 732 | MIN_OPEN_TIME (30s) not elapsed — SL/TP cannot fire yet |
PositionNotPending | 721 | Position is already filled but was expected to be pending |
InvalidPrice | 710 | Position's market_id does not match the batch's market_id, or price feed mismatch |
ContractFrozen | 742 | Contract is in Frozen state, all operations blocked |
StalePrice | 711 | Liquidation price predates position open time |
Settlement Ordering
The batch aggregates all transfers into a map (Address -> i128). Settlement follows a specific order:
- Vault pays first: if the vault's net transfer is negative (vault owes money),
strategy_withdrawis called to bring tokens into the trading contract. - Outbound transfers: all positive transfers to non-vault addresses (keeper fees, user payouts, treasury fees) are paid.
- Vault receives last: if the vault's net transfer is positive (vault gains), tokens are transferred to the vault.
This ordering prevents balance shortfalls within the trading contract during batch settlement.
Keeper Fee
Keepers earn a percentage of trading fees (base + impact) on each successful action:
Where trading_fee = base_fee + impact_fee. The caller fee is deducted from the vault's share, not from the user. On liquidation, caller_fee = min(trading_fee + liq_fee, col) * caller_rate / SCALAR_7.
No Authentication Required
The execute function does not require the caller to authenticate. Any address can submit keeper requests and receive the caller_rate percentage of fees. This is an intentional design choice that creates a competitive, permissionless keeper network where anyone can participate.
The caller address parameter determines who receives the keeper fee. The caller does not need to be related to the position owner.