Sickle Sol

Venue bindings

This layer builds the CPI instructions the program sends to Orca, Raydium, Meteora, and Jupiter, and reads the raw accounts those programs own.

The bindings are written by hand. No venue SDK crate is a dependency. Each binding serializes instruction data with Borsh, orders the account metas, and reads fields out of raw account bytes at fixed offsets. The venue program ID is never a constant in this layer. The caller passes the program from the allowlist (GlobalConfig protocol slots) on every call (programs/sickle/src/orca.rs:7-8, programs/sickle/src/raydium.rs:7-8). The helpers the bindings share live in orca.rs, and raydium.rs and meteora.rs import them from there (/programs/sickle/src/orca.rs:10-13).

Files

PathLinesPurpose
programs/sickle/src/orca.rs1803Orca Whirlpools instruction builders, Whirlpool and Position parsers, transfer-hook slice encoding, and the shared helpers.
programs/sickle/src/raydium.rs1422Raydium CLMM instruction builders, PoolState and PersonalPositionState parsers, reward groups.
programs/sickle/src/meteora.rs2436Meteora DLMM instruction builders, LbPair, PositionV2, BinArray, bitmap parsers, range checks.
programs/sickle/src/instructions/jupiter/cpi.rs282Jupiter route forwarder, token-account owner probe, post-swap vault check.
tests/fixtures/meteora-dlmm-layout.json559Cross-language offset contract for the Meteora accounts. meteora.rs asserts its constants against it.
tests/fixtures/clmm-mainnet-accounts.json78Verbatim mainnet Whirlpool, Position, PoolState, and PersonalPositionState accounts the Orca and Raydium parser tests read.

Responsibilities

  • Hold the 8-byte instruction discriminators of every venue instruction the program calls.
  • Serialize each venue instruction's arguments in the venue's Borsh layout.
  • Order the account metas of each venue instruction with the correct signer and writable flags.
  • Check the account discriminator and length before it reads a venue account.
  • Read tick spacing, live tick, position range, and liquidity out of raw pool and position bytes.
  • Read the initialized reward slots of a pool in raw slot order.
  • Encode the transfer-hook remaining-account descriptor for the Orca *_v2 instructions.
  • Derive Meteora bin-array and bitmap-extension addresses and check the bin-array tail of a range.
  • Forward an opaque Jupiter route and mark the Sickle PDA as a read-only signer.
  • Hold the seeds of the venue PDAs the handlers derive.
  • Hold the byte readers, the reward-slot scanner, the tick-range check, and the Memo program address that the venue bindings share.

The layer does not check the allowlist, the pause flag, or the caller role. It does not measure balances or charge fees. Those checks live in the handlers and the shared runners.

Legend for account tables

MarkMeaning
SThe account signs.
WThe account is writable.
noneRead-only and not a signer.

Public items

programs/sickle/src/orca.rs

The Orca binding has no program-ID constant. It holds one fixed foreign address and one seed. The tick bounds and the Memo program address are defined here once; the Raydium and Meteora bindings re-export them.

ItemKindValueMeaning
ORCA_POSITION_SEEDconstb"position"Seed of the Orca position PDA [b"position", position_mint].
MIN_TICK_INDEXconst-443636Lowest allowed tick. Shared with Raydium as MIN_TICK.
MAX_TICK_INDEXconst443636Highest allowed tick. Shared with Raydium as MAX_TICK.
WP_NFT_UPDATE_AUTHconst3axbTs2z5GBy6usVbNVoqEgZMng3vZvMnAoX29BFfwhrOrca's position-NFT metadata update authority. Passed as metadata_update_auth on open.
MEMO_PROGRAM_IDconstMemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHrThe Memo program. A fixed account of every Orca *_v2 instruction. The one definition.
WHIRLPOOL_REWARD_SLOT_COUNTconst3Number of reward slots in a Whirlpool.
ACCOUNTS_TYPE_TRANSFER_HOOK_Aconst0Borsh tag of the Orca AccountsType::TransferHookA slice.
ACCOUNTS_TYPE_TRANSFER_HOOK_Bconst1Borsh tag of TransferHookB.
ACCOUNTS_TYPE_TRANSFER_HOOK_REWARDconst2Borsh tag of TransferHookReward, used by collect_reward_v2.

Shared helpers

These items are pub(crate) unless marked. raydium.rs imports all of them and meteora.rs imports the readers and the Memo address.

ItemKindMeaning
read_u16, read_i32, read_u64, read_i64, read_u128, read_pubkeyfnRead one little-endian field at an offset. Every parser checks the length first, so the Result is one error surface, not a reachable failure.
RewardSlotLayoutstructWhere a pool keeps its reward-slot array: discriminator, array offset, slot count, slot stride, and the mint and vault offsets inside a slot.
for_each_initialized_reward_slotfnChecks the discriminator and the full array extent, then calls a closure with (index, mint, vault) for every slot whose mint is not the default pubkey.
validate_tick_rangefn, pubChecks lower < upper and both ticks inside the global tick limits. Returns InvalidTickRange. Re-exported by raydium.rs.
test_supportmod, test-onlyanchor_discriminator, keys<N>, the mainnet fixture loader, and the float tick helper the other bindings' unit tests import.

Orca discriminators

Every Orca discriminator is written as an 8-byte literal. The doc comment names the preimage sha256("global:<name>")[..8] or sha256("account:<Name>")[..8]. One table-driven unit test recomputes every instruction discriminator from its preimage (/programs/sickle/src/orca.rs:918-943) and one recomputes both account discriminators (/programs/sickle/src/orca.rs:954-967).

ConstantPreimageBytes
OPEN_POSITION_WITH_TOKEN_EXTENSIONS_DISCRIMINATORglobal:open_position_with_token_extensions[212, 47, 95, 92, 114, 102, 131, 250]
INCREASE_LIQUIDITY_V2_DISCRIMINATORglobal:increase_liquidity_v2[133, 29, 89, 223, 69, 238, 176, 10]
DECREASE_LIQUIDITY_V2_DISCRIMINATORglobal:decrease_liquidity_v2[58, 127, 188, 62, 79, 82, 196, 96]
COLLECT_FEES_V2_DISCRIMINATORglobal:collect_fees_v2[207, 117, 95, 191, 229, 180, 226, 15]
COLLECT_REWARD_V2_DISCRIMINATORglobal:collect_reward_v2[177, 107, 37, 180, 160, 19, 49, 209]
CLOSE_POSITION_WITH_TOKEN_EXTENSIONS_DISCRIMINATORglobal:close_position_with_token_extensions[1, 182, 135, 59, 155, 25, 99, 223]
WHIRLPOOL_ACCOUNT_DISCRIMINATORaccount:Whirlpool[63, 149, 209, 12, 225, 128, 99, 9]
POSITION_ACCOUNT_DISCRIMINATORaccount:Position[170, 188, 143, 228, 122, 64, 247, 208]

The Orca and Raydium increase_liquidity_v2 and decrease_liquidity_v2 discriminators are the same 8 bytes. Both programs use the same Anchor instruction name, so INCREASE_LIQUIDITY_V2_DISCRIMINATOR and DECREASE_LIQUIDITY_V2_DISCRIMINATOR are defined identically in both bindings. The program sends only the *_v2 forms; the v1 builders were removed in #171.

Orca argument structs

