Sickle Sol

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

PathLinesPurpose
programs/sickle/src/venue/mod.rs22Declares the modules. compound is private. adapters is #[doc(hidden)].
programs/sickle/src/venue/policy.rs160The fee table: one FeePolicy per PositionOperation.
programs/sickle/src/venue/clmm.rs359The CLMM manifest, the marker traits, the receipt primitives, and the Orca and Raydium venue tags.
programs/sickle/src/venue/dlmm.rs121The DLMM manifest and the Meteora marker impls.
programs/sickle/src/venue/family.rs314The venue registry macro, VenueFamily, PositionGeometry, and Protocol::position_geometry.
programs/sickle/src/venue/keeper.rs167Helpers shared by keeper rebalance and keeper exit: intent prerequisites and ledger credit.
programs/sickle/src/venue/open_increase.rs538run_open and run_increase.
programs/sickle/src/venue/decrease_close.rs694run_decrease and run_close.
programs/sickle/src/venue/claim.rs1126run_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.rs408Three CompoundFlowPolicy impls: Orca, Raydium, Meteora.
programs/sickle/src/venue/owner_rebalance.rs899run_owner_rebalance and the receipt, rent, and event helpers that the keeper runners reuse.
programs/sickle/src/venue/keeper_rebalance.rs520run_keeper_rebalance.
programs/sickle/src/venue/keeper_exit.rs504run_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

ItemKindMeaning
PositionOperationenumEight operations: CollectFees, CollectRewards, Harvest, Compound, Decrease, Close, Rebalance, Exit.
FeePolicystructThree Option<FeeAction> fields: principal, pool_fees, rewards.
position_fee_policyconst fnThe exhaustive match from operation to FeePolicy (policy.rs:32-87).
required_actionfnRun-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.

Operationprincipalpool_feesrewards
CollectFeesnoneHarvestnone
CollectRewardsnonenoneHarvest
HarvestnoneHarvestHarvest
CompoundnoneCompoundCompound
Decreasenonenonenone
ClosenonenoneHarvest
RebalanceRebalanceHarvestHarvest
ExitExitHarvestHarvest

keeper.rs

ItemKindMeaning
KeeperAssetstructOne signed pool asset: mint, vault, baseline balance.
KeeperIntentAccountsstructThe payer, Instructions sysvar, optional authorization, session, action, and System program.
require_intent_prerequisitespub(crate) fnA non-owner needs both the authorization and the action account, else IntentRequired.
exact_net_drain_creditpub(crate) fnprincipal.received + pool_fees.received - principal.fee - pool_fees.fee for one side.
measured_pool_withdrawal_creditpub(crate) fnThe two gross principal.received values.
credit_keeper_drain_intentpub(crate) fnVerifies gross withdrawal floors, then credits exact post-fee principal and trading yield.

clmm.rs

ItemKindMeaning
OrcaVenue, RaydiumVenueenumEmpty venue tags. Each implements ClmmVenueManifest.
YieldScope, AllYield, TradingFeesOnlytrait, enumA sealed scope tag. AllYield means fees plus every reward slot.
YieldClaimSurfacetraitDeclares type Scope: YieldScope for one account struct.
CompleteYieldSurfacetraitBlanket impl for every YieldClaimSurface<Scope = AllYield> (clmm.rs:70).
OpenFlowSurface, IncreaseFlowSurface, DecreaseFlowSurface, CloseFlowSurfacetraitBlanket impls over the matching adapter trait (clmm.rs:72-80, :152-160).
ClaimFlowCapability, the three claim proof tokens, and their capability classestrait, enumSealed adapter evidence for dedicated fee, dedicated reward, or combined all-yield claims (clmm.rs:82-138).
FeeClaimFlowSurface, RewardClaimFlowSurfacetraitEach 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, ExitOperationtraitEmpty marker traits that name the operation (clmm.rs:162-166).
HarvestFlowSurface, CompoundFlowSurface, RebalanceFlowSurface, RebalanceForFlowSurface, ExitForFlowSurfacetraitBlanket impls over operation marker + CompleteYieldSurface + adapter trait (clmm.rs:178-223).
ClmmVenueManifesttraitEleven associated types, one per instruction (clmm.rs:230-242).
MAX_VENUE_REWARDSconstReward slot cap used by every receipt validator.
IndexRangestructlower, upper as i32.
PositionSnapshotstructpool, range, has_liquidity.
RawAssetDeltastructmint, received.
AssetDeltastructmint, received, fee.
LimitValue
MAX_VENUE_REWARDS3

dlmm.rs

ItemKindMeaning
DlmmOpenFlowSurface, DlmmResizeFlowSurface, DlmmAddFlowSurface, DlmmRemoveFlowSurface, DlmmFeeClaimFlowSurface, DlmmRewardClaimFlowSurface, DlmmCloseFlowSurfacetraitEmpty marker traits for the seven Meteora owner instructions (dlmm.rs:22-28).
DlmmHarvestFlowSurfacetraitBlanket impl: HarvestOperation + CompleteYieldSurface + ClaimAllYieldAdapter (dlmm.rs:33-40).
DlmmCompoundFlowSurfacetraitBlanket impl: CompoundOperation + CompleteYieldSurface + CompoundFlowPolicy (dlmm.rs:44-51).
DlmmKeeperRebalanceFlowAdapter, DlmmKeeperExitFlowAdaptertraitEmpty marker traits. Implemented once each, for MeteoraRebalanceFor and MeteoraExitFor (dlmm.rs:57, dlmm.rs:69).
DlmmRebalanceForFlowSurface, DlmmExitForFlowSurfacetraitBlanket impls over the two marker traits above.
DlmmVenueManifesttraitEleven associated types (dlmm.rs:79-91). Implemented for MeteoraDlmmVenue (dlmm.rs:109-121).

