Sickle Sol

Entry points and program-wide plumbing

This layer declares the program ID, dispatches every instruction to its handler, installs the heap allocator, and defines the shared constants.

Files

PathLinesPurpose
programs/sickle/src/lib.rs630Module list, program ID, allocator install, and the 65 instruction entry functions.
programs/sickle/src/allocator.rs104An upward bump allocator that can use a requested heap frame above 32 KiB.
programs/sickle/src/constants.rs212PDA seeds, fee and settings caps, default program IDs, and authorization sizes.
programs/sickle/Cargo.toml57Crate metadata, Cargo features, and dependencies.
/Cargo.toml20Workspace members, release profile, and the shared lint table.
Anchor.toml77Program IDs per cluster, test scripts, and validator clone list.

Responsibilities

  • Declare the on-chain program ID with declare_id! at lib.rs:40.
  • Export the crate modules that every other layer imports at lib.rs:5-18.
  • Define one public entry function per instruction inside the #[program] module at lib.rs:55-630.
  • Forward each entry function to the handler with the same name in instructions/.
  • Replace the default SBF heap allocator when the custom-heap feature is on at lib.rs:43-53.
  • Hold the PDA seed bytes, the fee cap, the settings caps, and the authorization sizes in constants.rs.
  • Hold the default venue program IDs that initialize_global_config writes into the allowlist at instructions/admin/initialize_global_config.rs:70-76.
  • Select the Cargo features that Anchor and the Solana entrypoint read at programs/sickle/Cargo.toml:13-22.

Public items

lib.rs

ItemKindMeaning
ID, ID_CONSTstaticThe program ID qCxmYKG8poFtsi2PoWDuNgVoPPAcUVLGGDJWSUQ9VTc. Generated by declare_id! at lib.rs:40.
EVENT_AUTHORITY_AND_BUMPconstThe event authority PDA [b"__event_authority"] and its bump. Generated by declare_id! under the event-cpi feature. The events layer reads it at events.rs:89.
constants, errors, events, ...mod13 public modules at lib.rs:7-18. The allocator module is private and compiled only under test or under custom-heap on the solana target at lib.rs:5-6.
SBF_ALLOCATORstaticThe global allocator. Installed only when custom-heap is on, no-entrypoint is off, and the target is solana at lib.rs:43-53.
sicklemodThe #[program] module. Anchor generates the entrypoint and the dispatcher from it. It contains 65 public functions at lib.rs:59-629.

Anchor derives each instruction discriminator as the first 8 bytes of sha256("global:<function_name>"). The dispatcher compares the first 8 bytes of the instruction data against each discriminator and calls the matching function with the rest of the data. Source: anchor-syn-1.1.2/src/codegen/program/common.rs:5-16 and dispatch.rs:76-95.

Each entry function calls the handler function with the same name and the same arguments. No entry function checks anything itself. The table lists every entry function, grouped as in the dependency graph, with its line and its argument list.

Admin

Instructionlib.rsArguments
initialize_global_config59fee_collector: Pubkey
migrate_global_config66meteora_dlmm_program: Pubkey
transfer_admin73new_admin: Pubkey
accept_admin77none
set_fee_collector81fee_collector: Pubkey
set_fee85action: FeeAction, fee_bps: u16
set_guardian89guardian: Pubkey
pause93none
unpause97none
set_protocol_program101protocol: Protocol, program_id: Pubkey
set_global_automator109authority: Pubkey, permissions: u8
revoke_global_automator117none

Sickle

Instructionlib.rsArguments
initialize_sickle121manager: Pubkey, referral_code: [u8; 32]
set_manager129new_manager: Pubkey
set_custom_automator133automator: Pubkey, permissions: u8
create_sickle_ata145none
sweep_token149amount: Option<u64>

Settings

Instructionlib.rsArguments
set_position_settings552position: Pubkey, args: PositionSettingsArgs
unset_position_settings627none

Orca owner

Instructionlib.rsArguments
orca_open_position269tick_lower_index: i32, tick_upper_index: i32, with_token_metadata: bool
orca_increase_liquidity283liquidity_amount: u128, token_max_a: u64, token_max_b: u64, hook_lens: [u8; 2]
orca_decrease_liquidity345liquidity_amount: u128, token_min_a: u64, token_min_b: u64, hook_lens: [u8; 2]
orca_collect_fees299hook_lens: [u8; 2]
orca_collect_rewards306reward_hook_lens: [u8; 3]
orca_close_position361reward_hook_lens: [u8; 3]
orca_rebalance_position369new_tick_lower_index: i32, new_tick_upper_index: i32, token_min_a: u64, token_min_b: u64, with_token_metadata: bool, hook_lens: [u8; 2], reward_hook_lens: [u8; 3]