Every field is little-endian. data() returns the discriminator followed by the fields.

StructBorsh layout after the discriminatorLength
OpenPositionWithTokenExtensionsArgstick_lower_index: i32, tick_upper_index: i32, with_token_metadata: u817 bytes
IncreaseLiquidityV2Argsliquidity_amount: u128, token_max_a: u64, token_max_b: u64, then remaining_accounts_info(hook_slices)41 bytes, 47 with two slices
DecreaseLiquidityV2Argsliquidity_amount: u128, token_min_a: u64, token_min_b: u64, then remaining_accounts_info(hook_slices)41 bytes, 47 with two slices

The function remaining_accounts_info encodes the Orca Option<RemainingAccountsInfo> argument.

InputBytes
no slices[0]
slices [(type, len), ...][1], u32 slice count, then one (u8, u8) per slice

Slice order equals the order of the appended hook account metas. Orca consumes that many trailing accounts per slice (/programs/sickle/src/orca.rs:126-129).

Orca instruction builders

Each builder returns an Instruction with the caller-supplied program ID. The Sickle PDA signs as position_authority through invoke_signed in the handler.

open_position_with_token_extensions_ix. Data: OpenPositionWithTokenExtensionsArgs.

#AccountFlags
1funderS W
2sickle (position owner)
3positionW
4position_mintS W
5position_token_accountW
6whirlpool
7token_2022_program
8system_program
9associated_token_program
10metadata_update_auth

modify_liquidity_v2_ix. Shared layout for increase_liquidity_v2 and decrease_liquidity_v2. Data: caller-supplied. Hook account metas trail account 15.

#AccountFlags
1whirlpoolW
2token_program_a
3token_program_b
4memo_program
5position_authorityS
6positionW
7position_token_account
8token_mint_a
9token_mint_b
10token_owner_account_aW
11token_owner_account_bW
12token_vault_aW
13token_vault_bW
14tick_array_lowerW
15tick_array_upperW
16+hook accountsas passed

collect_fees_v2_ix. Data: discriminator, then remaining_accounts_info(hook_slices). Hook account metas trail account 13.

#AccountFlags
1whirlpool
2position_authorityS
3positionW
4position_token_account
5token_mint_a
6token_mint_b
7token_owner_account_aW
8token_vault_aW
9token_owner_account_bW
10token_vault_bW
11token_program_a
12token_program_b
13memo_program
14+hook accountsas passed

collect_reward_v2_ix. Data: discriminator, reward_index: u8, then remaining_accounts_info. The builder derives the single TransferHookReward slice from the hook account count.

#AccountFlags
1whirlpool
2position_authorityS
3positionW
4position_token_account
5reward_owner_accountW
6reward_mint
7reward_vaultW
8reward_token_program
9memo_program
10+hook accountsas passed

close_position_with_token_extensions_ix. Data: discriminator only. Orca sends all rent to receiver.

#AccountFlags
1position_authorityS
2receiverW
3positionW
4position_mintW
5position_token_accountW
6token_2022_program

Orca account parsers

Every parser checks the data length and the account discriminator first. It returns InvalidProtocolAccountData on either failure. The offsets are private constants written as sums of the named field sizes, and a unit test checks that each sum equals its documented value (/programs/sickle/src/orca.rs:1024-1040). A second test lays a Whirlpool out field by field and checks that reward_infos lands on the constant (/programs/sickle/src/orca.rs:1042-1082).

ParserAccountFieldOffsetTypeHow the offset is written
parse_whirlpool_tick_spacingWhirlpooltick_spacing41u16sum 8 + 32 + 1
parse_whirlpool_current_tick_indexWhirlpooltick_current_index81i32sum of 9 field sizes
parse_whirlpool_reward_slotsWhirlpoolreward_infos[3]269arraytick-current offset + 10 named field sizes
one WhirlpoolRewardInfo128stridesum of 5 named field sizes
mint in a slot+0pubkeyliteral
vault in a slot+32pubkeyprevious offset + 32
parse_position_statePositionwhirlpool8pubkeyliteral
liquidity72u128sum 8 + 32 + 32
tick_lower_index88i32previous offset + 16
tick_upper_index92i32previous offset + 4

parse_position_state returns a PositionState with the four fields above. parse_whirlpool_reward_slots describes reward_infos as a RewardSlotLayout and collects a WhirlpoolRewardSlot list with index, mint, and vault from the shared scanner. The scanner skips a slot whose mint is the default pubkey and keeps the raw slot index, and its length check covers all 3 slots before any read.

validate_tick_range checks lower < upper and both ticks inside the global tick limits. It returns InvalidTickRange.

The Orca binding derives no tick-array address. The handler passes tick_array_lower and tick_array_upper as accounts. The hook accounts come from the handler through take_hook_accounts and hook_account_metas in fees.rs (programs/sickle/src/fees.rs:481-560). The binding only encodes the slice descriptor and appends the metas.

programs/sickle/src/raydium.rs

The Raydium binding has no program-ID constant. It holds two seeds and re-exports the tick bounds, the tick-range check, and the Memo program address from orca.rs under Raydium's names.

ItemKindValueMeaning
RAYDIUM_POSITION_SEEDconstb"position"Seed of PersonalPositionState PDA [b"position", nft_mint].
RAYDIUM_TICK_ARRAY_BITMAP_SEEDconstb"pool_tick_array_bitmap_extension"Seed of the TickArrayBitmapExtension PDA [seed, pool_state].
MIN_TICKre-export-443636Lowest allowed tick. orca::MIN_TICK_INDEX.
MAX_TICKre-export443636Highest allowed tick. orca::MAX_TICK_INDEX.
validate_tick_rangere-exportThe shared range check. orca::validate_tick_range.
MEMO_PROGRAM_IDre-exportMemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHrThe Memo program, a fixed account of decrease_liquidity_v2.

Raydium discriminators

Every Raydium discriminator is a literal. The unit tests recompute each one with sha256 (/programs/sickle/src/raydium.rs:649-680).

ConstantPreimageBytes
OPEN_POSITION_WITH_TOKEN22_NFT_DISCRIMINATORglobal:open_position_with_token22_nft[77, 255, 174, 82, 125, 29, 201, 46]
INCREASE_LIQUIDITY_V2_DISCRIMINATORglobal:increase_liquidity_v2[133, 29, 89, 223, 69, 238, 176, 10]
DECREASE_LIQUIDITY_V2_DISCRIMINATORglobal:decrease_liquidity_v2[58, 127, 188, 62, 79, 82, 196, 96]
CLOSE_POSITION_DISCRIMINATORglobal:close_position[123, 134, 81, 0, 49, 68, 98, 98]
POOL_STATE_ACCOUNT_DISCRIMINATORaccount:PoolState[247, 237, 227, 245, 215, 195, 222, 70]
PERSONAL_POSITION_ACCOUNT_DISCRIMINATORaccount:PersonalPositionState[70, 111, 150, 126, 230, 15, 25, 117]

Raydium argument structs

StructBorsh layout after the discriminatorLength
OpenPositionWithToken22NftArgstick_lower_index: i32, tick_upper_index: i32, tick_array_lower_start_index: i32, tick_array_upper_start_index: i32, liquidity: u128, amount_0_max: u64, amount_1_max: u64, with_metadata: u8, base_flag: Option<bool>58 or 59 bytes
IncreaseLiquidityV2Argsliquidity: u128, amount_0_max: u64, amount_1_max: u64, base_flag: Option<bool>41 or 42 bytes
DecreaseLiquidityV2Argsliquidity: u128, amount_0_min: u64, amount_1_min: u6440 bytes