family.rs

ItemKindMeaning
VenueFamilyenumClmm, Dlmm, NonPosition.
PositionGeometryenumClmm { min_tick_index, max_tick_index } or Dlmm { min_bin_id, max_bin_id }.
ConformanceenumRegistered, NotApplicable.
MeteoraDlmmVenueenumEmpty venue tag. Implements DlmmVenueManifest, not ClmmVenueManifest.
DlmmVenuetraitSealed. const PROTOCOL: Protocol.
RegisteredClmmVenuetraitSealed. Requires ClmmVenueManifest. const PROTOCOL.
RegisteredDlmmVenuetraitSealed. Requires DlmmVenue + DlmmVenueManifest.
protocol_family, protocol_conformanceconst fnGenerated by venue_registry!. No wildcard arm, so a new Protocol variant fails the build.
Protocol::familyconst fnCalls protocol_family.
Protocol::position_geometryfnResolves the tick or bin limits for one venue. Covered by another layer page.

open_increase.rs

ItemKindMeaning
ActiveFlowIdentitystructauthority, sickle, protocol, protocol_program.
ActiveFlowPolicytraitactive_flow_identity, global_config, sickle_account. Supertrait of every flow adapter but claim.
OpenRequest<V>structrange plus venue fields.
OpenIdentitystructFlow identity plus pool, position, position mint, position token account, and its token program.
RawOpenFlowReceiptstructPost-CPI snapshot: keys, owners, range, liquidity flag, NFT balance.
OpenRunReceiptstructraw plus the PositionOpened event.
OpenFlowAdaptertraitVenueRequest, Prepared, open_identity, prepare_open, execute_open.
validate_open_receiptpub(crate) fnNine checks on the receipt (open_increase.rs:102-151). Reused by both rebalance runners.
run_openfnThe open runner (open_increase.rs:156-186).
IncreaseRequest<V>structliquidity, maximum_tokens, venue fields.
RawDepositDeltastructmint, debited.
RawIncreaseFlowReceiptstructTwo RawDepositDelta values.
IncreaseIdentitystructFlow identity plus pool, position, two mints, two source accounts.
IncreaseRunReceiptstructraw, the LiquidityIncreased event, intent_mode.
IncreaseFlowAdaptertraitIdentity, optional global automator, optional settings, optional action, source infos, prepare, execute.
run_increasefnThe increase runner (open_increase.rs:389-420).
shared and private helpersfnvalidate_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

ItemKindMeaning
DecreaseRequest<V>structliquidity, minimum_tokens, venue fields.
PoolDecreaseCompositionenumPrincipalOnly or PrincipalAndIncidentalYield.
IncidentalRewardDispositionenumMeasured or IncludedInPoolToken { side }. Defined in claim.rs and re-exported here, because a claim receipt needs it too.
RawIncidentalRewardDeltastructmint, received, disposition.
RawDecreaseFlowReceiptstructTwo pool deltas, the composition, a reward vector.
DecreaseIdentitystructsickle, protocol, pool, position, two mints.
DecreaseRunReceiptstructraw, the LiquidityDecreased event, the FeePolicy.
DecreaseFlowAdaptertraitdecrease_identity, prepare_decrease, execute_decrease. No ActiveFlowPolicy supertrait.
run_decreasefnThe decrease runner (decrease_close.rs:159-188).
CloseIdentitystructowner, sickle, authority, protocol, position, fee_collector.
CloseRentAccountsstructsource and receiver account infos.
CloseYieldReceiptstructexpected_mints and a RewardClaimDelta vector.
CloseFlowAdaptertraitIdentity, rent accounts, settings candidate, three closed accounts, prepare, settle_close_yield, close_empty_position.
CloseRunReceiptstructPositionClosed, reward events, fee events, optional SettingsUpdated, recovered rent, policy.
run_closefnThe close runner (decrease_close.rs:318-401).
private helpersfnvalidate_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

ItemKindMeaning
YieldFeeActionsstructpool_fees and rewards fee actions.
PoolFeeClaimReceiptstructcollected[2], fees[2].
RewardClaimDeltastructmint, collected, fee, disposition. collected is exact only when the disposition is Measured.
RewardClaimReceiptstructA reward vector. Used only by the Orca collect-rewards adapter.
AllYieldClaimReceiptstructpool plus a reward vector.
COLLECT_FEES_ACTION, COLLECT_REWARDS_ACTION, HARVEST_FEE_ACTIONS, COMPOUND_FEE_ACTIONSconstFee classes read from the table at compile time (claim.rs:158-163).
ClaimRunReceipt<R>, YieldRunReceipt<R>structThe adapter receipt plus the action or actions the runner selected.
CollectTradingFeesAdaptertraitcollect_trading_fees(request, action, remaining). One implementor: OrcaCollectFees.
CollectRewardsAdaptertraitcollect_rewards(request, action, remaining). One implementor: OrcaCollectRewards.
ClaimAllYieldAdaptertraitprepare_all_yield returns a tail. claim_all_yield runs the CPIs.
CompoundFlowPolicytraitSupertrait ClaimAllYieldAdapter. validate_compound_prerequisites, begin_compound_claim, finish_compound_claim.
run_collect_fees, run_collect_rewards, run_owner_all_yield, run_keeper_harvest, run_compoundfnThe five public claim runners (claim.rs:505-628). The all-yield body they share is private (claim.rs:547-562).
required_actionpub(crate) const fnReads a class the policy declares mandatory, and panics at compile time if it is absent. Imported by the rebalance and exit runners.
private helpersconst fnyield_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