Orca keeper

Instructionlib.rsArguments
harvest_for313hook_lens: [u8; 2], reward_hook_lens: [u8; 3]
compound_for321hook_lens: [u8; 2], reward_hook_lens: [u8; 3]
rebalance_for392new_tick_lower_index: i32, new_tick_upper_index: i32, token_min_a: u64, token_min_b: u64, with_token_metadata: bool, hook_lens: [u8; 2], reward_hook_lens: [u8; 3]
exit_for329token_min_a: u64, token_min_b: u64, hook_lens: [u8; 2], reward_hook_lens: [u8; 3]

Raydium owner

Instructionlib.rsArguments
raydium_open_position414tick_lower_index: i32, tick_upper_index: i32, tick_array_lower_start_index: i32, tick_array_upper_start_index: i32, with_metadata: bool
raydium_increase_liquidity432liquidity: u128, amount_0_max: u64, amount_1_max: u64
raydium_decrease_liquidity446liquidity: u128, amount_0_min: u64, amount_1_min: u64
raydium_collect_fees460none
raydium_close_position466none
raydium_rebalance_position473new_tick_lower_index: i32, new_tick_upper_index: i32, new_tick_array_lower_start_index: i32, new_tick_array_upper_start_index: i32, amount_0_min: u64, amount_1_min: u64, with_metadata: bool

Raydium keeper

Instructionlib.rsArguments
raydium_harvest_for495none
raydium_compound_for499none
raydium_rebalance_for506new_tick_lower_index: i32, new_tick_upper_index: i32, new_tick_array_lower_start_index: i32, new_tick_array_upper_start_index: i32, amount_0_min: u64, amount_1_min: u64, with_metadata: bool
raydium_exit_for528amount_0_min: u64, amount_1_min: u64

Meteora owner

Instructionlib.rsArguments
meteora_open_position177lower_bin_id: i32, width: i32
meteora_increase_position_length243length_to_add: u16, side: u8
meteora_add_liquidity153parameters: DlmmLiquidityParameterByStrategy
meteora_remove_liquidity255from_bin_id: i32, to_bin_id: i32, bps_to_remove: u16
meteora_claim_fees189min_bin_id: i32, max_bin_id: i32
meteora_claim_reward197reward_index: u64, min_bin_id: i32, max_bin_id: i32
meteora_close_position185none

Meteora keeper

Instructionlib.rsArguments
meteora_harvest_for211min_bin_id: i32, max_bin_id: i32
meteora_compound_for219min_bin_id: i32, max_bin_id: i32
meteora_rebalance_for235amount_x_min: u64, amount_y_min: u64
meteora_exit_for227amount_x_min: u64, amount_y_min: u64
meteora_intent_open_position167none
meteora_intent_increase_position_length171none
meteora_intent_add_liquidity160parameters: DlmmLiquidityParameterByStrategy

Swap

Instructionlib.rsArguments
jupiter_swap536amount_in: u64, min_amount_out: u64, fee_action: Option<FeeAction>, route_data: Vec<u8>

Authorization, CLMM

Instructionlib.rsArguments
initialize_intent_domain560domain_salt: [u8; 32]
set_intent_session567signer: Pubkey, args: IntentSessionArgs
preauthorize_intent575authorization: AuthorizationV2
cancel_intent_authorization589none
expire_intent_authorization603none
finalize_intent617none

Authorization, DLMM

Instructionlib.rsArguments
preauthorize_dlmm_intent582authorization: DlmmAuthorizationV1
cancel_dlmm_intent_authorization593none
expire_dlmm_intent_authorization609none
finalize_dlmm_intent621none

The argument types come from other layers. FeeAction and Protocol are enums in state.rs:21-28 and state.rs:36-43. PositionSettingsArgs is at state.rs:228-237. IntentSessionArgs is at instructions/intent/set_intent_session.rs:14-21. DlmmLiquidityParameterByStrategy is at meteora.rs:192. AuthorizationV2 and DlmmAuthorizationV1 are in intent.rs. The reward array length 3 is orca::WHIRLPOOL_REWARD_SLOT_COUNT.