extend_option_bool writes None as [0] and Some(b) as [1, b].

Note: The open struct declares its fields in the wire order, the four tick fields first, unlike the liquidity-first increase and decrease structs. The comment records the live-program failure of a liquidity-first encoding (/programs/sickle/src/raydium.rs:264-273).

The handler opens every position with liquidity = 0 and base_flag = None. A separate increase funds it (/programs/sickle/src/raydium.rs:275-279).

Raydium instruction builders

open_position_with_token22_nft_ix. Data: OpenPositionWithToken22NftArgs. The optional bitmap extension appends one writable meta.

#AccountFlags
1payerS W
2sickle (NFT owner)
3position_nft_mintS W
4position_nft_accountW
5pool_stateW
6protocol_position
7tick_array_lowerW
8tick_array_upperW
9personal_positionW
10token_account_0W
11token_account_1W
12token_vault_0W
13token_vault_1W
14rent
15system_program
16token_program
17associated_token_program
18token_program_2022
19vault_0_mint
20vault_1_mint
21tick_array_bitmap_extensionW (optional)

increase_liquidity_v2_ix. Data: IncreaseLiquidityV2Args.

#AccountFlags
1nft_owner (Sickle)S
2nft_account
3pool_stateW
4protocol_position
5personal_positionW
6tick_array_lowerW
7tick_array_upperW
8token_account_0W
9token_account_1W
10token_vault_0W
11token_vault_1W
12token_program
13token_program_2022
14vault_0_mint
15vault_1_mint
16tick_array_bitmap_extensionW (optional)

decrease_liquidity_v2_ix. Data: DecreaseLiquidityV2Args. The optional bitmap comes first in the remaining tail, then one [vault W, recipient W, mint] group per initialized reward slot in slot order.

#AccountFlags
1nft_owner (Sickle)S
2nft_account
3personal_positionW
4pool_stateW
5protocol_position
6token_vault_0W
7token_vault_1W
8tick_array_lowerW
9tick_array_upperW
10recipient_token_account_0W
11recipient_token_account_1W
12token_program
13token_program_2022
14memo_program
15vault_0_mint
16vault_1_mint
17tick_array_bitmap_extensionW (optional)
18+reward groupsW, W, none per group

Note: personal_position comes before pool_state here, and the vaults come before the tick arrays. The increase builder has the opposite order (/programs/sickle/src/raydium.rs:545-549).

close_position_ix. Data: discriminator only. Raydium sends all rent to nft_owner, so the Sickle PDA is writable.

#AccountFlags
1nft_owner (Sickle)S W
2position_nft_mintW
3position_nft_accountW
4personal_positionW
5system_program
6token_program

Raydium account parsers

PoolState is #[repr(C, packed)], so the offsets are packed byte sums (/programs/sickle/src/raydium.rs:101-105). A unit test checks that each sum equals its documented value (/programs/sickle/src/raydium.rs:1269-1283), and a second lays the account out field by field and checks that reward_infos lands on the constant (/programs/sickle/src/raydium.rs:1286-1334).

ParserAccountFieldOffsetTypeHow the offset is written
parse_pool_tick_spacingPoolStatetick_spacing235u16sum 8 + 1 + 32 * 7 + 1 + 1
parse_pool_current_tickPoolStatetick_current269i32previous + 2 + 16 + 16
parse_pool_reward_slotsPoolStatereward_infos[3]397arraytick-current offset + 11 named field sizes
token_mint+57pubkeysum of 7 named field sizes
token_vault+89pubkeyprevious + 32
one RewardInfo169stridevault offset + 32 + 32 + 16
parse_personal_position_statePersonalPositionStatenft_mint9pubkeysum 8 + 1
pool_id41pubkeyprevious + 32
tick_lower_index73i32previous + 32
tick_upper_index77i32previous + 4
liquidity81u128previous + 4

parse_personal_position_state returns a RaydiumPositionState with the five fields. parse_pool_reward_slots describes reward_infos as a RewardSlotLayout and collects a RaydiumRewardSlot list of mint and vault from the shared scanner in orca.rs, which skips a slot whose mint is the default pubkey. The Raydium slot does not carry the slot index. RaydiumRewardGroupKeys is the vault, recipient, mint triple that the decrease builder appends.

validate_tick_range is the shared check re-exported from orca.rs.

The Raydium binding derives no tick-array address. The caller supplies tick_array_lower_start_index and tick_array_upper_start_index as arguments and Raydium re-validates them (/programs/sickle/src/raydium.rs:264-273). The handler derives the bitmap PDA from RAYDIUM_TICK_ARRAY_BITMAP_SEED (programs/sickle/src/venue/adapters/raydium_flows.rs:1378-1394).

programs/sickle/src/meteora.rs

The Meteora binding has no program-ID constant. The layout fixture names the program LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo (tests/fixtures/meteora-dlmm-layout.json:23). Every public layout constant is a literal that the fixture test asserts.

ItemKindValueMeaning
BIN_ARRAY_SEEDconstb"bin_array"Seed of [b"bin_array", lb_pair, index: i64 LE].
BIN_ARRAY_BITMAP_EXTENSION_SEEDconstb"bitmap"Seed of [b"bitmap", lb_pair].
DLMM_EVENT_AUTHORITY_SEEDconstb"__event_authority"Seed of Meteora's Anchor event authority.
MEMO_PROGRAM_IDre-exportMemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHrThe Memo program. orca::MEMO_PROGRAM_ID.
BIN_ARRAY_BITMAP_SIZEconst512Inline bitmap covers bin-array indexes -512 to 511.
BASIS_POINT_MAXconst10000Maximum bps_to_remove.
DEFAULT_BIN_PER_POSITIONconst70Bins in the base PositionV2.
POSITION_MAX_LENGTHconst1400Maximum position width in bins.
MAX_RESIZE_LENGTHconst91Maximum length_to_add per resize.
MAX_BIN_PER_ARRAYconst70Bins per BinArray.
DLMM_STRATEGY_TYPE_COUNTconst9Number of Meteora strategy enum variants.
KNOWN_LB_PAIR_VERSIONconst1Highest accepted LbPair.version.
KNOWN_POSITION_V2_VERSIONconst1Highest accepted PositionV2.version.
KNOWN_BIN_ARRAY_VERSIONconst3Highest accepted BinArray.version.
LB_PAIR_STATUS_ENABLEDconst0LbPair.status value the program accepts.
LB_PAIR_FUNCTION_UNDETERMINEDconst0functionType value: undetermined.
LB_PAIR_FUNCTION_LIQUIDITY_MININGconst1functionType value: liquidity mining.
LB_PAIR_FUNCTION_LIMIT_ORDERconst2functionType value: limit order. Named by the tests and a doc comment only.
private per-bin constantsconst16, 48, 32, 48, 32, 16, 16, 64The PositionV2 per-bin strides and pending-field offsets. All asserted against the fixture.
EMPTY_REMAINING_ACCOUNTS_INFOconst[0, 0, 0, 0]Private. The empty RemainingAccountsInfo every range builder writes.

Meteora discriminators