ItemKindMeaning
OwnerRebalanceRequest<V>structnew_range, minimum_tokens, venue fields.
OwnerRebalanceIdentitystructFlow identity, owner, pool, old and new position keys, new mint and token account, mints, collector.
PositionDrainIdentitystructThe subset without reopen fields. From<OwnerRebalanceIdentity> (owner_rebalance.rs:65-76).
DrainFeeActionsstructprincipal, pool_fees, rewards fee actions.
RebalanceRewardDispositionenumMeasured or IncludedInPoolToken { side }.
RebalanceRewardDeltastructAssetDelta plus disposition.
RawDrainFlowReceiptstructOld snapshot, principal[2], pool_fees[2], expected reward mints, reward vector.
DrainRentAccountsstructsource, receiver.
RebalanceRunReceiptstructFeesCollected, pool fee events, reward events, Rebalanced, PositionOpened, settings, rent, policy.
OwnerRebalanceFlowAdaptertraitIdentity, settings candidate and mutable account, rent, closed accounts, prepare, spacing, bump, execute_drain_and_close, execute_reopen.
DrainEventGroupsstructThe 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_eventspub(crate) fnHelpers that keeper_rebalance.rs and keeper_exit.rs import.
run_owner_rebalancefnThe owner rebalance runner (owner_rebalance.rs:430-570).
private helpersfnvalidate_asset. required_action moved to policy.rs.

keeper_rebalance.rs

ItemKindMeaning
KeeperRebalanceRequest<V>structnew_range, minimum_tokens, new_position_settings_bump, venue fields.
KeeperRebalanceStatestructold_position, old_liquidity, index_spacing, current_index.
KeeperRebalanceFlowAdaptertraitIdentity, 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_rebalancefnThe keeper rebalance runner (keeper_rebalance.rs:227-375). Returns RebalanceRunReceipt.
shared and private helpersfnvalidate_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

ItemKindMeaning
KeeperExitRequest<V>structminimum_tokens, venue fields.
KeeperExitStatestructposition, liquidity, current_index.
KeeperExitRunReceiptstructFeesCollected, pool fee events, reward events, Exited, settings, rent, policy.
KeeperExitFlowAdaptertraitIdentity, 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_targetpub(crate) fnLow 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_exitfnThe keeper exit runner (keeper_exit.rs:234-357).
private helpersfnbegin_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.

TraitOrca account structRaydium account structMeteora account struct
ActiveFlowPolicyOrcaOpenPosition, 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
OpenFlowAdapterOrcaOpenPosition (adapters/orca.rs:1175)RaydiumOpenPosition (adapters/raydium.rs:1213)none
IncreaseFlowAdapterOrcaIncreaseLiquidity (adapters/orca.rs:1299)RaydiumIncreaseLiquidity (adapters/raydium.rs:1376)none
DecreaseFlowAdapterOrcaDecreaseLiquidity (adapters/orca.rs:1432)RaydiumDecreaseLiquidity (adapters/raydium.rs:1513)none
CloseFlowAdapterOrcaClosePosition (adapters/orca.rs:1561)RaydiumClosePosition (adapters/raydium.rs:1638)none
CollectTradingFeesAdapterOrcaCollectFees (adapters/orca.rs:1016)nonenone
CollectRewardsAdapterOrcaCollectRewards (adapters/orca.rs:1069)nonenone
ClaimAllYieldAdapterHarvestFor, 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)
CompoundFlowPolicyCompoundFor (compound.rs:302)RaydiumCompoundFor (compound.rs:205)MeteoraCompoundFor (compound.rs:84)
OwnerRebalanceFlowAdapterOrcaRebalancePosition (adapters/orca.rs:429)RaydiumRebalancePosition (adapters/raydium.rs:417)none
KeeperRebalanceFlowAdapterRebalanceFor (adapters/orca.rs:621)RaydiumRebalanceFor (adapters/raydium.rs:635)none
KeeperExitFlowAdapterExitFor (adapters/orca.rs:853)RaydiumExitFor (adapters/raydium.rs:940)none
ClmmVenueManifestOrcaVenue (clmm.rs:316)RaydiumVenue (clmm.rs:302)none
DlmmVenueManifestnonenoneMeteoraDlmmVenue (dlmm.rs:109)

Which runner each handler calls