allocator.rs

ItemKindMeaning
MAX_HEAP_FRAME_BYTESconst256 KiB. The largest address range the allocator hands out. allocator.rs:14.
UpwardBumpAllocatorstructHolds a start address and a length. allocator.rs:16-19.
UpwardBumpAllocator::with_fixed_address_rangefnconst unsafe constructor. The caller reserves the range. allocator.rs:27-29.
GlobalAlloc for UpwardBumpAllocatorimplalloc bumps a cursor upward with checked arithmetic. dealloc does nothing. allocator.rs:34-67.

Both items are pub(crate). Only lib.rs:49-53 uses them.

The module comment at allocator.rs:3-8 gives the reason for the allocator. The default Solana allocator starts at the top of a fixed 32 KiB range and grows downward. A RequestHeapFrame instruction maps more memory, but the default allocator cannot address it. This allocator grows upward from the heap base. A transaction without a heap request keeps the same first 32 KiB. A transaction with a request can continue into the mapped extension.

The allocator stores its cursor in the first word of the range at allocator.rs:37-44. A zero word means the first call. Every addition is checked, and an overflow or an address past the end returns null at allocator.rs:39-60.

The Solana entrypoint installs its own allocator only when the custom-heap feature is off. Source: solana-program-entrypoint-3.1.1/src/lib.rs:219-230. The crate turns custom-heap on by default at programs/sickle/Cargo.toml:14, so lib.rs:43-53 installs this allocator instead.

constants.rs

Seeds. The #[constant] attribute exports each seed into the IDL.

ItemKindValueLine
GLOBAL_CONFIG_SEEDconstb"global_config"4
SICKLE_SEEDconstb"sickle"7
POSITION_SETTINGS_SEEDconstb"position_settings"10
KEEPER_SEEDconstb"keeper"13
INTENT_DOMAIN_SEEDconstb"intent_domain"98
INTENT_NONCE_SEEDconstb"intent_nonce"101
INTENT_SESSION_SEEDconstb"intent_session"104
INTENT_AUTHORIZATION_SEEDconstb"intent_authorization"107
DLMM_INTENT_AUTHORIZATION_SEEDconstb"dlmm_intent_authorization"116
DLMM_INTENT_POSITION_SEEDconstb"dlmm_intent_position"124
ACTION_INTENT_SEEDconstb"action_intent"127

Caps and sizes.

ItemKindValueLineMeaning
BPS_DENOMINATORconst1000015The bps base for every fee and cap.
MAX_FEE_BPSconst500 bps18The maximum protocol fee.
FEE_ACTION_COUNTconst622The length of the fee array in the global config.
MAX_SLIPPAGE_BPSconst500 bps28The maximum settings slippage.
MAX_PRICE_IMPACT_BPSconst5000 bps29The maximum settings price impact.
MAX_DUST_BPSconst5000 bps30The maximum settings dust.
MAX_COMPOUND_DUST_BPSconst100 bps36The maximum dust in a compound authorization.
METEORA_DLMM_INTENT_MAX_WIDTHconst56 bins78The widest Meteora range a keeper authorization may cover.
PROTOCOL_COUNTconst481The number of venue enum variants.
GLOBAL_CONFIG_INLINE_PROTOCOL_COUNTconst389The number of allowlist slots in the original global config array.
SICKLE_VERSIONconst191The version written into a new Sickle.
INTENT_DOMAIN_VERSIONconst1121The version of the intent domain account.
AUTHORIZATION_VERSIONconst2127The version byte inside a CLMM authorization digest.
DLMM_AUTHORIZATION_VERSIONconst1132The version byte inside a DLMM authorization digest.
MAX_INTENT_ASSETSconst3139The number of asset entries in an authorization.
MAX_SWAP_EDGESconst2143The number of swap edges in an authorization.

Prefixes and addresses.

ItemKindValueLine
INTENT_DIGEST_PREFIXconstb"SICKLE_ACTION_INTENT"146
DLMM_INTENT_DIGEST_PREFIXconstb"SICKLE_DLMM_ACTION_INTENT"149
INTENT_DOMAIN_PREFIXconstb"SICKLE_INTENT_DOMAIN"152
ORCA_WHIRLPOOL_PROGRAM_IDconstwhirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc44
JUPITER_PROGRAM_IDconstJUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV451
RAYDIUM_CLMM_PROGRAM_IDconstCAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK57
METEORA_DLMM_PROGRAM_IDconstLBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo63
METEORA_DLMM_AUTOMATION_LB_PAIRconstJCYMX9Nx7DTUdguptRR5LLSc62MEbNmFYsbT5R9yCDGy71