Every instruction discriminator is recomputed from its preimage in instruction_discriminators_data_and_account_metas_match_idl (meteora.rs:1754-1781). Every account discriminator is recomputed there too (meteora.rs:1782-1797) and compared to the fixture in constants_match_the_shared_layout_contract (meteora.rs:2123-2390).

ConstantPreimageBytes
INITIALIZE_POSITION2_DISCRIMINATORglobal:initialize_position2[143, 19, 242, 145, 213, 15, 104, 115]
INCREASE_POSITION_LENGTH_DISCRIMINATORglobal:increase_position_length[80, 83, 117, 211, 66, 13, 33, 149]
CLOSE_POSITION_IF_EMPTY_DISCRIMINATORglobal:close_position_if_empty[59, 124, 212, 118, 91, 152, 110, 157]
CLAIM_FEE2_DISCRIMINATORglobal:claim_fee2[112, 191, 101, 171, 28, 144, 127, 187]
CLAIM_REWARD2_DISCRIMINATORglobal:claim_reward2[190, 3, 127, 119, 178, 87, 157, 183]
REMOVE_LIQUIDITY_BY_RANGE2_DISCRIMINATORglobal:remove_liquidity_by_range2[204, 2, 195, 145, 53, 145, 145, 205]
ADD_LIQUIDITY_BY_STRATEGY2_DISCRIMINATORglobal:add_liquidity_by_strategy2[3, 221, 149, 218, 111, 141, 118, 213]
LB_PAIR_ACCOUNT_DISCRIMINATORaccount:LbPair[33, 11, 49, 98, 181, 101, 177, 13]
POSITION_V2_ACCOUNT_DISCRIMINATORaccount:PositionV2[117, 176, 212, 199, 245, 180, 133, 182]
BIN_ARRAY_ACCOUNT_DISCRIMINATORaccount:BinArray[92, 142, 92, 220, 5, 148, 70, 181]
BIN_ARRAY_BITMAP_EXTENSION_ACCOUNT_DISCRIMINATORaccount:BinArrayBitmapExtension[80, 111, 124, 113, 55, 237, 18, 5]

Meteora argument layouts

Meteora's RemainingAccountsInfo { slices: Vec<_> } is not optional. The four range builders always write the empty vector as the 4 zero bytes of EMPTY_REMAINING_ACCOUNTS_INFO (meteora.rs:129-134). This differs from the Orca Option encoding, where the empty case is 1 zero byte.

BuilderBorsh layout after the discriminatorLength
initialize_position2_ixlower_bin_id: i32, width: i3216 bytes
increase_position_length_ixlength_to_add: u16, side: u811 bytes
close_position_if_empty_ixnone8 bytes
claim_fee2_ixmin_bin_id: i32, max_bin_id: i32, u32 020 bytes
claim_reward2_ixreward_index: u64, min_bin_id: i32, max_bin_id: i32, u32 028 bytes
remove_liquidity_by_range2_ixfrom_bin_id: i32, to_bin_id: i32, bps_to_remove: u16, u32 022 bytes
add_liquidity_by_strategy2_ixamount_x: u64, amount_y: u64, active_id: i32, max_active_bin_slippage: i32, min_bin_id: i32, max_bin_id: i32, strategy_type: u8, [u8; 64], u32 0109 bytes

DlmmLiquidityParameterByStrategy is the argument struct of the add. It derives AnchorSerialize and AnchorDeserialize because it is an instruction argument of the program itself (meteora.rs:186-202, programs/sickle/src/lib.rs:153-158). The builder writes it with that derived serializer; the struct's declaration order is the wire order (meteora.rs:1067).

Meteora instruction builders

Every Meteora builder appends event_authority and the DLMM program itself as the last two fixed accounts. dlmm_event_authority derives the authority from DLMM_EVENT_AUTHORITY_SEED (meteora.rs:784-786). The range builders append one writable meta per BinArray in the caller-supplied order.

initialize_position2_ix (meteora.rs:789-817).

#AccountFlags
1payerS W
2positionS W
3lb_pair
4owner (Sickle)S
5system_program
6event_authority
7dlmm_program

increase_position_length_ix (meteora.rs:820-848).

#AccountFlags
1funderS W
2lb_pair
3positionW
4owner (Sickle)S
5system_program
6event_authority
7dlmm_program

close_position_if_empty_ix (meteora.rs:850-868).

#AccountFlags
1positionW
2sender (Sickle)S
3rent_receiverW
4event_authority
5dlmm_program

claim_fee2_ix (meteora.rs:871-925).

#AccountFlags
1lb_pairW
2positionW
3sender (Sickle)S
4reserve_xW
5reserve_yW
6user_token_xW
7user_token_yW
8token_x_mint
9token_y_mint
10token_program_x
11token_program_y
12memo_program
13event_authority
14dlmm_program
15+bin arraysW

claim_reward2_ix (meteora.rs:928-976).

#AccountFlags
1lb_pairW
2positionW
3sender (Sickle)S
4reward_vaultW
5reward_mint
6user_token_accountW
7token_program
8memo_program
9event_authority
10dlmm_program
11+bin arraysW

remove_liquidity_by_range2_ix (meteora.rs:979-1039). Account 3 is writable even when the key is the DLMM program sentinel (meteora.rs:1012-1014).

#AccountFlags
1positionW
2lb_pairW
3bin_array_bitmap_extensionW
4user_token_xW
5user_token_yW
6reserve_xW
7reserve_yW
8token_x_mint
9token_y_mint
10sender (Sickle)S
11token_program_x
12token_program_y
13memo_program
14event_authority
15dlmm_program
16+bin arraysW

add_liquidity_by_strategy2_ix (meteora.rs:1042-1099). Same shape as the remove builder without the memo program.

#AccountFlags
1positionW
2lb_pairW
3bin_array_bitmap_extensionW
4user_token_xW
5user_token_yW
6reserve_xW
7reserve_yW
8token_x_mint
9token_y_mint
10sender (Sickle)S
11token_program_x
12token_program_y
13event_authority
14dlmm_program
15+bin arraysW

Meteora account parsers

Every Meteora parser requires an exact data length. Every parser except the bitmap one also requires version <= KNOWN_*_VERSION. The offsets are literals. The unit test constants_match_the_shared_layout_contract compares every one of them to the fixture (meteora.rs:2123-2390). The fixture was checked against mainnet accounts at slot 442674353 (tests/fixtures/meteora-dlmm-layout.json:25).

ParserAccountFieldOffsetTypeIn fixture assert
parse_lb_pairLbPair, 904 bytesparameters.min_bin_id24i32yes
parameters.max_bin_id28i32yes
parameters.function_type35u8yes
active_id76i32yes
status82u8yes
token_x_mint88pubkeyyes
token_y_mint120pubkeyyes
reserve_x152pubkeyyes
reserve_y184pubkeyyes
reward_infos[2], stride 144264arrayyes
RewardInfo.mint, RewardInfo.vault+0, +32pubkeyyes
version882u8yes
parse_position_v2PositionV2, 8120 + 112 per bin above 70lb_pair8pubkeyyes
owner40pubkeyyes
liquidity_shares[70], stride 1672u128yes
reward_infos[70], stride 48, pending at +321192structyes
fee_infos[70], stride 48, pending at +324552structyes
lower_bin_id7912i32yes
upper_bin_id7916i32yes
operator7960pubkeyyes
lock_release_point7992u64yes
fee_owner8001pubkeyyes
version8033u8yes
permissionless_operation_bits8034u8yes
extension bin: reward at +16, fee at +648120+structyes
parse_bin_arrayBinArray, 10136 bytesindex8i64yes
version16u8yes
lb_pair24pubkeyyes
parse_bin_array_bitmap_extensionBinArrayBitmapExtension, 1576 byteslb_pair8pubkeyyes