RunnerOrca handlerRaydium handlerMeteora handler
run_openinstructions/orca/open_position.rs:86instructions/raydium/open_position.rs:148none
run_increaseinstructions/orca/increase_liquidity.rs:172instructions/raydium/increase_liquidity.rs:168none
run_decreaseinstructions/orca/decrease_liquidity.rs:137instructions/raydium/decrease_liquidity.rs:142none
run_closeinstructions/orca/close_position.rs:122instructions/raydium/close_position.rs:116none
run_collect_feesinstructions/orca/collect_fees.rs:121nonenone
run_collect_rewardsinstructions/orca/collect_rewards.rs:62nonenone
run_owner_all_yieldnoneinstructions/raydium/collect_fees.rs:97none
run_keeper_harvestinstructions/orca/harvest_for.rs:139instructions/raydium/harvest_for.rs:115instructions/meteora/harvest_for.rs:140
run_compoundinstructions/orca/compound_for.rs:183instructions/raydium/compound_for.rs:154instructions/meteora/compound_for.rs:140
run_owner_rebalanceinstructions/orca/rebalance_position.rs:203instructions/raydium/rebalance_position.rs:196none
run_keeper_rebalanceinstructions/orca/rebalance_for.rs:252instructions/raydium/rebalance_for.rs:226none
run_keeper_exitinstructions/orca/exit_for.rs:256instructions/raydium/exit_for.rs:244none

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.

  1. The runner reads the flow identity from the adapter.
  2. The runner checks the pause flag.
  3. The runner checks the venue program against the allowlist.
  4. The runner checks that the authority is the owner. Error Unauthorized.
  5. 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.
  6. 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. Errors InvalidRentReceiver, InvalidAccountOwner.
  7. The runner selects the Rebalance fee classes with fee_actions. All three must be present.
  8. 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.
  9. The runner checks that old settings exist exactly when the adapter has a new settings account. Error PositionSettingsMismatch.
  10. The adapter parses the remaining accounts. The runner rejects any tail. Error RewardGroupMismatch.
  11. The runner checks that the adapter's new settings bump exists exactly when old settings exist. Error PositionSettingsMismatch.
  12. The runner records the three accounts the venue must close and the rent source balance.
  13. The adapter removes all liquidity, collects fees and rewards, charges the selected fee classes, and closes the old position.
  14. The runner validates the receipt with validate_drain_receipt.
  15. The runner checks that all three venue accounts have zero lamports, with validate_closed_accounts.
  16. The runner measures the returned rent with measure_protocol_rent. It must be positive. Error InvalidProtocolAccountData.
  17. If old settings exist, the runner copies them to the new settings account through transfer_position_settings with no keeper history and builds a SettingsUpdated event.
  18. The adapter opens the new position.
  19. The runner validates the open receipt against the new position identity and the requested range, with validate_open_receipt (open_increase.rs:102-151).
  20. The runner transfers the measured rent from the Sickle to the owner when the source is the Sickle, with dispose_protocol_rent.
  21. The runner closes the old settings account to the owner.
  22. 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)

  1. validate_active_flow: read the identity, check the pause flag, check the allowlist (open_increase.rs:163, open_increase.rs:37-52).
  2. Check is_owner. Error Unauthorized (open_increase.rs:164-167).
  3. Read open_identity and check identity.flow == flow. Error InvalidProtocolAccountData (open_increase.rs:168-172).
  4. Adapter: prepare_open (open_increase.rs:174).
  5. Adapter: execute_open (open_increase.rs:175).
  6. validate_open_receipt (open_increase.rs:176).
  7. Map PositionOpened from the receipt range and return (open_increase.rs:177-185).

run_increase (open_increase.rs:389-420)

  1. validate_active_flow (open_increase.rs:397).
  2. Read increase_identity and check identity.flow == flow (open_increase.rs:398-402).
  3. validate_increase_authority (open_increase.rs:403, open_increase.rs:253-318). The runner resolves swap_authority. Error Unauthorized. An owner returns intent_mode = false. An automator needs position settings that match the position and allow the increase. Error IncreaseConsentRequired. It needs an action account. Error IntentRequired. The action's Sickle, executor, position, and ledger vaults must match. Errors IntentActionMismatch, IntentExecutorMismatch, IntentLedgerMismatch. An Exit action is rejected.
  4. Adapter: prepare_increase. The runner rejects a tail. Error InvalidProtocolAccountData (open_increase.rs:405-406).
  5. Read the two source balances. The source keys must equal the identity's source accounts (open_increase.rs:407, open_increase.rs:320-336).
  6. Adapter: execute_increase (open_increase.rs:408).
  7. Read the source balances again (open_increase.rs:409).
  8. Compute before - after per side with checked subtraction. Error MathOverflow (open_increase.rs:410, open_increase.rs:338-353).
  9. In intent mode, debit each measured amount against the action ledger (open_increase.rs:411-413, open_increase.rs:372-383).
  10. Map LiquidityIncreased from the measured amounts and return (open_increase.rs:414-419).

run_decrease (decrease_close.rs:159-188)

  1. 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).
  2. Read decrease_identity (decrease_close.rs:172).
  3. Adapter: prepare_decrease. Reject a tail. Error RewardGroupMismatch (decrease_close.rs:174-175).
  4. Adapter: execute_decrease (decrease_close.rs:176).
  5. validate_decrease_receipt (decrease_close.rs:177, decrease_close.rs:114-157).
  6. Map LiquidityDecreased and 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)

  1. 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).
  2. Read close_identity and close_rent_accounts. The receiver must equal the owner. The source must be the receiver or the Sickle. Error InvalidRentReceiver (decrease_close.rs:333-343).
  3. Resolve the settings candidate (decrease_close.rs:344-346).
  4. Read the three accounts the venue must close (decrease_close.rs:347).
  5. Adapter: prepare_close. Reject a tail. Error RewardGroupMismatch (decrease_close.rs:348-349).
  6. Adapter: settle_close_yield with the reward fee class. Then validate_close_yield (decrease_close.rs:350-351, decrease_close.rs:250-268).
  7. Record the source balance. Adapter: close_empty_position. Check all three accounts have zero lamports (decrease_close.rs:353-355).
  8. 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).
  9. Map PositionClosed, one RewardsCollected per reward, and one FeeCharged per positive reward fee (decrease_close.rs:362-387).
  10. Close the settings account if the candidate resolved (decrease_close.rs:389-391).
  11. Return (decrease_close.rs:393-400).

run_collect_fees and run_collect_rewards (claim.rs:506-535)

  1. Adapter: collect_trading_fees or collect_rewards with the constant fee class (claim.rs:514, claim.rs:530).
  2. 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)

  1. Delegate to the private run_all_yield (claim.rs:576), which calls prepare_all_yield, rejects a tail with RewardGroupMismatch (claim.rs:555-556), calls claim_all_yield with HARVEST_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)

  1. authorize_keeper_claim with KeeperClaim::Harvest (claim.rs:602).
  2. Delegate to the private run_all_yield (claim.rs:603).

