Sickle Sol

orca_open_position

This instruction creates an empty Orca Whirlpool position that the Sickle owns.

The position holds no liquidity after this instruction. Add liquidity with orca_increase_liquidity in a later instruction or in the same transaction.

Who can call it

The owner. The owner signs as authority. A manager or a keeper cannot call this instruction.

A second signer, the funder, pays the rent for the new accounts. The funder can be any wallet.

Arguments

NameTypeMeaning
tick_lower_indexi32The lower tick of the price range.
tick_upper_indexi32The upper tick of the price range.
with_token_metadataboolIf true, Orca writes metadata for the position NFT. Pass true.

The tick range must satisfy these limits.

LimitValue
Minimum tick_lower_index-443,636
Maximum tick_upper_index443,636
tick_lower_index < tick_upper_indexrequired

Orca applies two rules of its own that the program does not check. Each tick must be a multiple of the pool tick spacing. A pool whose tick spacing is 32,768 or more accepts only its exact full range. Orca rejects the transaction if either rule fails. The builder's buildOpenRange satisfies both.

Accounts

#NameSignerWritableMeaning
0funderyesyesPays the rent for the position and the position mint.
1authorityyesnoThe owner.
2global_confignonoThe global config. Seeds: ["global_config"].
3sicklenonoThe Sickle. Seeds: ["sickle", owner].
4whirlpool_programnonoThe Orca Whirlpools program.
5whirlpoolnonoThe Orca pool.
6positionnoyesThe new position. Orca seeds: ["position", position_mint].
7position_mintyesyesA new keypair. It becomes the Token-2022 mint of the position NFT.
8position_token_accountnoyesThe Sickle's Token-2022 associated token account for position_mint.
9token_2022_programnonoThe Token-2022 program.
10system_programnonoThe System program.
11associated_token_programnonoThe Associated Token program.
12metadata_update_authnonoOrca's fixed metadata authority, 3axbTs2z5GBy6usVbNVoqEgZMng3vZvMnAoX29BFfwhr.
13event_authoritynonoThe program's event authority. Seeds: ["__event_authority"].
14programnonoThe Sickle program.

The builder derives accounts 2, 3, 6, 8, 12, 13, and 14. You supply the rest.

What the program checks

The program runs these checks in this order.

  1. The program is not paused. Otherwise it returns Paused.
  2. whirlpool_program equals the Orca entry in the allowlist. Otherwise it returns ProtocolNotAllowed.
  3. authority equals the owner of the Sickle. Otherwise it returns Unauthorized.
  4. whirlpool is owned by whirlpool_program. Otherwise it returns InvalidAccountOwner.
  5. The tick range satisfies the limits above. Otherwise it returns InvalidTickRange.
  6. position equals the address Orca derives from position_mint. Otherwise it returns InvalidPosition.
  7. position_token_account equals the Sickle's Token-2022 associated token account for position_mint. Otherwise it returns InvalidPosition.
  8. metadata_update_auth equals Orca's fixed authority. Otherwise it returns InvalidMetadataUpdateAuth.

After the Orca call, the program reads the new accounts and checks the receipt (venue/open_increase.rs:102-151):

  • the position, mint, and token account addresses equal the derived ones;
  • the position's pool equals whirlpool, its tick range equals the request, and it holds no liquidity;
  • the position account is owned by the Orca program, and the mint and token account are owned by the Token-2022 program;
  • the token account balance is exactly 1.

It does not read the token account's authority after the call. The Sickle's ownership of the NFT rests on the address check in step 7, which derives the token account from the Sickle as owner. Failures return InvalidPosition, InvalidProtocolAccountData, or InvalidAccountOwner.

What the program does

  1. The program calls Orca open_position_with_token_extensions with the Sickle as the position owner.
  2. Orca creates the position account, mints one position NFT to position_token_account, and charges the rent to funder.
  3. The program reads the new position and emits PositionOpened.

The Sickle holds the position NFT. The owner does not hold it. Only the Sickle program can move liquidity in or out of the position.

Fees

This instruction charges no fee.

Events

EventFields
PositionOpenedsickle, protocol, pool, position, tick_lower_index, tick_upper_index

The program emits the event twice: once as a self-CPI and once as a log line. Decode the self-CPI record. The log line can be truncated.

Errors

ErrorCauseFix
PausedThe guardian or the admin paused the program.Wait for the admin to unpause.
ProtocolNotAllowedwhirlpool_program is not the allowlisted Orca program.Pass the Orca Whirlpools program ID.
Unauthorizedauthority is not the owner.Sign with the owner key.
InvalidAccountOwnerwhirlpool is not an Orca account, or a created account has an unexpected program owner.Pass a pool account that the Orca program owns. The second case indicates a venue fault.
InvalidTickRangeThe ticks are inverted, equal, or outside the limits.Pass tick_lower_index < tick_upper_index inside ±443,636.
InvalidPositionposition or position_token_account has the wrong address.Use the builder to derive both accounts from position_mint.
InvalidMetadataUpdateAuthmetadata_update_auth is the wrong key.Pass 3axbTs2z5GBy6usVbNVoqEgZMng3vZvMnAoX29BFfwhr.
InvalidProtocolAccountDataOrca returned a position that does not match the request.Report this. It indicates a program or venue fault.

Example

import * as anchor from "@anchor-lang/core";
import {
  Connection,
  Keypair,
  PublicKey,
  Transaction,
  sendAndConfirmTransaction,
} from "@solana/web3.js";
import { getPoolState } from "../scripts/builder/flows";
import { buildOpenPosition } from "../scripts/builder/instructions";
import { buildOpenRange } from "../scripts/builder/orca";
import type { Sickle } from "../target/types/sickle";

declare const connection: Connection;
declare const program: anchor.Program<Sickle>;
declare const owner: Keypair;
declare const poolAddress: PublicKey;
const ORCA_WHIRLPOOL_PROGRAM_ID = new PublicKey(
  "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc",
);

// Every builder input carries the decoded pool. `getPoolState` fetches the
// account and resolves each mint's token program before it decodes.
const pool = await getPoolState(connection, poolAddress);
const positionMint = Keypair.generate();

// Orca rejects a tick that is not a multiple of the pool's tick spacing,
// a range outside its full-range bounds, and anything but the exact full
// range on a pool with tick spacing 32768 or more. `buildOpenRange` asks for
// 10 spacings below and above the live tick and returns a range that
// satisfies all three rules for the decoded pool.
const { tickLowerIndex, tickUpperIndex } = buildOpenRange(
  pool.tickCurrentIndex,
  pool.tickSpacing,
  10,
);

const ix = await buildOpenPosition(program, {
  programId: program.programId,
  owner: owner.publicKey,
  authority: owner.publicKey,
  whirlpoolProgram: ORCA_WHIRLPOOL_PROGRAM_ID,
  whirlpool: poolAddress,
  pool,
  funder: owner.publicKey,
  positionMint: positionMint.publicKey,
  tickLowerIndex,
  tickUpperIndex,
});

// The owner and the position mint keypair both sign.
const tx = new Transaction().add(ix);
await sendAndConfirmTransaction(connection, tx, [owner, positionMint]);
  • orca_increase_liquidity adds liquidity to the position.
  • orca_close_position closes an empty position and returns the rent to the owner.
  • set_position_settings lets a keeper act on the position.
  • Style guide for the rules this page follows.

Design note

The EVM reference mints the position NFT inside its deposit flow. The Solana program separates the mint from the deposit because an empty open moves no tokens. See the "Shared CLMM open/increase contract" entry in CLAUDE.md.

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