require_lb_pair_layout runs the LbPair guards once: exact size, discriminator, known version, and min_bin_id < max_bin_id (meteora.rs:219-233). parse_lb_pair builds the snapshot on top of it and sets has_initialized_reward when any reward slot has a non-zero mint (meteora.rs:235-258). parse_position_v2 derives the width from the two bin ids and requires the exact account size for that width. It then scans every bin for non-zero liquidity shares and pending fees or rewards (meteora.rs:455-502). The per-bin offsets differ between the base 70 bins and the extension bins (meteora.rs:437-453).

Reward parsers:

FunctionReturnsRule
parse_initialized_reward_infoDlmmRewardInfo for one indexRuns require_lb_pair_layout. Index below 2. Mint and vault both non-default, else InvalidDlmmReward.
parse_initialized_reward_infoslist of (index, DlmmRewardInfo)Runs require_lb_pair_layout. Skips a slot with default mint and vault. Rejects a half-initialized slot.

Range and position checks:

FunctionRuleError
parse_lb_pair_bin_boundsReturns the pair's (min_bin_id, max_bin_id).InvalidProtocolAccountData
assert_position_pair_liquidity_miningfunction_type 1, or 0 with an initialized reward. Every other value, limit order included, is rejected.UnsupportedDlmmPair
assert_position_pair_supportedThe rule above and status == 0.UnsupportedDlmmPair
validate_position_not_ownerThe position key differs from the owner wallet.InvalidPosition
position_v2_account_sizeWidth 1 to 1400. Size 8120 + max(0, width - 70) * 112.InvalidDlmmPositionRange
assert_sickle_positionassert_sickle_position_custody, and lb_pair equals the given pair.InvalidPosition
assert_sickle_position_custodyowner == sickle, operator default, fee_owner default or sickle, lock_release_point == 0, permission bits 0.InvalidPosition
validate_open_position_rangeWidth 1 to 70. Range inside the pair limits.InvalidDlmmPositionRange
resized_position_rangelength_to_add 1 to 91. New width at most 1400. side 0 grows down, 1 grows up. Range inside the pair limits.InvalidDlmmResize
validate_claim_rangemin <= max, inside the position and the pair. Width 1 to 1400.InvalidDlmmPositionRange
validate_add_liquidity_parametersOne amount non-zero. Slippage non-negative. active_id inside the pair. strategy_type < 9. Then validate_claim_range.InvalidDlmmLiquidityParameters
validate_remove_liquidity_bpsbps_to_remove 1 to 10000.InvalidDlmmRemovalBps

Bin-array and bitmap derivation:

FunctionRule
bin_array_indexfloor(bin_id / 70) with Euclidean division.
bin_array_pdafind_program_address([b"bin_array", lb_pair, index LE], dlmm_program).
bin_array_bitmap_extension_pdafind_program_address([b"bitmap", lb_pair], dlmm_program).
range_requires_bitmap_extensionTrue when the first index is below -512 or the last index is 512 or more.
validate_remove_bitmap_extensionInline range: the account key equals the DLMM program and is executable and writable. Overflow range: the key equals the extension PDA, the owner is the DLMM program, writable, not executable, and the parsed lb_pair matches.
claim_bin_array_countlast_index - first_index + 1.
validate_claim_bin_arraysExactly count accounts. Account i equals the PDA for first + i, is owned by the DLMM program, writable, not executable, and its parsed index and lb_pair match.

programs/sickle/src/instructions/jupiter/cpi.rs

The Jupiter binding has no constants for program IDs. It has three private layout constants for SPL token accounts.

ItemKindValueMeaning
TOKEN_ACCOUNT_OWNER_OFFSETconst32Offset of the token account owner field.
TOKEN_ACCOUNT_LENconst165Length of the base token account.
TOKEN_2022_ACCOUNT_TYPE_ACCOUNTconst2Token-2022 account-type byte at offset 165 for an account.
ItemKindMeaning
is_token_account_owned_byfnTrue when the data is at least 165 bytes, byte 165 is 2 or absent, and bytes 32 to 63 equal owner. It checks bytes only; the handler filters by program owner first, and the doc comment says so.
vault_authority_intactfnTakes the reloaded spl_token_2022::state::Account. True when its owner == sickle, mint == expected_mint, no delegate, delegated_amount == 0, and no close_authority.
route_instructionfnBuilds the inner instruction from (pubkey, is_signer, is_writable) triples and the opaque route bytes. The Sickle PDA meta becomes a signer and keeps the write flag the outer instruction carried. Every other meta is copied as is.

The route payload arrives as raw bytes from the handler and is forwarded unchanged (instructions/jupiter/cpi.rs:95, 106). The handler calls is_token_account_owned_by on every remaining account owned by a token program that is not one of the two declared vaults (programs/sickle/src/instructions/jupiter/swap.rs:33-46). It passes both reloaded vaults to vault_authority_intact after the CPI (instructions/jupiter/swap.rs:478-480).

Side-by-side: the three position venues

AspectOrcaRaydiumMeteora
Openopen_position_with_token_extensionsopen_position_with_token22_nft with liquidity = 0initialize_position2, then increase_position_length per 91 bins
Increaseincrease_liquidity_v2increase_liquidity_v2add_liquidity_by_strategy2
Decreasedecrease_liquidity_v2decrease_liquidity_v2remove_liquidity_by_range2 with bps_to_remove
Collect feescollect_fees_v2decrease_liquidity_v2 with liquidity = 0claim_fee2 over a bin range
Collect rewardscollect_reward_v2 per slot, reward_index: u8Inside decrease_liquidity_v2, one [vault, recipient, mint] group per initialized slotclaim_reward2 per slot, reward_index: u64
Closeclose_position_with_token_extensions, rent to receiverclose_position, rent to the signer nft_ownerclose_position_if_empty, rent to rent_receiver
Position accountPosition PDA [b"position", position_mint]PersonalPositionState PDA [b"position", nft_mint]Direct opens use a PositionV2 keypair; intent rebalances use Sickle's per-nonce position PDA
NFT standardToken-2022 mint, optional metadata extensionToken-2022 mint, no Metaplex metadataNone. The owner field of PositionV2 is the Sickle.
Reward slots3, 128 bytes each, initialized when mint non-default3, 169 bytes each, initialized when mint non-default2, 144 bytes each, initialized when mint and vault non-default
Range unittick, spacing from Whirlpooltick, spacing from PoolStatebin, 70 bins per BinArray
Range accountstick_array_lower, tick_array_upper passed by the handlertick_array_lower, tick_array_upper plus start-index argumentsone BinArray PDA per array in the range, derived and checked by the binding
Bitmap extensionnoneoptional trailing TickArrayBitmapExtension PDABinArrayBitmapExtension PDA or the program-ID sentinel, always account 3
Hook forwardingRemainingAccountsInfo slices plus trailing metasnonealways an empty slice vector
Memo programevery *_v2 instructiondecrease_liquidity_v2claim_fee2, claim_reward2, remove_liquidity_by_range2
Extra fixed accountsmetadata_update_auth on openprotocol_position, rent on openevent_authority and the DLMM program on every instruction
Version check on parsenonenoneLbPair <= 1, PositionV2 <= 1, BinArray <= 3
Length check on parseminimum lengthminimum lengthexact length