Permission bits.

ItemKindValueLine
PERMISSION_HARVESTconst0x01155
PERMISSION_COMPOUNDconst0x02156
PERMISSION_REBALANCEconst0x04157
PERMISSION_EXITconst0x08158
PERMISSION_ALLconst0x0F159

programs/sickle/Cargo.toml

FeatureLineEffect
default14Turns on custom-heap.
cpi15Turns on no-entrypoint. A crate that calls this program through CPI uses it.
no-entrypoint16Anchor skips the entrypoint. lib.rs:45 also skips the allocator install.
no-idl17Anchor skips the IDL instructions.
no-log-ix-name18Anchor skips the instruction-name log line.
idl-build19Turns on anchor-lang/idl-build and anchor-spl/idl-build for anchor build.
anchor-debug20Anchor debug output.
custom-heap21The Solana entrypoint does not install its allocator. lib.rs:43-53 installs the upward allocator.
custom-panic22Declared. No code in programs/sickle/src reads it.

The crate depends on anchor-lang 1.1.2 with init-if-needed and event-cpi, on anchor-spl 1.1.2 with associated_token, token, and token_2022, on solana-instructions-sysvar 3.0, and on solana-sha256-hasher 3.1.0 at programs/sickle/Cargo.toml:28-36. The dev dependencies are base64, serde_json, and trybuild at programs/sickle/Cargo.toml:41-48.

The workspace /Cargo.toml:15-20 allows the unexpected_cfgs lint for target_os = "solana" and for the custom-heap, custom-panic, and anchor-debug features. It also allows the deprecated lint for the whole workspace. The release profile turns on overflow-checks, fat LTO, and one codegen unit at /Cargo.toml:5-8.

Anchor.toml

ClusterProgramIDLine
devnetsickleScksx45gGiyMt2x7jcKzG39iKvHSaXarYK8L2jv7TbG9
localnetsickleqCxmYKG8poFtsi2PoWDuNgVoPPAcUVLGGDJWSUQ9VTc16
localnetmock_transfer_hookHookK1nGpuGKQxWWvVvXrpKjHi3rTfRvhmPXHkNW8yUS15
mainnetsickleqCxmYKG8poFtsi2PoWDuNgVoPPAcUVLGGDJWSUQ9VTc19

The declare_id! value at lib.rs:40 equals the localnet and mainnet IDs. It does not equal the devnet ID. A devnet build needs a different declare_id! value. Anchor's entrypoint rejects a mismatch with DeclaredProgramIdMismatch at anchor-syn-1.1.2/src/codegen/program/entry.rs:61-63.

Anchor.toml:25-32 defines the test scripts. Anchor.toml:47-69 lists 8 mainnet accounts that the legacy test validator clones. Anchor.toml:71-77 configures Surfpool against the mainnet RPC.

How a call flows through it

This follows sweep_token.

  1. The runtime calls the Anchor entrypoint with the instruction data. Anchor reads the first 8 bytes and matches them against sha256("global:sweep_token")[..8]. Source: anchor-syn-1.1.2/src/codegen/program/dispatch.rs:81-95.
  2. Anchor deserializes the SweepToken accounts struct and runs its constraints. The sickle constraint requires sickle.is_owner(&authority.key()) and returns Unauthorized otherwise. instructions/sickle/sweep_token.rs:23-28.
  3. Anchor calls sickle::sweep_token(ctx, amount) at lib.rs:146.
  4. The entry function calls instructions::sickle::sweep_token::sweep_token(ctx, amount) at lib.rs:147.
  5. The handler resolves amount, returns early on zero, and transfers the tokens with the Sickle PDA seeds [SICKLE_SEED, owner, bump]. instructions/sickle/sweep_token.rs:52-74.
  6. The handler builds a Swept event and emits it twice, once by self-CPI and once as a log line. instructions/sickle/sweep_token.rs:76-83.
  7. The self-CPI re-enters the program. Anchor's dispatcher recognizes the event tag and returns without a second dispatch. Source: anchor-syn-1.1.2/src/codegen/program/dispatch.rs:90-93.

