DEV Community

Thomas Cosialls
Thomas Cosialls

Posted on

Collecting Fees and Rewards from Orca Whirlpool Positions

A quick guide using Anchor and the whirlpool_cpi crate

What You'll Learn

Orca Whirlpools is a concentrated liquidity AMM on Solana. When you provide liquidity, you earn:

  • Trading Fees: A share of swaps within your price range (Token A + Token B)
  • Rewards: Up to 3 incentive tokens configured by the pool operator

This guide shows how to collect both programmatically using CPI.

The Critical Step Everyone Misses

You must call update_fees_and_rewards before collecting, or you will receive 0 tokens.

The position account stores fees/rewards as checkpoints. The update instruction calculates what's owed since the last checkpoint and writes it to fee_owed_a, fee_owed_b, and reward_infos[].amount_owed. Without this step, those fields remain at zero.

// CRITICAL: Always call this BEFORE collect_fees or collect_reward
execute_update_fees_and_rewards_cpi(
    &ctx.accounts.whirlpool_program,
    &ctx.accounts.whirlpool.to_account_info(),
    &ctx.accounts.position.to_account_info(),
    &ctx.accounts.tick_array_lower.to_account_info(),
    &ctx.accounts.tick_array_upper.to_account_info(),
    Some(vault_seeds),
)?;
Enter fullscreen mode Exit fullscreen mode

Collecting Fees

After updating, call collect_fees to receive both pool tokens:

whirlpool_cpi::cpi::collect_fees(cpi_ctx)?;
Enter fullscreen mode Exit fullscreen mode

Collecting Rewards

Rewards use an index-based system (0, 1, or 2). Check whirlpool.rewardInfos[index] for active rewards:

// Client-side: iterate through active reward slots
for (let rewardIndex = 0; rewardIndex < 3; rewardIndex++) {
  const rewardInfo = pool_data.data.rewardInfos[rewardIndex]

  // Skip uninitialized slots
  if (rewardInfo.mint.toString() === "11111111111111111111111111111111") {
    continue
  }

  // Collect this reward using its index, mint, and vault
  await program.methods
    .collectRewardsOrcaPosition(operationId, rewardIndex)
    .accounts({
      rewardTokenMint: rewardInfo.mint,
      rewardVault: rewardInfo.vault,
      // ... other accounts
    })
    .rpc()
}
Enter fullscreen mode Exit fullscreen mode

Quick Summary

  1. Always update first - Call update_fees_and_rewards or get 0 tokens
  2. Fees are both tokens - One call collects Token A and Token B
  3. Rewards are per-index - Loop through rewardInfos[0..2] and collect each active one

๐Ÿ“– Read the full guide for complete CPI implementations, account structures, and client-side fee/reward quote calculations using the Orca SDK.

Top comments (0)