How a call flows through it

The representative call is the owner's orca_collect_fees. The handler helper collect_fees_and_charge drives the binding.

  1. The handler checks the Whirlpool program against the allowlist (programs/sickle/src/venue/adapters/orca_flows.rs:974).
  2. The handler checks that the pool and the position are owned by that program (venue/adapters/orca_flows.rs:978-988).
  3. The handler checks each pool mint's token program (venue/adapters/orca_flows.rs:989-990).
  4. For mint A then mint B, resolve_pool_hooks calls take_hook_accounts. It receives the hook length or None, pushes (ACCOUNTS_TYPE_TRANSFER_HOOK_A | B, length), and converts the consumed accounts into metas (venue/adapters/orca_flows.rs:359-402, fees.rs:481-490,519-560).
  5. The handler records both owner-account balances (venue/adapters/orca_flows.rs:999-1002).
  6. invoke_orca_fee_collect builds one ordered account list and calls collect_fees_v2_ix with the 13 fixed keys, the slices, and the appended hook metas (venue/adapters/orca_flows.rs:881-915).
  7. The binding writes the discriminator and remaining_accounts_info(hook_slices) into the data (/programs/sickle/src/orca.rs:688-689).
  8. The binding orders the 13 fixed metas, marks the Sickle as signer, and appends the hook metas (/programs/sickle/src/orca.rs:705-729).
  9. The helper calls invoke_signed with the Sickle seeds and the account infos in the same order (venue/adapters/orca_flows.rs:888-914).
  10. The handler reloads both accounts and computes the deltas for the fee (venue/adapters/orca_flows.rs:1005-1009).

Invariants the layer owns

  1. A venue account is read only after its discriminator matches. Every parser checks data[..8] against the account discriminator, and both reward-slot parsers do so through the shared scanner. Error: InvalidProtocolAccountData (/programs/sickle/src/orca.rs:344-348, 433-436, 449-453, 462-466; /programs/sickle/src/raydium.rs:172-176, 235-238, 246-249; meteora.rs:220-225, 305-310, 319-323, 416-421).
  2. A parser never reads past the data. Orca and Raydium require a minimum length up to the last read field. Meteora requires the exact account size. Error: InvalidProtocolAccountData (same lines as above; meteora.rs:466-469).
  3. A Meteora account with a newer layout version is rejected. Error: InvalidProtocolAccountData (meteora.rs:223, 308, 419).
  4. A tick range is increasing and inside the venue tick limits. One check serves both CLMM venues. Error: InvalidTickRange (/programs/sickle/src/orca.rs:366-376, re-exported at /programs/sickle/src/raydium.rs:50-52).
  5. The hook slice descriptor and the appended hook metas have the same order and count. The builder appends the metas in the order the caller passes, and resolve_pool_hooks derives slices and metas from the same consumed prefix (/programs/sickle/src/orca.rs:126-129, 622, venue/adapters/orca_flows.rs:359-402).
  6. A reward slot counts as initialized only by its mint. The shared scanner skips a slot with the default mint for Orca and Raydium (/programs/sickle/src/orca.rs:352-354). Meteora requires mint and vault to agree. Error: InvalidDlmmReward (meteora.rs:294-300).
  7. Every BinArray in a claim or remove tail is the exact PDA for its index, in ascending order. Each one is owned by the DLMM program, is writable, and has a matching header. Error: InvalidDlmmBinArrays or InvalidAccountOwner (meteora.rs:743-782).
  8. The bitmap account is the program-ID sentinel for an inline range and the exact extension PDA for an overflow range. Error: InvalidDlmmBitmapExtension (meteora.rs:675-719).
  9. A Meteora position the program manages has no operator, no foreign fee owner, no lock, and no permissionless bits. Error: InvalidPosition (meteora.rs:514-530).
  10. A Meteora position key never equals the owner wallet. Error: InvalidPosition (meteora.rs:404-407).
  11. The Sickle PDA enters a Jupiter route as a signer, with the write flag the outer instruction carried. Some venues (Byreal, HumidiFi, BisonFi, GoonFi V2) declare Jupiter's user authority mut, and a downgrade to readonly failed those routes with PrivilegeEscalation; the flag grants the route nothing because only this program may change a Sickle-owned account's data or debit its lamports. No error. Only the signer bit is rewritten (instructions/jupiter/cpi.rs:100-104).
  12. A Sickle-owned token account other than the two vaults never enters a Jupiter route. The probe is is_token_account_owned_by. The handler returns SwapVaultNotAllowed (instructions/jupiter/cpi.rs:39-48, instructions/jupiter/swap.rs:41-44).
  13. After a route both vaults keep the Sickle as authority, the same mint, no delegate, and no close_authority. The predicate is vault_authority_intact. The handler returns SwapVaultTampered (instructions/jupiter/cpi.rs:58-68, instructions/jupiter/swap.rs:250-267).

Tests

LocationCovers
/programs/sickle/src/orca.rs:840-1803, 34 unit testsData layout of every args struct, with and without hook slices. Account order and flags of every builder, with and without hook tails. Position and Whirlpool parsers on synthetic bytes. Reward slot parser. Hook slice encoding. Byte readers.
/programs/sickle/src/orca.rs:918-967Every instruction and account discriminator recomputed with sha256.
/programs/sickle/src/orca.rs:1026-1042Offset constants checked against their documented values, and reward_infos derived from a field-by-field Whirlpool layout.
/programs/sickle/src/orca.rs:1164-1221The parsers on the verbatim mainnet Whirlpool and Position in tests/fixtures/clmm-mainnet-accounts.json, with the live tick cross-checked against sqrt_price.
/programs/sickle/src/raydium.rs:642-1422, 22 unit testsAll discriminators recomputed. Data layout of the 3 args structs with Option<bool>. Account order of the 4 builders. Bitmap and reward-group tails. Parsers on synthetic bytes.
/programs/sickle/src/raydium.rs:889-951, 1176-1243The parsers on the verbatim mainnet PoolState and PersonalPositionState; offset constants checked against their documented values and reward_infos derived from a field-by-field PoolState layout.
meteora.rs:1102-2390, 15 unit testsParsers with one mutation per guard. Two frozen mainnet LbPair accounts. Range and resize rules. Bin-array and bitmap checks on mock AccountInfo. Data and metas of all 7 builders. All 11 discriminators recomputed.
meteora.rs:2123-2390Every layout constant, the per-bin strides and pending offsets included, compared to tests/fixtures/meteora-dlmm-layout.json.
meteora.rs:1244-1251Reads management-ui/src/lib/dlmm/fixtures/mainnet-accounts.json for two real pairs.
instructions/jupiter/cpi.rs:110-282, 7 unit testsToken-account probe on classic, Token-2022, mint, and short data. Vault predicate on a built token account with one field mutated at a time. Signer upgrade of the Sickle meta, write flag forwarded, and a repeated Sickle occurrence.
tests/fixtures/meteora-dlmm-layout.jsonAsserted by meteora.rs:2123-2390, tests/builder-meteora.ts:5, tests/mainnet-meteora-acceptance-unit.ts:72, management-ui/src/lib/dlmm/meteora.test.ts:2.
tests/fixtures/clmm-mainnet-accounts.jsonFour verbatim mainnet accounts at one finalized slot. Read by /programs/sickle/src/orca.rs:1164-1221 and /programs/sickle/src/raydium.rs:889-951 through orca::test_support::mainnet_account.
tests/surfpool-lifecycle.tsOrca open, increase, decrease, collect fees, close, owner rebalance, harvest, compound, rebalance, exit on a mainnet fork with reward slots.
tests/surfpool-t22-lifecycle.tsOrca with Token-2022 pool mints. Hook forwarding through the decrease builder (2139-2400).
tests/surfpool-raydium-lifecycle.tsRaydium open, increase, decrease, collect fees, close, owner rebalance, harvest, compound, rebalance, exit with one ended reward slot.
tests/surfpool-t22-raydium-lifecycle.tsRaydium with Token-2022 pool mints. Open, increase, collect fees, owner rebalance, exit.
tests/surfpool-meteora-lifecycle.tsMeteora open, resize, add, remove, claim fees, claim reward, close, harvest, compound, rebalance, exit.
tests/surfpool-jupiter.tsjupiter_swap through a live Jupiter route.