Every heap allocation in steps 2 to 7 goes through UpwardBumpAllocator::alloc at allocator.rs:36-63.

Invariants the layer owns

  1. The runtime program ID equals ID. Anchor's generated entrypoint returns DeclaredProgramIdMismatch otherwise. Source: anchor-syn-1.1.2/src/codegen/program/entry.rs:61-63.
  2. Every instruction reaches exactly one handler. Each entry function at lib.rs:59-629 has one body line that calls the handler with the same name and arguments. No entry function has a check.
  3. The allocator never hands out an address past the 256 KiB frame. alloc compares the end address against start + len and returns null at allocator.rs:54-60.
  4. The allocator never overflows its arithmetic. Every addition uses checked_add at allocator.rs:39-57.
  5. A protocol fee never exceeds 500 bps. set_fee requires fee_bps <= MAX_FEE_BPS and returns FeeTooHigh at instructions/admin/set_fee.rs:21.
  6. A permission byte uses only the 4 known bits. set_global_automator checks the byte against PERMISSION_ALL and returns InvalidPermissions at instructions/admin/set_global_automator.rs:45.
  7. A new Sickle carries version 1. initialize_sickle writes SICKLE_VERSION at instructions/sickle/initialize_sickle.rs:36.
  8. The allowlist starts with the 4 default venue IDs. initialize_global_config writes the 4 constants at instructions/admin/initialize_global_config.rs:70-76.

Tests