run_compound (claim.rs:610-628)

  1. authorize_keeper_claim with KeeperClaim::Compound (claim.rs:618).
  2. Adapter: validate_compound_prerequisites (claim.rs:619). Every impl requires the authorization and action accounts for a non-owner. Error IntentRequired (compound.rs:87-93, compound.rs:208-214, compound.rs:305-311).
  3. Adapter: prepare_all_yield. The tail is kept (claim.rs:620).
  4. Adapter: begin_compound_claim with the tail (claim.rs:621). An owner returns no ledger. A non-owner loads the authorization, creates the action ledger for IntentAction::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. Error IntentActionMismatch (compound.rs:112-116).
  5. Adapter: claim_all_yield with COMPOUND_FEE_ACTIONS (claim.rs:622).
  6. Adapter: finish_compound_claim. A non-owner credits collected - fee per pool token and per ledger-listed reward, then writes the action account (claim.rs:623, compound.rs:388-407).
  7. Return the receipt and the actions (claim.rs:624-627).

run_keeper_rebalance (keeper_rebalance.rs:227-375)

  1. Read the flow identity, check the pause flag, check the allowlist (keeper_rebalance.rs:235).
  2. Read the identity. Check identity.flow == flow and identity.owner equals the Sickle owner (keeper_rebalance.rs:237-241).
  3. Read the intent accounts. The Instructions sysvar key must match. Error InvalidInstructionsSysvar (keeper_rebalance.rs:242-247).
  4. Read and check the rent accounts (keeper_rebalance.rs:248-252).
  5. Check is_authorized_automator with PERMISSION_REBALANCE. Error Unauthorized (keeper_rebalance.rs:254-261).
  6. require_intent_prerequisites for a non-owner. Error IntentRequired (keeper_rebalance.rs:262-267).
  7. Check the position settings identity (keeper_rebalance.rs:268-273).
  8. Adapter: validate_keeper_static_accounts (keeper_rebalance.rs:276).
  9. Select the Rebalance fee classes (keeper_rebalance.rs:277-278).
  10. Adapter: prepare_keeper_rebalance. The tail goes to the ledger (keeper_rebalance.rs:282).
  11. begin_keeper_rebalance_intent (keeper_rebalance.rs:283-293, keeper_rebalance.rs:110-188). An owner must pass an empty tail. Error RewardGroupMismatch. 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. Errors IntentActionMismatch, IntentDustPolicyExceeded, IntentLedgerMismatch.
  12. Adapter: load_keeper_rebalance_state (keeper_rebalance.rs:295).
  13. validate_rebalance_for: the live trigger and the debounce (keeper_rebalance.rs:295-302).
  14. expected_rebalance_range must equal the requested range. Error InvalidRangePlacement (keeper_rebalance.rs:303-310).
  15. validate_rebalance_progress: the anti-cycle rule (keeper_rebalance.rs:311-317).
  16. Copy the settings to the new settings account with the old range as keeper history. Build SettingsUpdated (keeper_rebalance.rs:190-221,319-329).
  17. Record the closed accounts and the rent source balance. Adapter: execute_keeper_drain_and_close (keeper_rebalance.rs:331-333).
  18. Validate the receipt, the closed accounts, and the positive rent (keeper_rebalance.rs:334-340).
  19. Adapter: execute_keeper_reopen. Validate the open receipt (keeper_rebalance.rs:342-354).
  20. Transfer the rent to the owner (keeper_rebalance.rs:355).
  21. 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).
  22. Map the events with map_events and return (keeper_rebalance.rs:366-374).

run_keeper_exit (keeper_exit.rs:234-357)

  1. Read the flow identity and is_owner. Check the pause flag with the owner exit exemption. Check the allowlist (keeper_exit.rs:242-249).
  2. Read the identity. Check the flow and the owner (keeper_exit.rs:251-255).
  3. Read the intent accounts. Check the Instructions sysvar key (keeper_exit.rs:257-262).
  4. Read and check the rent accounts (keeper_exit.rs:262-263).
  5. Check is_authorized_automator with PERMISSION_EXIT (keeper_exit.rs:266-273).
  6. require_intent_prerequisites (keeper_exit.rs:273-277).
  7. Check the position settings identity (keeper_exit.rs:278-283).
  8. Adapter: validate_keeper_exit_static_accounts (keeper_exit.rs:286).
  9. Resolve the settings candidate. It must exist. Error InvalidPositionSettings (keeper_exit.rs:287-290).
  10. Select the Exit fee classes (keeper_exit.rs:291-292).
  11. Adapter: prepare_keeper_exit, then load_keeper_exit_state. The state pool must equal the identity pool and the range must be ordered (keeper_exit.rs:294-300).
  12. validate_exit_for with the live tick (keeper_exit.rs:301-303).
  13. 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 for IntentAction::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.
  14. Record the closed accounts and the rent source balance. Adapter: execute_keeper_exit (keeper_exit.rs:316-318).
  15. The receipt's old position must equal the loaded state (keeper_exit.rs:319-322).
  16. Validate the receipt, the closed accounts, and the positive rent (keeper_exit.rs:323-329).
  17. Transfer the rent to the owner (keeper_exit.rs:333).
  18. 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).
  19. An owner closes the settings account now. A keeper leaves it for finalize_intent (keeper_exit.rs:343-348).
  20. Map the events with map_exit_events and 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 handlerRunner or trait usedBypassed
