Shared venue runners
This layer holds the generic flow logic that the Orca and Raydium instruction handlers share, and the traits that the venue adapters implement.
The layer is programs/sickle/src/venue/ without the adapters/ directory.
Every runner is a generic function over one adapter trait. The handler passes
its concrete Anchor account struct, and the runner calls the adapter methods in
a fixed order. The runner owns the order of checks, the fee class, the receipt
validation, and the event fields. The adapter owns the venue account grammar and
the venue CPI.
Files
| Path | Lines | Purpose |
|---|---|---|
programs/sickle/src/venue/mod.rs | 22 | Declares the modules. compound is private. adapters is #[doc(hidden)]. |
programs/sickle/src/venue/policy.rs | 160 | The fee table: one FeePolicy per PositionOperation. |
programs/sickle/src/venue/clmm.rs | 359 | The CLMM manifest, the marker traits, the receipt primitives, and the Orca and Raydium venue tags. |
programs/sickle/src/venue/dlmm.rs | 121 | The DLMM manifest and the Meteora marker impls. |
programs/sickle/src/venue/family.rs | 314 | The venue registry macro, VenueFamily, PositionGeometry, and Protocol::position_geometry. |
programs/sickle/src/venue/keeper.rs | 167 | Helpers shared by keeper rebalance and keeper exit: intent prerequisites and ledger credit. |
programs/sickle/src/venue/open_increase.rs | 538 | run_open and run_increase. |
programs/sickle/src/venue/decrease_close.rs | 694 | run_decrease and run_close. |
programs/sickle/src/venue/claim.rs | 1126 | run_collect_fees, run_collect_rewards, run_owner_all_yield, run_keeper_harvest, run_compound, and the shared keeper-claim authorization. |
programs/sickle/src/venue/compound.rs | 408 | Three CompoundFlowPolicy impls: Orca, Raydium, Meteora. |
programs/sickle/src/venue/owner_rebalance.rs | 899 | run_owner_rebalance and the receipt, rent, and event helpers that the keeper runners reuse. |
programs/sickle/src/venue/keeper_rebalance.rs | 520 | run_keeper_rebalance. |
programs/sickle/src/venue/keeper_exit.rs | 504 | run_keeper_exit. |
Another layer page covers the PositionGeometry tick and bin limits in family.rs. This
page covers only its trait registry.
Responsibilities
- Select the fee class for each position operation from one table
(
policy.rs:32-87). - Check the pause flag and the allowlist for open, increase, rebalance, and
exit flows (
open_increase.rs:37-52,owner_rebalance.rs:438,keeper_rebalance.rs:235,keeper_exit.rs:244-249). - Check the caller role for open, increase, owner rebalance, keeper rebalance,
and keeper exit (
open_increase.rs:164-167,open_increase.rs:253-318,owner_rebalance.rs:439-442,keeper_rebalance.rs:254-261,keeper_exit.rs:265-272). - Require the authorization and action accounts for every keeper caller that
is not the owner (
keeper.rs:36-48). - Create the action ledger, snapshot vault baselines, and credit measured
amounts for keeper rebalance, keeper exit, and compound
(
keeper_rebalance.rs:110-188,keeper_exit.rs:114-196,keeper.rs:70-82,compound.rs:84-408). - Validate every receipt that an adapter returns against the identity the
adapter exposed before the CPI (
open_increase.rs:102-151,decrease_close.rs:114-157,decrease_close.rs:250-268,owner_rebalance.rs:189-231). - Check that the venue closed all three of its accounts after a close
(
owner_rebalance.rs:233-241). - Measure the rent the venue returned and transfer it to the owner
(
decrease_close.rs:270-316,owner_rebalance.rs:267-296). - Carry position settings from a closed position to a reopened one
(
owner_rebalance.rs:514-540,keeper_rebalance.rs:190-221,319-329). - Apply the live trigger, debounce, placement, and progress checks on a keeper
rebalance (
keeper_rebalance.rs:295-317) and the live exit trigger on a keeper exit (keeper_exit.rs:301-303). - Map each measured receipt into the event structs the handler emits
(
open_increase.rs:177-184,open_increase.rs:355-370,decrease_close.rs:178-187,decrease_close.rs:362-387,owner_rebalance.rs:310-421,keeper_exit.rs:198-227). - Prove at compile time that each venue tag implements every runner trait
(
clmm.rs:230-242,dlmm.rs:79-91,family.rs:124-180).
Public items
policy.rs
| Item | Kind | Meaning |
|---|---|---|
PositionOperation | enum | Eight operations: CollectFees, CollectRewards, Harvest, Compound, Decrease, Close, Rebalance, Exit. |
FeePolicy | struct | Three Option<FeeAction> fields: principal, pool_fees, rewards. |
position_fee_policy | const fn | The exhaustive match from operation to FeePolicy (policy.rs:32-87). |
required_action | fn | Run-time read of a class the policy declares mandatory. Error InvalidProtocolAccountData. |
The fee table is the single source for every fee class in this layer.
| Operation | principal | pool_fees | rewards |
|---|---|---|---|
| CollectFees | none | Harvest | none |
| CollectRewards | none | none | Harvest |
| Harvest | none | Harvest | Harvest |
| Compound | none | Compound | Compound |
| Decrease | none | none | none |
| Close | none | none | Harvest |
| Rebalance | Rebalance | Harvest | Harvest |
| Exit | Exit | Harvest | Harvest |
keeper.rs
| Item | Kind | Meaning |
|---|---|---|
KeeperAsset | struct | One signed pool asset: mint, vault, baseline balance. |
KeeperIntentAccounts | struct | The payer, Instructions sysvar, optional authorization, session, action, and System program. |
require_intent_prerequisites | pub(crate) fn | A non-owner needs both the authorization and the action account, else IntentRequired. |
exact_net_drain_credit | pub(crate) fn | principal.received + pool_fees.received - principal.fee - pool_fees.fee for one side. |
measured_pool_withdrawal_credit | pub(crate) fn | The two gross principal.received values. |
credit_keeper_drain_intent | pub(crate) fn | Verifies gross withdrawal floors, then credits exact post-fee principal and trading yield. |
clmm.rs
| Item | Kind | Meaning |
|---|---|---|
OrcaVenue, RaydiumVenue | enum | Empty venue tags. Each implements ClmmVenueManifest. |
YieldScope, AllYield, TradingFeesOnly | trait, enum | A sealed scope tag. AllYield means fees plus every reward slot. |
YieldClaimSurface | trait | Declares type Scope: YieldScope for one account struct. |
CompleteYieldSurface | trait | Blanket impl for every YieldClaimSurface<Scope = AllYield> (clmm.rs:70). |
OpenFlowSurface, IncreaseFlowSurface, DecreaseFlowSurface, CloseFlowSurface | trait | Blanket impls over the matching adapter trait (clmm.rs:72-80, :152-160). |
ClaimFlowCapability, the three claim proof tokens, and their capability classes | trait, enum | Sealed adapter evidence for dedicated fee, dedicated reward, or combined all-yield claims (clmm.rs:82-138). |
FeeClaimFlowSurface, RewardClaimFlowSurface | trait | Each names a proof token that is valid only when its concrete account type implements the corresponding executable adapter (clmm.rs:140-150). |
HarvestOperation, CompoundOperation, RebalanceOperation, AutomatedRebalanceOperation, ExitOperation | trait | Empty marker traits that name the operation (clmm.rs:162-166). |
HarvestFlowSurface, CompoundFlowSurface, RebalanceFlowSurface, RebalanceForFlowSurface, ExitForFlowSurface | trait | Blanket impls over operation marker + CompleteYieldSurface + adapter trait (clmm.rs:178-223). |
ClmmVenueManifest | trait | Eleven associated types, one per instruction (clmm.rs:230-242). |
MAX_VENUE_REWARDS | const | Reward slot cap used by every receipt validator. |
IndexRange | struct | lower, upper as i32. |
PositionSnapshot | struct | pool, range, has_liquidity. |
RawAssetDelta | struct | mint, received. |
AssetDelta | struct | mint, received, fee. |
| Limit | Value |
|---|---|
MAX_VENUE_REWARDS | 3 |
dlmm.rs
| Item | Kind | Meaning |
|---|---|---|
DlmmOpenFlowSurface, DlmmResizeFlowSurface, DlmmAddFlowSurface, DlmmRemoveFlowSurface, DlmmFeeClaimFlowSurface, DlmmRewardClaimFlowSurface, DlmmCloseFlowSurface | trait | Empty marker traits for the seven Meteora owner instructions (dlmm.rs:22-28). |
DlmmHarvestFlowSurface | trait | Blanket impl: HarvestOperation + CompleteYieldSurface + ClaimAllYieldAdapter (dlmm.rs:33-40). |
DlmmCompoundFlowSurface | trait | Blanket impl: CompoundOperation + CompleteYieldSurface + CompoundFlowPolicy (dlmm.rs:44-51). |
DlmmKeeperRebalanceFlowAdapter, DlmmKeeperExitFlowAdapter | trait | Empty marker traits. Implemented once each, for MeteoraRebalanceFor and MeteoraExitFor (dlmm.rs:57, dlmm.rs:69). |
DlmmRebalanceForFlowSurface, DlmmExitForFlowSurface | trait | Blanket impls over the two marker traits above. |
DlmmVenueManifest | trait | Eleven associated types (dlmm.rs:79-91). Implemented for MeteoraDlmmVenue (dlmm.rs:109-121). |
family.rs
| Item | Kind | Meaning |
|---|---|---|
VenueFamily | enum | Clmm, Dlmm, NonPosition. |
PositionGeometry | enum | Clmm { min_tick_index, max_tick_index } or Dlmm { min_bin_id, max_bin_id }. |
Conformance | enum | Registered, NotApplicable. |
MeteoraDlmmVenue | enum | Empty venue tag. Implements DlmmVenueManifest, not ClmmVenueManifest. |
DlmmVenue | trait | Sealed. const PROTOCOL: Protocol. |
RegisteredClmmVenue | trait | Sealed. Requires ClmmVenueManifest. const PROTOCOL. |
RegisteredDlmmVenue | trait | Sealed. Requires DlmmVenue + DlmmVenueManifest. |
protocol_family, protocol_conformance | const fn | Generated by venue_registry!. No wildcard arm, so a new Protocol variant fails the build. |
Protocol::family | const fn | Calls protocol_family. |
Protocol::position_geometry | fn | Resolves the tick or bin limits for one venue. Covered by another layer page. |
open_increase.rs
| Item | Kind | Meaning |
|---|---|---|
ActiveFlowIdentity | struct | authority, sickle, protocol, protocol_program. |
ActiveFlowPolicy | trait | active_flow_identity, global_config, sickle_account. Supertrait of every flow adapter but claim. |
OpenRequest<V> | struct | range plus venue fields. |
OpenIdentity | struct | Flow identity plus pool, position, position mint, position token account, and its token program. |
RawOpenFlowReceipt | struct | Post-CPI snapshot: keys, owners, range, liquidity flag, NFT balance. |
OpenRunReceipt | struct | raw plus the PositionOpened event. |
OpenFlowAdapter | trait | VenueRequest, Prepared, open_identity, prepare_open, execute_open. |
validate_open_receipt | pub(crate) fn | Nine checks on the receipt (open_increase.rs:102-151). Reused by both rebalance runners. |
run_open | fn | The open runner (open_increase.rs:156-186). |
IncreaseRequest<V> | struct | liquidity, maximum_tokens, venue fields. |
RawDepositDelta | struct | mint, debited. |
RawIncreaseFlowReceipt | struct | Two RawDepositDelta values. |
IncreaseIdentity | struct | Flow identity plus pool, position, two mints, two source accounts. |
IncreaseRunReceipt | struct | raw, the LiquidityIncreased event, intent_mode. |
IncreaseFlowAdapter | trait | Identity, optional global automator, optional settings, optional action, source infos, prepare, execute. |
run_increase | fn | The increase runner (open_increase.rs:389-420). |
| shared and private helpers | fn | validate_active_flow is pub(crate), called by the owner and keeper rebalance runners. Private: validate_increase_authority, source_balances, measured_increase_receipt, map_increase_event, apply_intent_debits. |
decrease_close.rs
| Item | Kind | Meaning |
|---|---|---|
DecreaseRequest<V> | struct | liquidity, minimum_tokens, venue fields. |
PoolDecreaseComposition | enum | PrincipalOnly or PrincipalAndIncidentalYield. |
IncidentalRewardDisposition | enum | Measured or IncludedInPoolToken { side }. Defined in claim.rs and re-exported here, because a claim receipt needs it too. |
RawIncidentalRewardDelta | struct | mint, received, disposition. |
RawDecreaseFlowReceipt | struct | Two pool deltas, the composition, a reward vector. |
DecreaseIdentity | struct | sickle, protocol, pool, position, two mints. |
DecreaseRunReceipt | struct | raw, the LiquidityDecreased event, the FeePolicy. |
DecreaseFlowAdapter | trait | decrease_identity, prepare_decrease, execute_decrease. No ActiveFlowPolicy supertrait. |
run_decrease | fn | The decrease runner (decrease_close.rs:159-188). |
CloseIdentity | struct | owner, sickle, authority, protocol, position, fee_collector. |
CloseRentAccounts | struct | source and receiver account infos. |
CloseYieldReceipt | struct | expected_mints and a RewardClaimDelta vector. |
CloseFlowAdapter | trait | Identity, rent accounts, settings candidate, three closed accounts, prepare, settle_close_yield, close_empty_position. |
CloseRunReceipt | struct | PositionClosed, reward events, fee events, optional SettingsUpdated, recovered rent, policy. |
run_close | fn | The close runner (decrease_close.rs:318-401). |
| private helpers | fn | validate_decrease_receipt, validate_close_yield, dispose_protocol_rent. Compile-time policy constants pin Decrease and Close; validate_closed_accounts comes from owner_rebalance.rs. |
claim.rs
| Item | Kind | Meaning |
|---|---|---|
YieldFeeActions | struct | pool_fees and rewards fee actions. |
PoolFeeClaimReceipt | struct | collected[2], fees[2]. |
RewardClaimDelta | struct | mint, collected, fee, disposition. collected is exact only when the disposition is Measured. |
RewardClaimReceipt | struct | A reward vector. Used only by the Orca collect-rewards adapter. |
AllYieldClaimReceipt | struct | pool plus a reward vector. |
COLLECT_FEES_ACTION, COLLECT_REWARDS_ACTION, HARVEST_FEE_ACTIONS, COMPOUND_FEE_ACTIONS | const | Fee classes read from the table at compile time (claim.rs:158-163). |
ClaimRunReceipt<R>, YieldRunReceipt<R> | struct | The adapter receipt plus the action or actions the runner selected. |
CollectTradingFeesAdapter | trait | collect_trading_fees(request, action, remaining). One implementor: OrcaCollectFees. |
CollectRewardsAdapter | trait | collect_rewards(request, action, remaining). One implementor: OrcaCollectRewards. |
ClaimAllYieldAdapter | trait | prepare_all_yield returns a tail. claim_all_yield runs the CPIs. |
CompoundFlowPolicy | trait | Supertrait ClaimAllYieldAdapter. validate_compound_prerequisites, begin_compound_claim, finish_compound_claim. |
run_collect_fees, run_collect_rewards, run_owner_all_yield, run_keeper_harvest, run_compound | fn | The five public claim runners (claim.rs:505-628). The all-yield body they share is private (claim.rs:547-562). |
required_action | pub(crate) const fn | Reads a class the policy declares mandatory, and panics at compile time if it is absent. Imported by the rebalance and exit runners. |
| private helpers | const fn | yield_fee_actions. Private to this file despite sitting beside the shared helper above. |
compound.rs
This file has no public items. It holds three CompoundFlowPolicy impls:
MeteoraCompoundFor (compound.rs:84-203), RaydiumCompoundFor
(compound.rs:205-300), and CompoundFor (compound.rs:302-408). Each impl
sets type State = Option<Box<ActionIntent>>.
owner_rebalance.rs
| Item | Kind | Meaning |
|---|---|---|
OwnerRebalanceRequest<V> | struct | new_range, minimum_tokens, venue fields. |
OwnerRebalanceIdentity | struct | Flow identity, owner, pool, old and new position keys, new mint and token account, mints, collector. |
PositionDrainIdentity | struct | The subset without reopen fields. From<OwnerRebalanceIdentity> (owner_rebalance.rs:65-76). |
DrainFeeActions | struct | principal, pool_fees, rewards fee actions. |
RebalanceRewardDisposition | enum | Measured or IncludedInPoolToken { side }. |
RebalanceRewardDelta | struct | AssetDelta plus disposition. |
RawDrainFlowReceipt | struct | Old snapshot, principal[2], pool_fees[2], expected reward mints, reward vector. |
DrainRentAccounts | struct | source, receiver. |
RebalanceRunReceipt | struct | FeesCollected, pool fee events, reward events, Rebalanced, PositionOpened, settings, rent, policy. |
OwnerRebalanceFlowAdapter | trait | Identity, settings candidate and mutable account, rent, closed accounts, prepare, spacing, bump, execute_drain_and_close, execute_reopen. |
DrainEventGroups | struct | The three event groups a drain produces: pool fees, rewards, and the principal. Returned by map_drain_events. |
fee_actions, validate_drain_receipt, validate_closed_accounts, validate_rent_accounts, measure_protocol_rent, dispose_protocol_rent, map_drain_events, map_events | pub(crate) fn | Helpers that keeper_rebalance.rs and keeper_exit.rs import. |
run_owner_rebalance | fn | The owner rebalance runner (owner_rebalance.rs:430-570). |
| private helpers | fn | validate_asset. required_action moved to policy.rs. |
keeper_rebalance.rs
| Item | Kind | Meaning |
|---|---|---|
KeeperRebalanceRequest<V> | struct | new_range, minimum_tokens, new_position_settings_bump, venue fields. |
KeeperRebalanceState | struct | old_position, old_liquidity, index_spacing, current_index. |
KeeperRebalanceFlowAdapter | trait | Identity, static account checks, automator, Sickle account, settings, settings pair, intent accounts, assets, rent, closed accounts, prepare, load state, execute_keeper_drain_and_close, execute_keeper_reopen. |
run_keeper_rebalance | fn | The keeper rebalance runner (keeper_rebalance.rs:227-375). Returns RebalanceRunReceipt. |
| shared and private helpers | fn | validate_owner_intent_tail is pub(crate), called by keeper exit and all three compound policies. Private: begin_keeper_rebalance_intent, transfer_keeper_rebalance_settings. |
keeper_exit.rs
| Item | Kind | Meaning |
|---|---|---|
KeeperExitRequest<V> | struct | minimum_tokens, venue fields. |
KeeperExitState | struct | position, liquidity, current_index. |
KeeperExitRunReceipt | struct | FeesCollected, pool fee events, reward events, Exited, settings, rent, policy. |
KeeperExitFlowAdapter | trait | Identity, static account checks, automator, Sickle account, settings, settings info, intent accounts, assets, rent, closed accounts, prepare, load state, execute_keeper_exit. |
select_exit_side_and_target | pub(crate) fn | Low side if the tick is below trigger_tick_low, else high side (keeper_exit.rs:101-110). Also used by instructions/meteora/exit_for.rs:27. |
run_keeper_exit | fn | The keeper exit runner (keeper_exit.rs:234-357). |
| private helpers | fn | begin_keeper_exit_intent, map_exit_events. |
Trait implementors
The table lists every production impl. Test fakes live in claim.rs:637-814
and decrease_close.rs:413-440.
| Trait | Orca account struct | Raydium account struct | Meteora account struct |
|---|---|---|---|
ActiveFlowPolicy | OrcaOpenPosition, OrcaIncreaseLiquidity, OrcaRebalancePosition, RebalanceFor, ExitFor (adapters/orca.rs:1169,1288,422,614,851) | RaydiumOpenPosition, RaydiumIncreaseLiquidity, RaydiumRebalancePosition, RaydiumRebalanceFor, RaydiumExitFor (adapters/raydium.rs:1199,1365,410,628,938) | none |
OpenFlowAdapter | OrcaOpenPosition (adapters/orca.rs:1175) | RaydiumOpenPosition (adapters/raydium.rs:1213) | none |
IncreaseFlowAdapter | OrcaIncreaseLiquidity (adapters/orca.rs:1299) | RaydiumIncreaseLiquidity (adapters/raydium.rs:1376) | none |
DecreaseFlowAdapter | OrcaDecreaseLiquidity (adapters/orca.rs:1432) | RaydiumDecreaseLiquidity (adapters/raydium.rs:1513) | none |
CloseFlowAdapter | OrcaClosePosition (adapters/orca.rs:1561) | RaydiumClosePosition (adapters/raydium.rs:1638) | none |
CollectTradingFeesAdapter | OrcaCollectFees (adapters/orca.rs:1016) | none | none |
CollectRewardsAdapter | OrcaCollectRewards (adapters/orca.rs:1069) | none | none |
ClaimAllYieldAdapter | HarvestFor, CompoundFor (adapters/orca.rs:1123,1146) | RaydiumCollectFees, RaydiumHarvestFor, RaydiumCompoundFor (adapters/raydium.rs:1128,1151,1175) | MeteoraHarvestFor, MeteoraCompoundFor, MeteoraRebalanceFor, MeteoraExitFor via impl_meteora_yield_adapter! (instructions/meteora/yield_adapter.rs:167-427) |
CompoundFlowPolicy | CompoundFor (compound.rs:302) | RaydiumCompoundFor (compound.rs:205) | MeteoraCompoundFor (compound.rs:84) |
OwnerRebalanceFlowAdapter | OrcaRebalancePosition (adapters/orca.rs:429) | RaydiumRebalancePosition (adapters/raydium.rs:417) | none |
KeeperRebalanceFlowAdapter | RebalanceFor (adapters/orca.rs:621) | RaydiumRebalanceFor (adapters/raydium.rs:635) | none |
KeeperExitFlowAdapter | ExitFor (adapters/orca.rs:853) | RaydiumExitFor (adapters/raydium.rs:940) | none |
ClmmVenueManifest | OrcaVenue (clmm.rs:316) | RaydiumVenue (clmm.rs:302) | none |
DlmmVenueManifest | none | none | MeteoraDlmmVenue (dlmm.rs:109) |
Which runner each handler calls
| Runner | Orca handler | Raydium handler | Meteora handler |
|---|---|---|---|
run_open | instructions/orca/open_position.rs:86 | instructions/raydium/open_position.rs:148 | none |
run_increase | instructions/orca/increase_liquidity.rs:172 | instructions/raydium/increase_liquidity.rs:168 | none |
run_decrease | instructions/orca/decrease_liquidity.rs:137 | instructions/raydium/decrease_liquidity.rs:142 | none |
run_close | instructions/orca/close_position.rs:122 | instructions/raydium/close_position.rs:116 | none |
run_collect_fees | instructions/orca/collect_fees.rs:121 | none | none |
run_collect_rewards | instructions/orca/collect_rewards.rs:62 | none | none |
run_owner_all_yield | none | instructions/raydium/collect_fees.rs:97 | none |
run_keeper_harvest | instructions/orca/harvest_for.rs:139 | instructions/raydium/harvest_for.rs:115 | instructions/meteora/harvest_for.rs:140 |
run_compound | instructions/orca/compound_for.rs:183 | instructions/raydium/compound_for.rs:154 | instructions/meteora/compound_for.rs:140 |
run_owner_rebalance | instructions/orca/rebalance_position.rs:203 | instructions/raydium/rebalance_position.rs:196 | none |
run_keeper_rebalance | instructions/orca/rebalance_for.rs:252 | instructions/raydium/rebalance_for.rs:226 | none |
run_keeper_exit | instructions/orca/exit_for.rs:256 | instructions/raydium/exit_for.rs:244 | none |
How a call flows through it
The representative call is an owner rebalance on Orca. The handler
orca_rebalance_position builds an OwnerRebalanceRequest and calls
run_owner_rebalance (instructions/orca/rebalance_position.rs:203).
The steps below follow run_owner_rebalance (owner_rebalance.rs:430-570) in
the order it runs them. Each entry names the operation and the error it
returns. Open that function for the lines. Where a step delegates to a helper,
the entry names the helper. Every helper named below lives in
owner_rebalance.rs, except where the entry names another file.
validate_closed_accounts and dispose_protocol_rent also exist in
decrease_close.rs, which historical note 2 and L03-N003 record as duplication.
- The runner reads the flow identity from the adapter.
- The runner checks the pause flag.
- The runner checks the venue program against the allowlist.
- The runner checks that the authority is the owner. Error
Unauthorized. - The runner reads the rebalance identity. It checks that the identity's flow
equals the flow from step 1 and that its owner equals the Sickle owner.
Error
InvalidProtocolAccountData. - The runner reads the rent accounts, narrows the identity once, and checks
them with
validate_rent_accounts. The receiver must equal the owner. A source that is not the receiver must be the Sickle and owned by this program. ErrorsInvalidRentReceiver,InvalidAccountOwner. - The runner selects the Rebalance fee classes with
fee_actions. All three must be present. - The runner loads the old settings candidate if the adapter exposes one. A
missing candidate is allowed only when the authority is the owner. Error
SettingsCandidateRequired. - The runner checks that old settings exist exactly when the adapter has a
new settings account. Error
PositionSettingsMismatch. - The adapter parses the remaining accounts. The runner rejects any tail.
Error
RewardGroupMismatch. - The runner checks that the adapter's new settings bump exists exactly when
old settings exist. Error
PositionSettingsMismatch. - The runner records the three accounts the venue must close and the rent source balance.
- The adapter removes all liquidity, collects fees and rewards, charges the selected fee classes, and closes the old position.
- The runner validates the receipt with
validate_drain_receipt. - The runner checks that all three venue accounts have zero lamports, with
validate_closed_accounts. - The runner measures the returned rent with
measure_protocol_rent. It must be positive. ErrorInvalidProtocolAccountData. - If old settings exist, the runner copies them to the new settings account
through
transfer_position_settingswith no keeper history and builds aSettingsUpdatedevent. - The adapter opens the new position.
- The runner validates the open receipt against the new position identity and
the requested range, with
validate_open_receipt(open_increase.rs:102-151). - The runner transfers the measured rent from the Sickle to the owner when
the source is the Sickle, with
dispose_protocol_rent. - The runner closes the old settings account to the owner.
- The runner maps the events and returns them with
map_events.
The remaining runners follow. Each list names the check the runner owns and the step it delegates to the adapter.
run_open (open_increase.rs:156-186)
validate_active_flow: read the identity, check the pause flag, check the allowlist (open_increase.rs:163,open_increase.rs:37-52).- Check
is_owner. ErrorUnauthorized(open_increase.rs:164-167). - Read
open_identityand checkidentity.flow == flow. ErrorInvalidProtocolAccountData(open_increase.rs:168-172). - Adapter:
prepare_open(open_increase.rs:174). - Adapter:
execute_open(open_increase.rs:175). validate_open_receipt(open_increase.rs:176).- Map
PositionOpenedfrom the receipt range and return (open_increase.rs:177-185).
run_increase (open_increase.rs:389-420)
validate_active_flow(open_increase.rs:397).- Read
increase_identityand checkidentity.flow == flow(open_increase.rs:398-402). validate_increase_authority(open_increase.rs:403,open_increase.rs:253-318). The runner resolvesswap_authority. ErrorUnauthorized. An owner returnsintent_mode = false. An automator needs position settings that match the position and allow the increase. ErrorIncreaseConsentRequired. It needs an action account. ErrorIntentRequired. The action's Sickle, executor, position, and ledger vaults must match. ErrorsIntentActionMismatch,IntentExecutorMismatch,IntentLedgerMismatch. An Exit action is rejected.- Adapter:
prepare_increase. The runner rejects a tail. ErrorInvalidProtocolAccountData(open_increase.rs:405-406). - Read the two source balances. The source keys must equal the identity's
source accounts (
open_increase.rs:407,open_increase.rs:320-336). - Adapter:
execute_increase(open_increase.rs:408). - Read the source balances again (
open_increase.rs:409). - Compute
before - afterper side with checked subtraction. ErrorMathOverflow(open_increase.rs:410,open_increase.rs:338-353). - In intent mode, debit each measured amount against the action ledger
(
open_increase.rs:411-413,open_increase.rs:372-383). - Map
LiquidityIncreasedfrom the measured amounts and return (open_increase.rs:414-419).
run_decrease (decrease_close.rs:159-188)
- Select the Decrease policy. A module-level const assertion requires all
three fee classes to be absent, so table drift fails the build
(
decrease_close.rs:24-29,171). - Read
decrease_identity(decrease_close.rs:172). - Adapter:
prepare_decrease. Reject a tail. ErrorRewardGroupMismatch(decrease_close.rs:174-175). - Adapter:
execute_decrease(decrease_close.rs:176). validate_decrease_receipt(decrease_close.rs:177,decrease_close.rs:114-157).- Map
LiquidityDecreasedand return (decrease_close.rs:178-187).
The runner does not check the pause flag, the allowlist, or the caller. The
handler checks the pause flag (instructions/orca/decrease_liquidity.rs:134).
The adapter checks the allowlist (adapters/orca.rs:1453).
run_close (decrease_close.rs:318-401)
- Select the Close policy. Principal and pool fee classes must be absent. The
reward class is required. Both rules are compile-time assertions
(
decrease_close.rs:31-36,331-332). - Read
close_identityandclose_rent_accounts. The receiver must equal the owner. The source must be the receiver or the Sickle. ErrorInvalidRentReceiver(decrease_close.rs:333-343). - Resolve the settings candidate (
decrease_close.rs:344-346). - Read the three accounts the venue must close (
decrease_close.rs:347). - Adapter:
prepare_close. Reject a tail. ErrorRewardGroupMismatch(decrease_close.rs:348-349). - Adapter:
settle_close_yieldwith the reward fee class. Thenvalidate_close_yield(decrease_close.rs:350-351,decrease_close.rs:250-268). - Record the source balance. Adapter:
close_empty_position. Check all three accounts have zero lamports (decrease_close.rs:353-355). dispose_protocol_rent: re-check the receiver and source, measure the returned rent, and transfer it when the source is the Sickle. The rent must be positive (decrease_close.rs:356-360,decrease_close.rs:270-316).- Map
PositionClosed, oneRewardsCollectedper reward, and oneFeeChargedper positive reward fee (decrease_close.rs:362-387). - Close the settings account if the candidate resolved
(
decrease_close.rs:389-391). - Return (
decrease_close.rs:393-400).
run_collect_fees and run_collect_rewards (claim.rs:506-535)
- Adapter:
collect_trading_feesorcollect_rewardswith the constant fee class (claim.rs:514,claim.rs:530). - Return the receipt and the fee class (
claim.rs:515-518,claim.rs:531-534).
The runner owns no check. Only the Orca collect handlers call these two.
run_owner_all_yield (claim.rs:568-577)
- Delegate to the private
run_all_yield(claim.rs:576), which callsprepare_all_yield, rejects a tail withRewardGroupMismatch(claim.rs:555-556), callsclaim_all_yieldwithHARVEST_FEE_ACTIONS(claim.rs:557), and returns the receipt and the actions (claim.rs:558-561).
The runner owns no caller check, and that is why the types that can reach it
are a closed set. raydium_collect_fees is the only caller, because Raydium
settles every reward slot through the same CPI, and its caller check is the
account struct's owner constraint. The bound is the sealed
OwnerAllYieldClaim (claim.rs:387-410), which no keeper claim struct
implements and none can, so the only entry points a registered keeper claim
type reaches are run_keeper_harvest and run_compound. Proven by
tests/ui/claim_owner_entry_rejects_keeper_struct.rs.
run_keeper_harvest (claim.rs:594-604)
authorize_keeper_claimwithKeeperClaim::Harvest(claim.rs:602).- Delegate to the private
run_all_yield(claim.rs:603).
run_compound (claim.rs:610-628)
authorize_keeper_claimwithKeeperClaim::Compound(claim.rs:618).- Adapter:
validate_compound_prerequisites(claim.rs:619). Every impl requires the authorization and action accounts for a non-owner. ErrorIntentRequired(compound.rs:87-93,compound.rs:208-214,compound.rs:305-311). - Adapter:
prepare_all_yield. The tail is kept (claim.rs:620). - Adapter:
begin_compound_claimwith the tail (claim.rs:621). An owner returns no ledger. A non-owner loads the authorization, creates the action ledger forIntentAction::Compound, checks the two vault mints against the signed asset set, sets their baselines, and snapshots extra baselines from the tail (compound.rs:313-386). The Orca and Meteora impls also set a baseline for a reward recipient when its mint is in the ledger (compound.rs:367-378,compound.rs:162-173). The Meteora impl also checks the signed bin range against the prepared range. ErrorIntentActionMismatch(compound.rs:112-116). - Adapter:
claim_all_yieldwithCOMPOUND_FEE_ACTIONS(claim.rs:622). - Adapter:
finish_compound_claim. A non-owner creditscollected - feeper pool token and per ledger-listed reward, then writes the action account (claim.rs:623,compound.rs:388-407). - Return the receipt and the actions (
claim.rs:624-627).
run_keeper_rebalance (keeper_rebalance.rs:227-375)
- Read the flow identity, check the pause flag, check the allowlist
(
keeper_rebalance.rs:235). - Read the identity. Check
identity.flow == flowandidentity.ownerequals the Sickle owner (keeper_rebalance.rs:237-241). - Read the intent accounts. The Instructions sysvar key must match. Error
InvalidInstructionsSysvar(keeper_rebalance.rs:242-247). - Read and check the rent accounts (
keeper_rebalance.rs:248-252). - Check
is_authorized_automatorwithPERMISSION_REBALANCE. ErrorUnauthorized(keeper_rebalance.rs:254-261). require_intent_prerequisitesfor a non-owner. ErrorIntentRequired(keeper_rebalance.rs:262-267).- Check the position settings identity (
keeper_rebalance.rs:268-273). - Adapter:
validate_keeper_static_accounts(keeper_rebalance.rs:276). - Select the Rebalance fee classes (
keeper_rebalance.rs:277-278). - Adapter:
prepare_keeper_rebalance. The tail goes to the ledger (keeper_rebalance.rs:282). begin_keeper_rebalance_intent(keeper_rebalance.rs:283-293,keeper_rebalance.rs:110-188). An owner must pass an empty tail. ErrorRewardGroupMismatch. A non-owner loads the authorization, creates the ledger, checks the signed range equals the requested range, checks the signed dust does not exceed the settings dust, checks the withdrawal floors, records the new position, checks the two asset mints, checks the signed asset set, sets baselines, and snapshots extra baselines. ErrorsIntentActionMismatch,IntentDustPolicyExceeded,IntentLedgerMismatch.- Adapter:
load_keeper_rebalance_state(keeper_rebalance.rs:295). validate_rebalance_for: the live trigger and the debounce (keeper_rebalance.rs:295-302).expected_rebalance_rangemust equal the requested range. ErrorInvalidRangePlacement(keeper_rebalance.rs:303-310).validate_rebalance_progress: the anti-cycle rule (keeper_rebalance.rs:311-317).- Copy the settings to the new settings account with the old range as keeper
history. Build
SettingsUpdated(keeper_rebalance.rs:190-221,319-329). - Record the closed accounts and the rent source balance. Adapter:
execute_keeper_drain_and_close(keeper_rebalance.rs:331-333). - Validate the receipt, the closed accounts, and the positive rent
(
keeper_rebalance.rs:334-340). - Adapter:
execute_keeper_reopen. Validate the open receipt (keeper_rebalance.rs:342-354). - Transfer the rent to the owner (
keeper_rebalance.rs:355). - For a ledger: check the withdrawal floors against the gross principal,
credit the net amount per side, write the action account
(
keeper_rebalance.rs:357-364,keeper.rs:70-82). - Map the events with
map_eventsand return (keeper_rebalance.rs:366-374).
run_keeper_exit (keeper_exit.rs:234-357)
- Read the flow identity and
is_owner. Check the pause flag with the owner exit exemption. Check the allowlist (keeper_exit.rs:242-249). - Read the identity. Check the flow and the owner (
keeper_exit.rs:251-255). - Read the intent accounts. Check the Instructions sysvar key
(
keeper_exit.rs:257-262). - Read and check the rent accounts (
keeper_exit.rs:262-263). - Check
is_authorized_automatorwithPERMISSION_EXIT(keeper_exit.rs:266-273). require_intent_prerequisites(keeper_exit.rs:273-277).- Check the position settings identity (
keeper_exit.rs:278-283). - Adapter:
validate_keeper_exit_static_accounts(keeper_exit.rs:286). - Resolve the settings candidate. It must exist. Error
InvalidPositionSettings(keeper_exit.rs:287-290). - Select the Exit fee classes (
keeper_exit.rs:291-292). - Adapter:
prepare_keeper_exit, thenload_keeper_exit_state. The state pool must equal the identity pool and the range must be ordered (keeper_exit.rs:294-300). validate_exit_forwith the live tick (keeper_exit.rs:301-303).begin_keeper_exit_intent(keeper_exit.rs:304-314,keeper_exit.rs:114-196). An owner must pass an empty tail. A non-owner checks the withdrawal floors, creates the ledger forIntentAction::Exit, checks the signed exit parameters equal the settings, records the side, start tick, and target mint, checks the asset mints and the signed set with the target, sets baselines, and snapshots extra baselines.- Record the closed accounts and the rent source balance. Adapter:
execute_keeper_exit(keeper_exit.rs:316-318). - The receipt's old position must equal the loaded state
(
keeper_exit.rs:319-322). - Validate the receipt, the closed accounts, and the positive rent
(
keeper_exit.rs:323-329). - Transfer the rent to the owner (
keeper_exit.rs:333). - For a ledger: check the withdrawal floors, credit the net amount per side,
write the action account (
keeper_exit.rs:334-341,keeper.rs:70-82). - An owner closes the settings account now. A keeper leaves it for
finalize_intent(keeper_exit.rs:343-348). - Map the events with
map_exit_eventsand return (keeper_exit.rs:349-356).
How the Meteora handlers use this layer
Meteora has no adapter in venue/adapters/. Its handlers use three parts of
this layer.
| Meteora handler | Runner or trait used | Bypassed |
|---|---|---|
meteora_harvest_for | run_keeper_harvest with ClaimAllYieldAdapter (instructions/meteora/harvest_for.rs:140) | none |
meteora_compound_for | run_compound with CompoundFlowPolicy (instructions/meteora/compound_for.rs:140, compound.rs:84) | none |
meteora_rebalance_for | ClaimAllYieldAdapter methods called directly, HARVEST_FEE_ACTIONS (instructions/meteora/rebalance_for.rs:552,597) | run_keeper_harvest, run_keeper_rebalance |
meteora_exit_for | ClaimAllYieldAdapter methods called directly, HARVEST_FEE_ACTIONS, select_exit_side_and_target (instructions/meteora/exit_for.rs:447,463, instructions/meteora/exit_for.rs:27) | run_keeper_harvest, run_keeper_exit |
| Seven Meteora owner instructions | Marker traits only (dlmm.rs:93-99) | every runner |
The Meteora rebalance and exit handlers read their principal class from the
shared matrix through the const fn in claim.rs
(instructions/meteora/rebalance_for.rs:41,
instructions/meteora/exit_for.rs:37), so an edit that drops the class from
position_fee_policy fails their build. The Meteora claim handlers read it too,
through the shared COLLECT_FEES_ACTION and COLLECT_REWARDS_ACTION
(instructions/meteora/claim_fees.rs:265,278,
instructions/meteora/claim_reward.rs:255); dropping those classes fails the
build at venue/claim.rs:159 and :161, where the consts are defined. MeteoraRebalanceFor and MeteoraExitFor
implement ClaimAllYieldAdapter but not KeeperRebalanceFlowAdapter or
KeeperExitFlowAdapter. The DLMM manifest requires only the empty marker
traits DlmmKeeperRebalanceFlowAdapter and DlmmKeeperExitFlowAdapter for
those two slots (dlmm.rs:55-72).
Invariants the layer owns
- Every fee class comes from
position_fee_policy. Claim, decrease, and close read the table at compile time and fail the build when a mandatory class is absent or a forbidden class appears (claim.rs:32-37,claim.rs:158-163,decrease_close.rs:24-36). Rebalance keeps the runtime form because it returns aResult. ErrorInvalidProtocolAccountData(owner_rebalance.rs:168-174). - A rebalance or exit receipt must carry a fee no larger than the received
amount for every asset.
validate_asset. ErrorInvalidProtocolAccountData(owner_rebalance.rs:176-187). - A same-mint reward that the venue paid into a pool token account must
report zero received and zero fee and name a valid side.
validate_drain_receipt,validate_decrease_receipt. ErrorInvalidProtocolAccountData(owner_rebalance.rs:216-228,decrease_close.rs:136-153). - The reward vector length must equal the expected mint list and must not
exceed the reward slot cap (
owner_rebalance.rs:206-210,decrease_close.rs:125-128,decrease_close.rs:251-255). - After a close, all three venue accounts must have zero lamports. Error
InvalidProtocolAccountData(owner_rebalance.rs:233-241,owner_rebalance.rs:233-241). - Returned rent goes only to the owner. The receiver must equal the owner. A
distinct source must be the Sickle owned by this program. The rent must be
positive. Errors
InvalidRentReceiver,InvalidAccountOwner,InvalidProtocolAccountData(decrease_close.rs:275-296,decrease_close.rs:357-360,owner_rebalance.rs:243-259,owner_rebalance.rs:509-512). - An opened or reopened position must have the expected keys, pool, range,
no liquidity, the venue program as owner, the token program as mint and
token account owner, and an NFT balance of one. Errors
InvalidPosition,InvalidProtocolAccountData,InvalidAccountOwner(open_increase.rs:102-151). - The increase event and the ledger debit use the measured source debit, not
the requested maximum (
open_increase.rs:338-353,open_increase.rs:355-370). - A keeper that is not the owner must present both the authorization and the
action account before the runner parses any venue account. Error
IntentRequired(keeper.rs:36-48,keeper_rebalance.rs:262-276,keeper_exit.rs:273-286). - An owner on a keeper path must pass an empty intent tail. Error
RewardGroupMismatch(keeper_rebalance.rs:101-106,keeper_exit.rs:125-128). - Keeper ledger credit is
received - feefor principal plus pool fees per side, and the withdrawal floors apply to the gross principal (keeper.rs:50-82). - A keeper rebalance must place the new range at
expected_rebalance_range. ErrorInvalidRangePlacement(keeper_rebalance.rs:303-310). - A keeper rebalance's signed dust must not exceed the settings dust. Error
IntentDustPolicyExceeded(keeper_rebalance.rs:157-160). - A keeper exit's signed trigger ticks and target mints must equal the
settings. Error
IntentActionMismatch(keeper_exit.rs:157-165). - The exit side and target are selected once from the live tick and stored
in the ledger (
keeper_exit.rs:101-110,keeper_exit.rs:167-170). - A settings transfer on a keeper rebalance records the closed range as
history. An owner rebalance passes
None(keeper_rebalance.rs:319-329,owner_rebalance.rs:523). - A new
Protocolvariant must appear invenue_registry!or the build fails (family.rs:146-155). - A venue tag that implements
RegisteredClmmVenueimplements every runner adapter trait, because each manifest slot requires an adapter trait (clmm.rs:230-242,family.rs:115-117).
Tests
| Test location | Covers |
|---|---|
policy.rs:90-160 | Every row of the fee table. |
keeper.rs:84-167 | Intent prerequisites plus exact gross-floor and post-fee ledger credit. |
family.rs:226-314 | Family and conformance per protocol, type registration, geometry resolution. |
open_increase.rs:422-538 | validate_open_receipt rejections, measured_increase_receipt, map_increase_event. |
decrease_close.rs:403-694 | validate_decrease_receipt, run_decrease with a fake adapter, validate_close_yield, dispose_protocol_rent, validate_closed_accounts. |
claim.rs:630-1126 | All five claim runners with a fake adapter, tail rejection on the all-yield body, tail pass-through on compound, and the keeper-claim authorization: permission and consent are demanded per operation, and a paused or misidentified claim stops before the adapter. |
owner_rebalance.rs:572-899 | validate_drain_receipt, same-mint reward rule, map_events, both rent paths, validate_closed_accounts. |
keeper_rebalance.rs:377-520 | Owner-tail validation and the settings-transfer stage, including identity, history, and event flags. |
keeper_exit.rs:359-504 | select_exit_side_and_target, map_exit_events. |
programs/sickle/tests/clmm_conformance.rs | The trybuild suite. Described below. |
tests/surfpool-lifecycle.ts | Orca: open, increase, decrease, collect fees, close, owner rebalance, harvest, compound, rebalance, exit on a mainnet fork. |
tests/surfpool-raydium-lifecycle.ts | Raydium: the same set through the Raydium handlers. |
tests/surfpool-t22-lifecycle.ts, tests/surfpool-t22-raydium-lifecycle.ts | Token-2022 pools through the same runners. |
tests/surfpool-meteora-lifecycle.ts | meteora_harvest_for, meteora_compound_for, meteora_rebalance_for, meteora_exit_for. |
tests/sickle/orca.ts, orca-fork.ts, orca-lifecycle.ts | Orca handlers on the legacy validator. |
tests/sickle/raydium.ts, raydium-fork.ts | Raydium handlers on the legacy validator. |
tests/sickle/intents.ts | compound_for, rebalance_for under the authorization flow. |
The trybuild suite
programs/sickle/tests/clmm_conformance.rs:1-26 runs the fixtures in
programs/sickle/tests/ui/. The .stderr files are part of the contract.
| Fixture | Kind | Proves |
|---|---|---|
clmm_orca_manifest.rs | pass | OrcaVenue: RegisteredClmmVenue. |
clmm_raydium_manifest.rs | pass | RaydiumVenue: RegisteredClmmVenue. |
dlmm_meteora_manifest.rs | pass | MeteoraDlmmVenue: DlmmVenueManifest. |
dlmm_registered_meteora.rs | pass | MeteoraDlmmVenue: RegisteredDlmmVenue. |
dlmm_meteora_compound_surface.rs | pass | MeteoraCompoundFor: DlmmCompoundFlowSurface. |
dlmm_meteora_rebalance_surface.rs | pass | MeteoraRebalanceFor: DlmmRebalanceForFlowSurface. |
dlmm_meteora_exit_surface.rs | pass | MeteoraExitFor: DlmmExitForFlowSurface. |
clmm_manifest_rejects_meteora_dlmm.rs | compile_fail | MeteoraDlmmVenue does not implement ClmmVenueManifest. |
clmm_manifest_rejects_partial_yield.rs | compile_fail | A HarvestFor slot with Scope = TradingFeesOnly and no ClaimAllYieldAdapter is rejected. |
clmm_manifest_requires_collect_rewards.rs | compile_fail | A manifest without the CollectRewards associated type is rejected. |
clmm_manifest_requires_claim_capability.rs | compile_fail | A reward slot cannot name the dedicated reward proof without implementing CollectRewardsAdapter. |
clmm_reward_surface_rejects_fee_capability.rs | compile_fail | A real fee-only adapter cannot use its fee proof to satisfy the reward surface. |
clmm_manifest_requires_open_adapter.rs | compile_fail | An Open slot type without OpenFlowAdapter is rejected. |
clmm_manifest_requires_increase_adapter.rs | compile_fail | The same for IncreaseFlowAdapter. |
clmm_manifest_requires_decrease_adapter.rs | compile_fail | The same for DecreaseFlowAdapter. |
clmm_manifest_requires_close_adapter.rs | compile_fail | The same for CloseFlowAdapter. |
clmm_manifest_requires_owner_rebalance_adapter.rs | compile_fail | The same for OwnerRebalanceFlowAdapter. |
clmm_manifest_requires_keeper_rebalance_adapter.rs | compile_fail | The same for KeeperRebalanceFlowAdapter. |
clmm_manifest_requires_keeper_exit_adapter.rs | compile_fail | The same for KeeperExitFlowAdapter. |
dlmm_manifest_requires_keeper_rebalance_adapter.rs | compile_fail | A RebalanceFor slot type without DlmmKeeperRebalanceFlowAdapter is rejected. |
dlmm_manifest_requires_keeper_exit_adapter.rs | compile_fail | An ExitFor slot type without DlmmKeeperExitFlowAdapter is rejected. |
claim_owner_entry_rejects_keeper_struct.rs | compile_fail | HarvestFor cannot reach run_owner_all_yield, the one all-yield claim runner without its own caller check. |
| Count | Value |
|---|---|
| pass fixtures | 7 |
| compile_fail fixtures | 15 |
The suite proves that each associated type in a manifest implements the trait its slot requires.
It does not prove what a handler body does. clmm.rs:58-62 states this for
YieldClaimSurface.
Gaps
- No unit test drives
run_open,run_increase,run_close,run_owner_rebalance,run_keeper_rebalance, orrun_keeper_exitwith a fake adapter. Onlyrun_decreaseand the four claim runners have one. begin_keeper_rebalance_intenthas no direct unit test.compound.rshas no unit test.clmm.rsanddlmm.rshave none beyond the trybuild suite.run_collect_rewardsis exercised bytests/mainnet-acceptance.ts:1920only. No Surfpool or legacy-validator test callsorca_collect_rewards.- No test covers the
IncludedInPoolTokenreward path throughrun_close. - No negative test covers
validate_increase_authorityon an Exit action (open_increase.rs:296) or on a ledger vault mismatch (open_increase.rs:303-316).
Simplification and audit notes
- L03-N003 —
decrease_close.rs:270-316andowner_rebalance.rs:243-296implement the same rent rule twice. The first is one function. The second is three functions:validate_rent_accounts,measure_protocol_rent,dispose_protocol_rent. The lamport arithmetic atdecrease_close.rs:298-314andowner_rebalance.rs:283-294is the same. Kept as design work rather than duplication.decrease_close.rshas one function;owner_rebalance.rshas three. They take different identity and rent types,CloseIdentitywithCloseRentAccountsagainstPositionDrainIdentitywithDrainRentAccounts. Unifying them needs one shared identity and one shared rent type, which is a design change with no runtime effect, not a mechanical removal. - L03-N005 —
keeper_rebalance.rs:110-188andkeeper_exit.rs:114-196share the sequence: load authorization, create the ledger, check asset mints, check the signed set, set two baselines, snapshot the tail. The rebalance version checks the range and dust. The exit version checks the exit parameters. Kept, because the order differs and the order is observable.verify_withdrawal_floor_argumentsis the third check inbegin_keeper_exit_intentand the fifth inbegin_keeper_rebalance_intent, after the range and dust checks. Sharing one sequence moves it in one of the two, so a malformed input would hit a different error first. Error codes reach indexers, so that is a behavior change rather than a deduplication. - L03-N011 — The pause and allowlist checks live in different layers per flow. The
runner owns them for open, increase, rebalance, and exit. The handler owns
the pause check and the adapter owns the allowlist check for decrease,
close (
instructions/orca/decrease_liquidity.rs:134,adapters/orca.rs:1453,adapters/orca.rs:1602,instructions/meteora/yield_adapter.rs:180-181). Therun_decreaseandrun_closerunners own no authorization check at all;run_keeper_harvestandrun_compoundnow do (claim.rs:238-271). - L03-N012 —
run_closechecks the rent receiver and source atdecrease_close.rs:335-343, thendispose_protocol_rentchecks the same keys again atdecrease_close.rs:275-279anddecrease_close.rs:288-296. - L03-N013 —
run_owner_rebalancecallsvalidate_rent_accountsatowner_rebalance.rs:455and again insidemeasure_protocol_rentatowner_rebalance.rs:508viaowner_rebalance.rs:272. The keeper runners repeat the pattern (keeper_rebalance.rs:252,keeper_rebalance.rs:336,keeper_exit.rs:263,keeper_exit.rs:325). - L03-N015 —
run_openchecksidentity.flow == flowatopen_increase.rs:169-172. Both values come from the same adapter throughactive_flow_identityandopen_identity. The same self-consistency check appears atopen_increase.rs:399-402,owner_rebalance.rs:445-448,keeper_rebalance.rs:238-241, andkeeper_exit.rs:252-255. - L03-N018 —
CollectTradingFeesAdapter(claim.rs:426-436) andCollectRewardsAdapter(claim.rs:439-449) each have one production implementor, inadapters/orca.rs:1016andadapters/orca.rs:1069.raydium_collect_feesusesrun_owner_all_yieldinstead (instructions/raydium/collect_fees.rs:97). The manifest now hides that realization difference behind the capability proof recorded in resolved L03-N019; the two dedicated runtime adapter traits remain separate and singly implemented. - L03-N021 —
TradingFeesOnly(clmm.rs:51) has no production use. Its only user is the trybuild fixtureclmm_manifest_rejects_partial_yield.rs:80. Kept: that is its purpose. The fixture provesCompleteYieldSurfacerejects a partial-yield surface, and its snapshot names the type, so removing it would delete the proof that the bound works. - L03-N027 —
#[inline(never)]appears onrun_close(decrease_close.rs:322),run_owner_rebalance(owner_rebalance.rs:429),begin_keeper_rebalance_intent(keeper_rebalance.rs:109),run_keeper_rebalance(keeper_rebalance.rs:226),begin_keeper_exit_intent(keeper_exit.rs:113),run_keeper_exit(keeper_exit.rs:233), andvalidate_increase_authority(open_increase.rs:252). None of these sites has a comment that gives the reason.claim.rs:6-7explains the opposite choice for the claim runners.keeper.rs:25-26explains theBoxonKeeperIntentAccountswith the SBF frame. - L03-N028 —
#[inline(always)]appears onrun_open,run_increase,run_decrease, and the four claim runners (open_increase.rs:155,open_increase.rs:388,decrease_close.rs:162,claim.rs:505,claim.rs:521,claim.rs:546,claim.rs:609). The split between always and never is not documented at the call sites. - L03-N033 —
KeeperRebalanceFlowAdapter::position_settings_pairreturns(&PositionSettings, &mut PositionSettings)from one&mut self(keeper_rebalance.rs:67). The adapter must hold two distinct settings accounts for this to compile. - L03-N034 —
run_keeper_exitreadsis_owneratkeeper_exit.rs:243before the allowlist check, whilerun_keeper_rebalancereads it atkeeper_rebalance.rs:262after the automator check. The two runners order the same steps differently. - L03-N035 —
KeeperRebalanceState.old_liquidity(keeper_rebalance.rs:49) andKeeperExitState.liquidity(keeper_exit.rs:46) are not read by either runner. Only the adapter reads them. Kept. The fields are not dead. The runner passes them through and the adapter reads them, which is the state contract between the two. Removing them would move the liquidity read into the adapter's own prepare step and change what the runner can validate. - L03-N037 — Tests:
keeper_rebalance.rshas three unit tests (keeper_rebalance.rs:460-519).compound.rshas none. No fake-adapter test exists for the six runners that own the most checks. See the Gaps list above.
Resolved
L03-N020 — The two empty marker traits remain because they are the manifest's compile-time proof that each additive keeper handler declares its family contract (
dlmm.rs:53-72,dlmm.rs:79-90). Rebalance and exit now have symmetric, single-error compile-fail fixtures (dlmm_manifest_requires_keeper_rebalance_adapter.rs:1-25,dlmm_manifest_requires_keeper_exit_adapter.rs:1-25). Every other manifest slot uses its production account type, so each snapshot can fail only on the marker it names. Removing either marker bound makes its fixture compile and the trybuild suite fail. Resolved on 2026-09-17.L03-N014 — The note's two signals did not encode the same fact. The replacement settings account now supplies its own presence check through
new_position_settings_mut(owner_rebalance.rs:139-154,owner_rebalance.rs:490-501), and the redundanthas_new_position_settingsmethod and both venue implementations are gone (adapters/orca.rs:429-461,adapters/raydium.rs:417-446). The prepared bump check remains because account presence does not prove that the venue request supplied the bump consumed bytransfer_position_settings. The legacy-validator cases still pin new-without-old and old-without-new for Orca (tests/sickle/orca.ts:1775-1881) and the unpaired Raydium case (tests/sickle/raydium.ts:1113-1131). Resolved on 2026-09-17.L03-N019 — The
CollectFeesandCollectRewardsslots now require an associatedClaimFlowCapabilityproof (clmm.rs:82-150,:230-242). Orca's surfaces name the dedicated fee and reward proof tokens (clmm.rs:246-251); Raydium's one zero-liquidity-decrease surface namesAllYieldClaimin both slots (clmm.rs:273-280). The sealed proof exists only when the account type implementsCollectTradingFeesAdapter,CollectRewardsAdapter, orClaimAllYieldAdapter, so Raydium does not falsely implement an Orca adapter it never executes. The compile-fail fixtureclmm_manifest_requires_claim_capability.rs:1-30supplies a synthetic reward surface with no adapter and is rejected at the associated proof type;clmm_reward_surface_rejects_fee_capability.rs:1-26proves a real fee-only adapter cannot fill the reward slot. Removing either bound makes its fixture compile and the trybuild suite fail. Resolved on 2026-09-17.L03-N016 — Decrease and Close now bind module-level constants to the shared fee-policy table (
decrease_close.rs:24-36). Const assertions reject every fee class on Decrease, reject principal and pool fees on Close, and require Close's reward action before either runner can compile; the runners only copy those checked values (decrease_close.rs:171,:331-332). Giving Decrease a reward, giving Close a principal fee, or removing Close's reward action each producesE0080duringcargo check. The former runtimeInvalidProtocolAccountDatapaths are gone. Resolved on 2026-09-17.L03-N006 — Keeper rebalance and keeper exit now call one
credit_keeper_drain_intent(keeper.rs:70-82). The helper preserves the observable order: signed withdrawal floors bind the two gross principal receipts before the ledger receives exact post-fee principal plus trading yield. Its focused test (keeper.rs:98-166) drives the real ledger write; bypassing the shared net calculation fails with 100 instead of 115. Resolved on 2026-09-17.L03-N026 —
run_keeper_rebalance's contiguous settings-transfer andSettingsUpdatedconstruction moved unchanged intotransfer_keeper_rebalance_settings(keeper_rebalance.rs:190-221); the CPI and check order remain in the 149-line runner (keeper_rebalance.rs:227-375). The focused test (keeper_rebalance.rs:479-519) distinguishes all three event flags while pinning the replacement identity and keeper history. Mappingautomate_rewardsfromauto_rebalancemakes it fail on(true, true, true)instead of(true, false, true). The extracted stage and its proof, not crossing the inventory threshold by one line, are why this record is resolved. Resolved on 2026-09-17.L03-R001 —
Conformance::Pending,PendingClmmVenue,PendingDlmmVenue, andprotocol_conformance(family.rs:68-72,family.rs:100-103,family.rs:113-115,family.rs:175-183) have no implementor and no caller outsidefamily.rs. Bothpending_*registry lists are empty (family.rs:192,family.rs:196).family.rs:64-66states that removing the state is part of finishing the abstraction. Resolved in this pass.
The pending state is gone: the Pending variant, both Pending*Venue
traits, both sealed markers, both macro arms, and both empty registry lists.
protocol_conformance and the Conformance enum stay. Their only caller is
their own unit test, but they are a compile-time guard rather than dead code:
the generated match has no wildcard arm, so adding a Protocol variant
without registering it fails to compile. Verified by adding one, which
produced E0004 in five places.
- L03-R002 —
PositionDrainIdentityand the genericI: Into<PositionDrainIdentity>requirement onvalidate_drain_receipt,validate_rent_accounts, andmeasure_protocol_rent(owner_rebalance.rs:193-199,owner_rebalance.rs:251-257,owner_rebalance.rs:279-286) serve two callers:OwnerRebalanceIdentityandPositionDrainIdentityitself. Resolved in this pass.
The three functions now take PositionDrainIdentity directly. Each runner
narrows once into a named local rather than converting at every call site.
That states what the narrower identity is for: an exit adapter cannot reach
the reopen fields, so the postconditions must not see them.
L03-R003 —
open_increase.rs:37-47definesvalidate_active_flowas a private function.owner_rebalance.rs:412-416,keeper_rebalance.rs:213-217, andkeeper_exit.rs:290-297repeat its body instead of calling it. Partly resolved in this pass.Two of the three were exact copies and now call the helper, which is
pub(crate). The third is not a copy.run_keeper_exitchecks the pause flag withassert_not_paused_unless_owner_exit, so an owner can always leave a paused program. That is a different rule, recorded in CLAUDE.md as an intentional divergence, and folding it into the shared helper would have removed it. The helper's doc comment now says so, so the next pass does not complete the deduplication and delete the carve-out.L03-R004 —
decrease_close.rs:243-245,owner_rebalance.rs:169-171, andclaim.rs:16-21each define arequired_actionhelper. The first two are identical run-time functions. The third is a const fn that panics. Three definitions of one idea. Partly resolved in this pass.The two run-time copies are now one, in
policy.rsbesideposition_fee_policy, because both are fee policy. The third is not a third definition of the same idea.claim.rsneeds the value in aconstcontext, which cannot return aResult, so it panics instead. The new helper's doc comment says so.L03-R005 —
decrease_close.rs:267-275andowner_rebalance.rs:238-246definevalidate_closed_accountswith identical bodies. Resolved in this pass.The bodies were byte-identical apart from visibility.
decrease_close.rsimports theowner_rebalance.rsone, which is the patternvalidate_active_flowandRawOwnerRebalanceReceiptalready follow. A module holding two functions would add more surface than it removes.L03-R006 —
owner_rebalance.rs:315-366andkeeper_exit.rs:207-258buildFeesCollected, the four poolFeeChargedevents, and the reward events with identical code. Only the final struct differs,RebalancedversusExited. Resolved in this pass.map_drain_eventsbuilds the three shared groups from the narrowerPositionDrainIdentity, which the rebalance identity already converts into. Each runner keeps only its own closing event,RebalancedagainstExited. A unit test pins the shared groups directly rather than through the two wrappers.L03-R007 —
compound.rs:27-35,compound.rs:158-166, andcompound.rs:260-268are three identicalvalidate_compound_prerequisitesbodies. Each repeatsrequire_intent_prerequisitesfromkeeper.rs:35-47without calling it. Resolved in this pass.All three now call
require_intent_prerequisites. The bodies differed only in the name of the authorization field, which the helper takes as a boolean.L03-R008 —
compound.rs:25-152,compound.rs:154-252, andcompound.rs:254-379differ only in field names, the pool and position keys, the DLMM load function, and the Raydium impl's lack of a reward baseline loop. Thefinish_compound_claimbodies atcompound.rs:114-151andcompound.rs:338-378are identical except for field names. Resolved in this pass.The three bodies now call
credit_pool_deltas, and the two that need it callcredit_matching_rewards. The note treats Raydium's missing reward loop as an omission. It is not.Merging all three would in fact have been correct but not worth doing, which is a third answer the notes have no word for. Raydium pays a reward whose recipient is a pool-token account into that same account, so
decrease_liquidity.rs:496-511reports it with zero amounts and its delta is already insidereceipt.pool. A distinct reward mint is not a ledger entry, so theentry_indexguard skips it.credit_drainof zero is a checked add of zero. The loop is therefore provably dead on Raydium.It was still declined. A provably dead loop on a compute-constrained keeper path costs compute for nothing, and it makes the code assert a reward-credit path Raydium does not have. A future reader who re-derives the no-op proof should not conclude the merge was missed.
L03-R009 —
owner_rebalance.rs:325setsFeesCollected.actiontoactions.principal, while the fee charged on the same two amounts usesactions.pool_fees(owner_rebalance.rs:332-333). The event reports Rebalance or Exit for an amount charged as Harvest. This was two places until historical note 4 merged the mappers, so one line now carries it for both the owner rebalance and the keeper exit. Resolved in this pass.FeesCollected.actionnow carriesactions.pool_fees, the action those two amounts were charged under. The collect and harvest flows already reported the charged action, so this brings the four into line rather than introducing a convention. The change is observable off-chain and is inRELEASE_RUNBOOK.mdunder Consumer-visible changes.No integration test asserted this field on a rebalance or an exit. Both tests that assert
feesCollected.data.actioncover the collect flow, which was already correct, so the wrong value could have shipped indefinitely without a red test. A unit test now pins it, and reverting the field makes that test fail.L03-R010 —
keeper_rebalance.rs:97-102is a named functionvalidate_owner_intent_tail.keeper_exit.rs:126-129inlines the same check. Resolved in this pass.Declined once, then revised. The first reading was that the condition is shared while the control flow is not: the helper returns
Ok(())and its caller continues, while thekeeper_exitsite returnsOk(None)early from a function with a different return type. That is true and it is not a reason to keep two copies of the condition. Both sites now callvalidate_owner_intent_tailand then take their own branch, and the three compound policies do the same, so one definition serves four call sites.L03-R011 —
claim.rs:122-124states that harvest rejects a tail and compound assigns it to intent baselines. On the owner path everybegin_compound_claimreturns before it reads the tail (compound.rs:99-101,compound.rs:210-212,compound.rs:305-307). The owner compound accepts extra remaining accounts without a check, unlike the owner keeper rebalance (keeper_rebalance.rs:97-102) and owner keeper exit (keeper_exit.rs:128-130). Resolved in this pass.All three
begin_compound_claimpolicies now callvalidate_owner_intent_tailbefore the owner branch returns, so the owner path rejects a tail exactly as the keeper rebalance and keeper exit owner paths do. Theme 7 recorded the decision; this is where it landed.L03-R012 —
decrease_close.rs:2-6says the module owns "fee-class selection". The Decrease policy has no fee class (policy.rs:54-58), and the adapter receives the reward class for close (decrease_close.rs:348). The only selection is a read of the table. Resolved in this pass.The module doc no longer claims fee-class selection. It reads the classes for its operation from
policy.rs, and the doc says so.L03-R013 —
claim.rs:114-116says theClaimAllYieldAdaptercapability's absence "caught the live Orca keeper gap".clmm.rs:1-9anddlmm.rs:1-8carry similar history. The comments describe past incidents, not the current contract. Resolved in this pass.The comment now states the contract, that a type settling only trading fees does not satisfy the trait, rather than the incident that revealed it.
L03-R014 —
clmm.rs:3-8states that claim, harvest, and compound handlers route throughsuper::claim.instructions/meteora/rebalance_for.rs:496,562andinstructions/meteora/exit_for.rs:392,437callprepare_all_yieldandclaim_all_yielddirectly and skiprun_harvest. Resolved in this pass.The module doc now says the manifest covers the CLMM family only, so it no longer implies the Meteora DLMM handlers route through
super::claim.L03-R015 —
owner_rebalance.rs:15andkeeper_rebalance.rs:14importtransfer_position_settingsfrominstructions/orca/rebalance_position.rs:390. A shared runner depends on one venue's handler file. The helper's parameter is namedwhirlpool(instructions/orca/rebalance_position.rs:393) and receives a Raydium pool key on the Raydium path. Resolved in this pass.The parameter is renamed
pool. It takes a Raydium pool key on the Raydium path, so the venue-specific name was wrong at half its call sites. The upward dependency on one venue's handler file stays, and is design work rather than a rename.L03-R016 — Meteora rebalance and exit write the principal fee class as a literal (
instructions/meteora/rebalance_for.rs:528,541,instructions/meteora/exit_for.rs:408,421) instead ofposition_fee_policy(Rebalance)orposition_fee_policy(Exit). The fee table's claim to be "shared by all venue families" (policy.rs:31) does not hold for those two handlers. Resolved in this pass. Both handlers now read the class from the table through theconst fninclaim.rs, so a change toposition_fee_policythat drops the principal action fails the build at the handler instead of leaving a stale literal. The values agreed before the change; this removes the path by which they could stop agreeing. Verified by removing the action from the table and watching the build fail.
The line citations in this note refer to the file as it was before the change.
- L03-R017 —
keeper_exit.rsimportsRawOwnerRebalanceReceipt,OwnerRebalanceRentAccounts, andRebalanceFeeActionsfor an exit (keeper_exit.rs:29-33).run_keeper_rebalancereturnsOwnerRebalanceRunReceipt(keeper_rebalance.rs:214). The names say "owner rebalance" for keeper and exit flows. Resolved in this pass. The four becameRawDrainFlowReceipt,DrainRentAccounts,DrainFeeActions, andRebalanceRunReceipt: 84 occurrences across 8 files, no behaviour change, and the trybuild snapshots and the IDL both unchanged. The names follow the families already here,RawOpenFlowReceipt,CloseRentAccounts,YieldFeeActionsandOpenRunReceipt, and "drain" is the word this codebase already uses for the step the three flows share.RebalanceRunReceiptkeeps "rebalance" because both of its users are rebalances; the exit has its ownKeeperExitRunReceipt. The deferral reasoning was checked before it was acted on, and it was partly wrong. The rename pluscargo fmtchanges the length of three of the eight files, not all eight, so the old convention would have cost a re-sweep below three shift points rather than a rewrite of four pages. The ordering still paid: that re-sweep moved 131 citations, and the checker found every one of them.
Related
- Dependency graph
- Layer 2, Instruction handlers:
02-instruction-handlers.md - Layer 4, Venue adapters:
04-venue-adapters.md - Layer 7, State and settings:
07-state-and-settings.md, forPositionGeometryandPositionSettings - Layer 8, Keeper authorization:
08-intent.md, forActionIntentand the finalizer - Layer 9, Fees:
09-fees.md