Gaps:

  • No Surfpool suite exercises the standalone orca_collect_rewards instruction. Only tests/mainnet-acceptance.ts:1920 and builder unit tests build it. collect_reward_v2_ix reaches a fork only inside the keeper flows.
  • No Surfpool Raydium suite passes a TickArrayBitmapExtension. The word bitmap does not appear in tests/surfpool-raydium-lifecycle.ts or tests/surfpool-t22-raydium-lifecycle.ts.
  • The mainnet fixture holds one position per pool, and each pool has one initialized reward slot. The synthetic tests cover the other reward-slot shapes.

Simplification and audit notes

Historical notes 1 to 15, 17, 18, 20, 21, 23, and 24 were resolved in #171 and are listed under "Resolved" at the end of this section. The numbering of the kept notes is unchanged, so both lists carry a prettier-ignore comment: without it, prettier renumbers a list from its first item.

  1. L05-N016 — A parser that scans the whole account. parse_position_v2 loops over every bin of the position to compute two booleans (meteora.rs:473-485). A 1400-bin position costs 1400 iterations with 3 slice scans each on every parse. Every caller pays this, including callers that use only the range or the owner. 13 handler files call it. Kept: both booleans are fields of the public PositionV2Snapshot those handlers consume, so a cheaper header-only parse needs a handler-side split, which is outside the bindings contract.
  2. L05-N019 — Sentinel marked writable. remove_liquidity_by_range2_ix and add_liquidity_by_strategy2_ix mark account 3 writable even when it is the DLMM program ID (meteora.rs:1012-1014, 1034-1036). validate_remove_bitmap_extension requires the sentinel to be executable and writable (meteora.rs:688-691). The test at meteora.rs:2090-2119 records this shape. Kept: the flag is part of the byte-identical CPI the deployed program sends, it matches Meteora's own SDK, and the on-chain check and the test agree with it.
  3. L05-N022 — Derivation placement differs per venue. Meteora derives bin-array and bitmap PDAs in the binding (meteora.rs:648-662). Raydium derives the bitmap PDA in a handler from a seed the binding exports (/programs/sickle/src/raydium.rs:42, venue/adapters/raydium_flows.rs:1378-1394). Orca derives nothing and receives every tick array as an account (/programs/sickle/src/orca.rs:542-543). Kept: moving the Raydium derivation into the binding means editing the handler that owns it, which is outside the bindings contract; the Orca and Raydium programs re-derive their own tick arrays inside the CPI.
  4. L05-N025 — Fixture entries with no reader in this layer. The seeds.position entry (meteora-dlmm-layout.json:43-49) describes a PDA the program does not use. Meteora positions here are keypair accounts (meteora.rs:404-407, 769). The Bin and Oracle layouts (meteora-dlmm-layout.json:432-494, 513-547) have no parser in meteora.rs. Kept: the fixture is the cross-language contract, the management UI decoder asserts Bin and Oracle, and management-ui/src/lib/dlmm/pdas.ts:9-11 points at seeds.position as documentation; the fixture note now says so (meteora-dlmm-layout.json:17-19).
  5. L05-N026 — Missing tests. See the gaps under Tests. They cover the Raydium bitmap on a fork and the standalone orca_collect_rewards on a fork. Kept: both need a Surfpool suite change, which is outside the bindings contract; the hook-tail unit tests and the real-account parser tests that were the other half of this note landed in #171.
  6. L05-N027 — Shared helpers hosted in a venue binding. The byte readers, RewardSlotLayout, for_each_initialized_reward_slot, validate_tick_range, the tick bounds, MEMO_PROGRAM_ID, and test_support live in /programs/sickle/src/orca.rs:265-376 and 706-748, and /programs/sickle/src/raydium.rs:32-34 and meteora.rs:22 import them from there. The bindings layer has no module of its own for code that is not about one venue, so the first binding hosts it. A cross-cutting module would need a mod line in lib.rs.
  7. L05-N028 — Two paths to one value. /programs/sickle/src/raydium.rs:50-52 re-exports orca::MIN_TICK_INDEX as MIN_TICK and orca::MAX_TICK_INDEX as MAX_TICK. venue/family.rs:198-207 and state.rs:1483-1490 read the same two numbers through both paths as if they were per-venue.
  8. L05-N029 — A constant only the tests name. LB_PAIR_FUNCTION_LIMIT_ORDER (meteora.rs:46) is public. After the redundant match arm went, no non-test code reads it; a doc comment and the tests name it.

Resolved

  1. L05-R001 — A comment that cites the security review by item number. instructions/jupiter/cpi.rs:50 opens with "security review LOW–MED #5", a document that is not in this repository. Theme 6 of the summary lists the same pattern in layers 01, 02, and 08. Resolved in this pass.

The premise was false and contract 13 measured it before acting. TODO.md enumerates every referenced finding as a checklist entry and README.md defines seven of the eight again, so the pointers resolved -- in two documents, with nothing naming either. Not a dangling pointer; an unnamed target.