meteora_harvest_forrun_keeper_harvest with ClaimAllYieldAdapter (instructions/meteora/harvest_for.rs:140)none
meteora_compound_forrun_compound with CompoundFlowPolicy (instructions/meteora/compound_for.rs:140, compound.rs:84)none
meteora_rebalance_forClaimAllYieldAdapter methods called directly, HARVEST_FEE_ACTIONS (instructions/meteora/rebalance_for.rs:552,597)run_keeper_harvest, run_keeper_rebalance
meteora_exit_forClaimAllYieldAdapter 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 instructionsMarker 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

  1. 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 a Result. Error InvalidProtocolAccountData (owner_rebalance.rs:168-174).
  2. A rebalance or exit receipt must carry a fee no larger than the received amount for every asset. validate_asset. Error InvalidProtocolAccountData (owner_rebalance.rs:176-187).
  3. 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. Error InvalidProtocolAccountData (owner_rebalance.rs:216-228, decrease_close.rs:136-153).
  4. 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).
  5. After a close, all three venue accounts must have zero lamports. Error InvalidProtocolAccountData (owner_rebalance.rs:233-241, owner_rebalance.rs:233-241).
  6. 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).
  7. 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).
  8. 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).
  9. 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).
  10. An owner on a keeper path must pass an empty intent tail. Error RewardGroupMismatch (keeper_rebalance.rs:101-106, keeper_exit.rs:125-128).
  11. Keeper ledger credit is received - fee for principal plus pool fees per side, and the withdrawal floors apply to the gross principal (keeper.rs:50-82).
  12. A keeper rebalance must place the new range at expected_rebalance_range. Error InvalidRangePlacement (keeper_rebalance.rs:303-310).
  13. A keeper rebalance's signed dust must not exceed the settings dust. Error IntentDustPolicyExceeded (keeper_rebalance.rs:157-160).
  14. A keeper exit's signed trigger ticks and target mints must equal the settings. Error IntentActionMismatch (keeper_exit.rs:157-165).
  15. 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).
  16. 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).
  17. A new Protocol variant must appear in venue_registry! or the build fails (family.rs:146-155).
  18. A venue tag that implements RegisteredClmmVenue implements every runner adapter trait, because each manifest slot requires an adapter trait (clmm.rs:230-242, family.rs:115-117).

Tests

Test locationCovers
policy.rs:90-160Every row of the fee table.
keeper.rs:84-167Intent prerequisites plus exact gross-floor and post-fee ledger credit.
family.rs:226-314Family and conformance per protocol, type registration, geometry resolution.
open_increase.rs:422-538validate_open_receipt rejections, measured_increase_receipt, map_increase_event.
decrease_close.rs:403-694validate_decrease_receipt, run_decrease with a fake adapter, validate_close_yield, dispose_protocol_rent, validate_closed_accounts.
claim.rs:630-1126All 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-899validate_drain_receipt, same-mint reward rule, map_events, both rent paths, validate_closed_accounts.
keeper_rebalance.rs:377-520Owner-tail validation and the settings-transfer stage, including identity, history, and event flags.
keeper_exit.rs:359-504select_exit_side_and_target, map_exit_events.
programs/sickle/tests/clmm_conformance.rsThe trybuild suite. Described below.
tests/surfpool-lifecycle.tsOrca: open, increase, decrease, collect fees, close, owner rebalance, harvest, compound, rebalance, exit on a mainnet fork.
tests/surfpool-raydium-lifecycle.tsRaydium: the same set through the Raydium handlers.
tests/surfpool-t22-lifecycle.ts, tests/surfpool-t22-raydium-lifecycle.tsToken-2022 pools through the same runners.
tests/surfpool-meteora-lifecycle.tsmeteora_harvest_for, meteora_compound_for, meteora_rebalance_for, meteora_exit_for.
tests/sickle/orca.ts, orca-fork.ts, orca-lifecycle.tsOrca handlers on the legacy validator.
tests/sickle/raydium.ts, raydium-fork.tsRaydium handlers on the legacy validator.
tests/sickle/intents.tscompound_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.

FixtureKindProves
clmm_orca_manifest.rspassOrcaVenue: RegisteredClmmVenue.
clmm_raydium_manifest.rspassRaydiumVenue: RegisteredClmmVenue.
dlmm_meteora_manifest.rspassMeteoraDlmmVenue: DlmmVenueManifest.
dlmm_registered_meteora.rspassMeteoraDlmmVenue: RegisteredDlmmVenue.
dlmm_meteora_compound_surface.rspassMeteoraCompoundFor: DlmmCompoundFlowSurface.
dlmm_meteora_rebalance_surface.rspassMeteoraRebalanceFor: DlmmRebalanceForFlowSurface.
dlmm_meteora_exit_surface.rspassMeteoraExitFor: DlmmExitForFlowSurface.
clmm_manifest_rejects_meteora_dlmm.rscompile_failMeteoraDlmmVenue does not implement ClmmVenueManifest.
clmm_manifest_rejects_partial_yield.rscompile_failA HarvestFor slot with Scope = TradingFeesOnly and no ClaimAllYieldAdapter is rejected.
clmm_manifest_requires_collect_rewards.rscompile_failA manifest without the CollectRewards associated type is rejected.
clmm_manifest_requires_claim_capability.rscompile_failA reward slot cannot name the dedicated reward proof without implementing CollectRewardsAdapter.
clmm_reward_surface_rejects_fee_capability.rscompile_failA real fee-only adapter cannot use its fee proof to satisfy the reward surface.
clmm_manifest_requires_open_adapter.rscompile_failAn Open slot type without OpenFlowAdapter is rejected.
clmm_manifest_requires_increase_adapter.rscompile_failThe same for IncreaseFlowAdapter.
clmm_manifest_requires_decrease_adapter.rscompile_failThe same for DecreaseFlowAdapter.
clmm_manifest_requires_close_adapter.rscompile_failThe same for CloseFlowAdapter.
clmm_manifest_requires_owner_rebalance_adapter.rscompile_failThe same for OwnerRebalanceFlowAdapter.
clmm_manifest_requires_keeper_rebalance_adapter.rscompile_failThe same for KeeperRebalanceFlowAdapter.
clmm_manifest_requires_keeper_exit_adapter.rscompile_failThe same for KeeperExitFlowAdapter.
dlmm_manifest_requires_keeper_rebalance_adapter.rscompile_failA RebalanceFor slot type without DlmmKeeperRebalanceFlowAdapter is rejected.
dlmm_manifest_requires_keeper_exit_adapter.rscompile_failAn ExitFor slot type without DlmmKeeperExitFlowAdapter is rejected.
claim_owner_entry_rejects_keeper_struct.rscompile_failHarvestFor cannot reach run_owner_all_yield, the one all-yield claim runner without its own caller check.
CountValue
pass fixtures7
compile_fail fixtures15

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, or run_keeper_exit with a fake adapter. Only run_decrease and the four claim runners have one.
  • begin_keeper_rebalance_intent has no direct unit test.
  • compound.rs has no unit test. clmm.rs and dlmm.rs have none beyond the trybuild suite.
  • run_collect_rewards is exercised by tests/mainnet-acceptance.ts:1920 only. No Surfpool or legacy-validator test calls orca_collect_rewards.
  • No test covers the IncludedInPoolToken reward path through run_close.
  • No negative test covers validate_increase_authority on an Exit action (open_increase.rs:296) or on a ledger vault mismatch (open_increase.rs:303-316).