Test locationCovers
allocator.rs:76-92An allocation sequence crosses the 32 KiB boundary and stays inside 256 KiB.
allocator.rs:95-103A 256 KiB allocation returns null.
allocator.rs:73A compile-time assert that custom-heap is on during cargo test.
state.rs:1373The Protocol variant count equals PROTOCOL_COUNT.
.github/workflows/ci.yml:106-108The build log contains no SBF frame overflow diagnostic.
tests/sickle.ts and tests/sickle/*.tsIntegration tests call the instructions through the IDL on a local validator.

Gaps. No test asserts the declare_id! value against Anchor.toml. No test asserts the 65 instruction count or the instruction list against the IDL. No test reads SICKLE_VERSION back. No test covers a RequestHeapFrame transaction against the deployed allocator in Rust. The allocator tests run on the host, not on the SBF target.

Simplification and audit notes

  1. L01-N001 — lib.rs:1 allows clippy::diverging_sub_expression for the whole crate. The allow has no comment. A reader cannot tell which expression needs it.
  2. L01-N002 — lib.rs:5 compiles the allocator module under test or under custom-heap on the solana target. lib.rs:43-47 installs the allocator only when no-entrypoint is also off. The two cfg lists differ. Under cpi the module compiles on the solana target, and nothing uses it.
  3. L01-N003 — allocator.rs:73 is a const assert that the custom-heap feature is on. cargo test --no-default-features therefore fails to compile the test module. The assert documents the coupling between the test and the default feature set.
  4. L01-N004 — lib.rs:59-629 orders the entry functions as admin, Sickle, Meteora, Orca, Raydium, swap, set_position_settings, authorization, unset_position_settings. The order differs from the grouping in docs/dev/dependency-graph.md section 5. set_position_settings at lib.rs:552 and unset_position_settings at lib.rs:627 are 75 lines apart with 10 authorization functions between them. The order has no effect on dispatch.
  5. L01-N005 — lib.rs:590-597 are the only 2 entry functions with a doc comment. The other 63 have none. The IDL carries an instruction doc only for those 2.
  6. L01-N006 — lib.rs:153 and lib.rs:160 name crate::meteora::DlmmLiquidityParameterByStrategy with a full path. lib.rs:21-22 imports AuthorizationV2, DlmmAuthorizationV1, FeeAction, PositionSettingsArgs, and Protocol at the top. IntentSessionArgs at lib.rs:570 arrives through the instructions::* glob at lib.rs:20. Three import styles serve one purpose.
  7. L01-N007 — lib.rs:359, lib.rs:382, lib.rs:463, and lib.rs:496 allow clippy::too_many_arguments on 4 entry functions with 7 arguments each. The Orca and Raydium rebalance pairs repeat the same 7-argument list twice each.
  8. L01-N008 — constants.rs:133 defines SICKLE_VERSION. instructions/sickle/initialize_sickle.rs:36 writes it. No line in programs/sickle/src reads the stored version field back. The field has no consumer on-chain.
  9. L01-N009 — constants.rs:125 defines PROTOCOL_COUNT. Its only use outside constants.rs is the test at state.rs:1373. Production code does not read it.
  10. L01-N011 — constants.rs:184-188 says the asset ledger holds token A and token B because "reward emissions are deferred". instructions/orca/harvest_for.rs:193 and instructions/orca/compound_for.rs:237 emit RewardsCollected for keeper flows today, as do the three Raydium claim paths since #184. The comment does not say what is still deferred.
  11. L01-N012 — constants.rs:44-45, 51, 57, and 63 hold 4 default program IDs. Their only production reader is instructions/admin/initialize_global_config.rs:70-76. Every later check reads the global config. The comments at constants.rs:38-43 say so, but each of the 4 comments repeats the same 3 sentences.
  12. L01-N013 — constants.rs:16-17, 24-27, and 43 reference the EVM contracts inside doc comments. A reader of this layer cannot verify those references from this repository.
  13. L01-N014 — programs/sickle/Cargo.toml:22 declares the custom-panic feature. No file in programs/sickle/src reads it. The workspace lint table at /Cargo.toml:18 still lists it as a known feature.
  14. L01-N015 — /Cargo.toml:20 allows the deprecated lint for the workspace. A deprecated Anchor or Solana API produces no warning in CI.
  15. L01-N016 — Anchor.toml:29 defines test-surfpool as one script over 6 files. Anchor.toml:30-32 define the 3 per-venue scripts over the same 6 files. The file lists are written twice.
  16. L01-N017 — No test asserts the instruction count. docs/dev/dependency-graph.md:182 states 65, and instructions/mod.rs has 65 pub mod lines, but only a manual count connects them.

Resolved

  1. L01-R001 — Anchor.toml:9 gives devnet the ID Scksx45gGiyMt2x7jcKzG39iKvHSaXarYK8L2jv7TbG. lib.rs:40 declares qCxmYKG8poFtsi2PoWDuNgVoPPAcUVLGGDJWSUQ9VTc. Anchor's entrypoint returns DeclaredProgramIdMismatch when the runtime program ID differs from ID. Source: anchor-syn-1.1.2/src/codegen/program/entry.rs:61-63. A devnet deploy of the checked-in source therefore needs an edit to lib.rs first. scripts/deploy/deploy.sh runs anchor keys sync before the build, but bare: it is passed neither the run's cluster nor its --program-keypair, both of which do reach anchor deploy. With Anchor.toml setting the provider cluster to localnet, that sync need not touch the programs.devnet entry at all, and the script's clean-tree assertion cannot see a sync that changed nothing. No script or test asserts the pair.

    The mismatch was real and the deploy path did not handle it. deploy.sh ran anchor keys sync, which takes its answer from target/deploy/sickle-keypair.json and accepts neither a cluster nor a --program-keypair argument, so a devnet run could leave the devnet entry untouched and build the mainnet id into the binary. A program whose declared id is not its deployed address rejects every instruction, and no gate caught it: a sync that changes nothing leaves a clean tree behind.

    Both ids are now declared in lib.rs behind the devnet cargo feature, as reviewed constants, and programs/sickle/tests/program_id.rs pins each arm against its own Anchor.toml entry. The sync is gone from both deploy.sh and upgrade.sh, which also removes the deeper problem: it rewrote tracked source in the middle of a deploy whose entire purpose is to prove the binary came from a reviewed commit. That gate is now absolute rather than something worked around.

    The note asked for a test pinning "the pair". There are two pairs, and asserting the devnet one against declare_id! would have asserted exactly what the old deploy path existed to break. With both ids constant the question dissolves: each arm is pinned to its own cluster. Resolved in #191.

  2. L01-N010 — The program hard-coded one Meteora pair address, and the DLMM canonical-form check compared every DLMM authorization pool against it. A second pair needed a program upgrade. The pin is removed. validate_canonical now requires only a non-default pool, at intent.rs:618. Each keeper transaction-B instruction checks the inclusive width against the live pair reward-slot count, at meteora.rs:377-394. The limit table is constants.rs:103. The keeper and the management UI still act only on the ADX-USDC pair. That pin is now a rollout choice off chain.

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