The reference is gone anyway, on the layer 02 test rather than that one: the sentence beside it already said what the guard does, so the pointer added nothing. That test was applied to all 25 in scope one at a time, and two survive because their sentences do not carry the reason without them.

  1. L05-R002 — Dead code. INCREASE_LIQUIDITY_DISCRIMINATOR, IncreaseLiquidityArgs, DECREASE_LIQUIDITY_DISCRIMINATOR, DecreaseLiquidityArgs, COLLECT_FEES_DISCRIMINATOR, modify_liquidity_ix, and collect_fees_ix (orca.rs:70-122, 226, 499-531, 593-620) have no caller outside orca.rs. Only their own unit tests reference them (orca.rs:804-864, 1104-1183). They are the v1 Orca instructions the *_v2 builders replaced. Resolved in #171.
  2. L05-R003 — Dead constant. TICK_ARRAY_SIZE (raydium.rs:49) is public. Its only reference is a doc comment at programs/sickle/src/instructions/raydium/open_position.rs:29. Resolved in #171.
  3. L05-R004 — Stale comments about hook forwarding. IncreaseLiquidityV2Args says the program always serializes None and forwards no hook accounts (orca.rs:131-135, 147-148). The struct carries hook_slices (orca.rs:140-143) and data() serializes them (orca.rs:155). The COLLECT_FEES_V2_DISCRIMINATOR comment says the same (orca.rs:228-235). The collect_fees_v2_ix comment says remaining_accounts_info is None (orca.rs:628-629) while the builder takes hook_slices and hook_accounts (orca.rs:646-650). Resolved in #171.
  4. L05-R005 — Tests that cannot fail. Five Orca discriminator tests compare a constant to the same literal (orca.rs:786-794, 821-829, 847-855, 856-864, 865-874). The Raydium tests recompute every discriminator (raydium.rs:631-664). The Orca account and *_v2 tests also recompute (orca.rs:882-895, 1216-1231, 1338-1348). Resolved in #171.
  5. L05-R006 — Two Meteora account discriminators are not recomputed. LB_PAIR_ACCOUNT_DISCRIMINATOR (meteora.rs:18) and BIN_ARRAY_ACCOUNT_DISCRIMINATOR (meteora.rs:63) are compared only to the fixture (meteora.rs:2092-2100, 2220-2228). The fixture is hand-authored (tests/fixtures/meteora-dlmm-layout.json:3-5). PositionV2 and the bitmap extension are recomputed (meteora.rs:1750-1757). Resolved in #171.
  6. L05-R007 — Three copies of one address. MEMO_PROGRAM_ID is defined in orca.rs:247, raydium.rs:54, and meteora.rs:107. The Orca test checks equality with the Raydium copy (orca.rs:1277-1286). No test checks the Meteora copy. Resolved in #171.
  7. L05-R008 — Duplicated tick-range check. orca::validate_tick_range (orca.rs:434-444) and raydium::validate_tick_range (raydium.rs:281-291) have the same body. MIN_TICK_INDEX/MAX_TICK_INDEX (orca.rs:23-24) and MIN_TICK/MAX_TICK (raydium.rs:42-43) have the same values. Resolved in #171.
  8. L05-R009 — Duplicated reward-slot parser. parse_whirlpool_reward_slots (orca.rs:332-361) and parse_pool_reward_slots (raydium.rs:211-234) have the same shape: length and discriminator check, loop over 3 slots, skip default mint, read vault. The Orca one returns the slot index and the Raydium one does not. Resolved in #171.
  9. L05-R010 — Three ways to read a field. meteora.rs:182-212 defines read_i32, read_u64, read_i64, and read_pubkey. orca.rs:371-393 and raydium.rs:163-186 inline try_into().map_err(...) at every read. Meteora uses Pubkey::new_from_array, Orca and Raydium use Pubkey::try_from. Resolved in #171.
  10. L05-R011 — Duplicated test helpers. anchor_discriminator appears in orca.rs:876-880, raydium.rs:625-629, and meteora.rs:1215-1219. keys<N>() appears in orca.rs:1057-1059 and raydium.rs:886-888. Resolved in #171.
  11. L05-R012 — Offsets with no fixture check. LB_PAIR_ACTIVE_ID_OFFSET (meteora.rs:30) is not in the fixture assert (meteora.rs:2083-2271) although the fixture lists activeId at offset 76 (meteora-dlmm-layout.json:127). The private constants at meteora.rs:83-88 (USER_REWARD_INFO_SIZE, USER_REWARD_PENDING_OFFSET, FEE_INFO_SIZE, FEE_PENDING_OFFSET, POSITION_BIN_REWARD_INFO_OFFSET, POSITION_BIN_FEE_INFO_OFFSET) have fixture counterparts under UserRewardInfo, FeeInfo, and PositionBinData (meteora-dlmm-layout.json:350-400) but no assert. The liquidity_shares stride is the literal 16 at meteora.rs:392. These constants drive has_liquidity and has_pending_yield. The close and settlement handlers read those two flags. Resolved in #171.
  12. L05-R013 — Literal reward offsets in Orca and Raydium. WHIRLPOOL_REWARD_INFOS_OFFSET = 269 and WHIRLPOOL_REWARD_INFO_LEN = 128 (orca.rs:287-288) and POOL_REWARD_INFOS_OFFSET = 397, POOL_REWARD_INFO_LEN = 169, POOL_REWARD_MINT_OFFSET = 57, POOL_REWARD_VAULT_OFFSET = 89 (raydium.rs:118-121) are literals with the derivation in a comment. The other offsets in the same files are sums of field sizes (orca.rs:274-280, raydium.rs:102-106). No test derives them from a full layout. Resolved in #171.
  13. L05-R014 — Struct order differs from wire order. OpenPositionWithToken22NftArgs declares liquidity first (raydium.rs:319-329). data() writes the four tick fields first (raydium.rs:334-347). The comment at raydium.rs:304-313 records the live failure a liquidity-first encoding caused. Resolved in #171.
  14. L05-R015 — A check owned elsewhere. The Meteora claim, remove, and add builders write 4 zero bytes for the hook slices without a condition (meteora.rs:850, 908, 965, 1031). Their comments say the caller screens hook mints first (meteora.rs:841-845, 898-902, 957-959). The binding has no check of its own and cannot serve a hook mint. Resolved in #171.
  15. L05-R016 — Double parse. parse_initialized_reward_info (meteora.rs:253) and parse_initialized_reward_infos (meteora.rs:278) call parse_lb_pair again on data the caller already parsed. The comment explains an SBF frame-size reason (meteora.rs:250-252). Resolved in #171.
  16. L05-R017 — Redundant match arm. assert_position_pair_liquidity_mining has LB_PAIR_FUNCTION_LIMIT_ORDER => false followed by _ => false (meteora.rs:337-338). Resolved in #171.
  17. L05-R018 — Two encodings of one struct. DlmmLiquidityParameterByStrategy derives AnchorSerialize (meteora.rs:157-158), and add_liquidity_by_strategy2_ix serializes its fields by hand (meteora.rs:1023-1030). The doc comment at meteora.rs:166 records that the upstream field name is parameteres. Resolved in #171.
  18. L05-R019 — Split ownership of the Jupiter vault probe. is_token_account_owned_by (instructions/jupiter/cpi.rs:33-42) checks bytes only. The program-owner filter lives in the handler (instructions/jupiter/swap.rs:34-36). The comment at instructions/jupiter/cpi.rs:28-32 describes the byte rule and not the split. Resolved in #171.
  19. L05-R020 — A 7-parameter boolean. vault_authority_intact (instructions/jupiter/cpi.rs:52-67) carries #[allow(clippy::too_many_arguments)]. The handler passes the reloaded vault fields one by one (instructions/jupiter/swap.rs:361-369). Resolved in #171.
  20. L05-R021 — Module header describes staging, not the module. meteora.rs:1-10 talks about "Stage 2", "the first owner-lifecycle foundation", and "registry activation". None of those words name an item in the file. Resolved in #171.
  21. L05-R022 — Fixture note is out of date. tests/fixtures/meteora-dlmm-layout.json:13 says a future programs/sickle/src/meteora.rs must assert against it. The file exists and asserts at meteora.rs:2083-2271. Resolved in #171.

The line citations in the resolved notes refer to the files as they were before #171.

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