Simplification and audit notes

  1. L03-N003 — decrease_close.rs:270-316 and owner_rebalance.rs:243-296 implement 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 at decrease_close.rs:298-314 and owner_rebalance.rs:283-294 is the same. Kept as design work rather than duplication. decrease_close.rs has one function; owner_rebalance.rs has three. They take different identity and rent types, CloseIdentity with CloseRentAccounts against PositionDrainIdentity with DrainRentAccounts. Unifying them needs one shared identity and one shared rent type, which is a design change with no runtime effect, not a mechanical removal.
  2. L03-N005 — keeper_rebalance.rs:110-188 and keeper_exit.rs:114-196 share 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_arguments is the third check in begin_keeper_exit_intent and the fifth in begin_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.
  3. 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). The run_decrease and run_close runners own no authorization check at all; run_keeper_harvest and run_compound now do (claim.rs:238-271).
  4. L03-N012 — run_close checks the rent receiver and source at decrease_close.rs:335-343, then dispose_protocol_rent checks the same keys again at decrease_close.rs:275-279 and decrease_close.rs:288-296.
  5. L03-N013 — run_owner_rebalance calls validate_rent_accounts at owner_rebalance.rs:455 and again inside measure_protocol_rent at owner_rebalance.rs:508 via owner_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).
  6. L03-N015 — run_open checks identity.flow == flow at open_increase.rs:169-172. Both values come from the same adapter through active_flow_identity and open_identity. The same self-consistency check appears at open_increase.rs:399-402, owner_rebalance.rs:445-448, keeper_rebalance.rs:238-241, and keeper_exit.rs:252-255.
  7. L03-N018 — CollectTradingFeesAdapter (claim.rs:426-436) and CollectRewardsAdapter (claim.rs:439-449) each have one production implementor, in adapters/orca.rs:1016 and adapters/orca.rs:1069. raydium_collect_fees uses run_owner_all_yield instead (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.
  8. L03-N021 — TradingFeesOnly (clmm.rs:51) has no production use. Its only user is the trybuild fixture clmm_manifest_rejects_partial_yield.rs:80. Kept: that is its purpose. The fixture proves CompleteYieldSurface rejects a partial-yield surface, and its snapshot names the type, so removing it would delete the proof that the bound works.
  9. L03-N027 — #[inline(never)] appears on run_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), and validate_increase_authority (open_increase.rs:252). None of these sites has a comment that gives the reason. claim.rs:6-7 explains the opposite choice for the claim runners. keeper.rs:25-26 explains the Box on KeeperIntentAccounts with the SBF frame.
  10. L03-N028 — #[inline(always)] appears on run_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.
  11. L03-N033 — KeeperRebalanceFlowAdapter::position_settings_pair returns (&PositionSettings, &mut PositionSettings) from one &mut self (keeper_rebalance.rs:67). The adapter must hold two distinct settings accounts for this to compile.
  12. L03-N034 — run_keeper_exit reads is_owner at keeper_exit.rs:243 before the allowlist check, while run_keeper_rebalance reads it at keeper_rebalance.rs:262 after the automator check. The two runners order the same steps differently.
  13. L03-N035 — KeeperRebalanceState.old_liquidity (keeper_rebalance.rs:49) and KeeperExitState.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.
  14. L03-N037 — Tests: keeper_rebalance.rs has three unit tests (keeper_rebalance.rs:460-519). compound.rs has none. No fake-adapter test exists for the six runners that own the most checks. See the Gaps list above.

Resolved

  1. 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.

  2. 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 redundant has_new_position_settings method 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 by transfer_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.

  3. L03-N019 — The CollectFees and CollectRewards slots now require an associated ClaimFlowCapability proof (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 names AllYieldClaim in both slots (clmm.rs:273-280). The sealed proof exists only when the account type implements CollectTradingFeesAdapter, CollectRewardsAdapter, or ClaimAllYieldAdapter, so Raydium does not falsely implement an Orca adapter it never executes. The compile-fail fixture clmm_manifest_requires_claim_capability.rs:1-30 supplies a synthetic reward surface with no adapter and is rejected at the associated proof type; clmm_reward_surface_rejects_fee_capability.rs:1-26 proves 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.

  4. 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 produces E0080 during cargo check. The former runtime InvalidProtocolAccountData paths are gone. Resolved on 2026-09-17.

  5. 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.

  6. L03-N026 — run_keeper_rebalance's contiguous settings-transfer and SettingsUpdated construction moved unchanged into transfer_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. Mapping automate_rewards from auto_rebalance makes 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.

  7. L03-R001 — Conformance::Pending, PendingClmmVenue, PendingDlmmVenue, and protocol_conformance (family.rs:68-72, family.rs:100-103, family.rs:113-115, family.rs:175-183) have no implementor and no caller outside family.rs. Both pending_* registry lists are empty (family.rs:192, family.rs:196). family.rs:64-66 states 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.

  1. L03-R002 — PositionDrainIdentity and the generic I: Into<PositionDrainIdentity> requirement on validate_drain_receipt, validate_rent_accounts, and measure_protocol_rent (owner_rebalance.rs:193-199, owner_rebalance.rs:251-257, owner_rebalance.rs:279-286) serve two callers: OwnerRebalanceIdentity and PositionDrainIdentity itself. 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.

  1. L03-R003 — open_increase.rs:37-47 defines validate_active_flow as a private function. owner_rebalance.rs:412-416, keeper_rebalance.rs:213-217, and keeper_exit.rs:290-297 repeat 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_exit checks the pause flag with assert_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.

  2. L03-R004 — decrease_close.rs:243-245, owner_rebalance.rs:169-171, and claim.rs:16-21 each define a required_action helper. 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.rs beside position_fee_policy, because both are fee policy. The third is not a third definition of the same idea. claim.rs needs the value in a const context, which cannot return a Result, so it panics instead. The new helper's doc comment says so.

  3. L03-R005 — decrease_close.rs:267-275 and owner_rebalance.rs:238-246 define validate_closed_accounts with identical bodies. Resolved in this pass.

    The bodies were byte-identical apart from visibility. decrease_close.rs imports the owner_rebalance.rs one, which is the pattern validate_active_flow and RawOwnerRebalanceReceipt already follow. A module holding two functions would add more surface than it removes.

  4. L03-R006 — owner_rebalance.rs:315-366 and keeper_exit.rs:207-258 build FeesCollected, the four pool FeeCharged events, and the reward events with identical code. Only the final struct differs, Rebalanced versus Exited. Resolved in this pass.

    map_drain_events builds the three shared groups from the narrower PositionDrainIdentity, which the rebalance identity already converts into. Each runner keeps only its own closing event, Rebalanced against Exited. A unit test pins the shared groups directly rather than through the two wrappers.

  5. L03-R007 — compound.rs:27-35, compound.rs:158-166, and compound.rs:260-268 are three identical validate_compound_prerequisites bodies. Each repeats require_intent_prerequisites from keeper.rs:35-47 without 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.

  6. L03-R008 — compound.rs:25-152, compound.rs:154-252, and compound.rs:254-379 differ only in field names, the pool and position keys, the DLMM load function, and the Raydium impl's lack of a reward baseline loop. The finish_compound_claim bodies at compound.rs:114-151 and compound.rs:338-378 are identical except for field names. Resolved in this pass.

    The three bodies now call credit_pool_deltas, and the two that need it call credit_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-511 reports it with zero amounts and its delta is already inside receipt.pool. A distinct reward mint is not a ledger entry, so the entry_index guard skips it. credit_drain of 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.

  7. L03-R009 — owner_rebalance.rs:325 sets FeesCollected.action to actions.principal, while the fee charged on the same two amounts uses actions.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.action now carries actions.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 in RELEASE_RUNBOOK.md under Consumer-visible changes.

    No integration test asserted this field on a rebalance or an exit. Both tests that assert feesCollected.data.action cover 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.

  8. L03-R010 — keeper_rebalance.rs:97-102 is a named function validate_owner_intent_tail. keeper_exit.rs:126-129 inlines 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 the keeper_exit site returns Ok(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 call validate_owner_intent_tail and then take their own branch, and the three compound policies do the same, so one definition serves four call sites.

  9. L03-R011 — claim.rs:122-124 states that harvest rejects a tail and compound assigns it to intent baselines. On the owner path every begin_compound_claim returns 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_claim policies now call validate_owner_intent_tail before 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.

  10. L03-R012 — decrease_close.rs:2-6 says 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.

  11. L03-R013 — claim.rs:114-116 says the ClaimAllYieldAdapter capability's absence "caught the live Orca keeper gap". clmm.rs:1-9 and dlmm.rs:1-8 carry 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.

  12. L03-R014 — clmm.rs:3-8 states that claim, harvest, and compound handlers route through super::claim. instructions/meteora/rebalance_for.rs:496,562 and instructions/meteora/exit_for.rs:392,437 call prepare_all_yield and claim_all_yield directly and skip run_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.

  13. L03-R015 — owner_rebalance.rs:15 and keeper_rebalance.rs:14 import transfer_position_settings from instructions/orca/rebalance_position.rs:390. A shared runner depends on one venue's handler file. The helper's parameter is named whirlpool (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.

  14. 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 of position_fee_policy(Rebalance) or position_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 the const fn in claim.rs, so a change to position_fee_policy that 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.

  1. L03-R017 — keeper_exit.rs imports RawOwnerRebalanceReceipt, OwnerRebalanceRentAccounts, and RebalanceFeeActions for an exit (keeper_exit.rs:29-33). run_keeper_rebalance returns OwnerRebalanceRunReceipt (keeper_rebalance.rs:214). The names say "owner rebalance" for keeper and exit flows. Resolved in this pass. The four became RawDrainFlowReceipt, DrainRentAccounts, DrainFeeActions, and RebalanceRunReceipt: 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, YieldFeeActions and OpenRunReceipt, and "drain" is the word this codebase already uses for the step the three flows share. RebalanceRunReceipt keeps "rebalance" because both of its users are rebalances; the exit has its own KeeperExitRunReceipt. The deferral reasoning was checked before it was acted on, and it was partly wrong. The rename plus cargo fmt changes 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.
  • 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, for PositionGeometry and PositionSettings
  • Layer 8, Keeper authorization: 08-intent.md, for ActionIntent and the finalizer
  • Layer 9, Fees: 09-fees.md

Source: docs/dev/layers/03-shared-runners.md in vfat-io/sickle-programs, vendored here by its sync workflow. Edit the markdown there; this page is rebuilt from it on deploy.