# Apertura / cierre de posición

Abre una posición con un rango de precios personalizado para proporcionar liquidez concentrada, o cierra una posición vacía para recuperar el rent.

***

### Cómo funcionan las posiciones CLMM

Cada posición CLMM está representada por un NFT en tu wallet. Una posición define:

* Un **rango de precios** (tick inferior y tick superior) donde tu liquidez está activa.
* La **cantidad de token A y token B** depositada dentro de ese rango.

La Liquidez solo genera comisiones de trading cuando el precio actual del pool está dentro del rango de tu posición.

***

### Obteniendo información del pool

Tanto las operaciones de apertura como de cierre requieren información del pool.

```typescript
import { ApiV3PoolInfoConcentratedItem, ClmmKeys, CLMM_PROGRAM_ID, DEVNET_PROGRAM_ID } from '@raydium-io/raydium-sdk-v2'
import { initSdk, txVersion } from '../config'

const VALID_PROGRAM_ID = new Set([
  CLMM_PROGRAM_ID.toBase58(),
  DEVNET_PROGRAM_ID.CLMM_PROGRAM_ID.toBase58(),
])
const isValidClmm = (id: string) => VALID_PROGRAM_ID.has(id)

let poolInfo: ApiV3PoolInfoConcentratedItem
let poolKeys: ClmmKeys | undefined

const raydium = await initSdk()
const poolId = 'YOUR_POOL_ID'

if (raydium.cluster === 'mainnet') {
  const data = await raydium.api.fetchPoolById({ ids: poolId })
  poolInfo = data[0] as ApiV3PoolInfoConcentratedItem
  if (!isValidClmm(poolInfo.programId)) throw new Error('target pool is not CLMM pool')
} else {
  const data = await raydium.clmm.getPoolInfoFromRpc(poolId)
  poolInfo = data.poolInfo
  poolKeys = data.poolKeys
}
```

***

### Abriendo una posición

Usa `raydium.clmm.openPositionFromBase()` para abrir una nueva posición. Especificas la cantidad de un token, un rango de precios, y el SDK calcula la cantidad del otro token necesaria.

```typescript
import {
  ApiV3PoolInfoConcentratedItem,
  TickUtils,
  PoolUtils,
  ClmmKeys,
} from '@raydium-io/raydium-sdk-v2'
import BN from 'bn.js'
import Decimal from 'decimal.js'
import { initSdk, txVersion } from '../config'
import { isValidClmm } from './utils'

const createPosition = async () => {
  const raydium = await initSdk()

  let poolInfo: ApiV3PoolInfoConcentratedItem
  const poolId = '61R1ndXxvsWXXkWSyNkCxnzwd3zUNB8Q2ibmkiLPC8ht'
  let poolKeys: ClmmKeys | undefined

  if (raydium.cluster === 'mainnet') {
    const data = await raydium.api.fetchPoolById({ ids: poolId })
    poolInfo = data[0] as ApiV3PoolInfoConcentratedItem
    if (!isValidClmm(poolInfo.programId)) throw new Error('target pool is not CLMM pool')
  } else {
    const data = await raydium.clmm.getPoolInfoFromRpc(poolId)
    poolInfo = data.poolInfo
    poolKeys = data.poolKeys
  }

  const inputAmount = 0.000001
  const [startPrice, endPrice] = [0.000001, 100000]

  const { tick: lowerTick } = TickUtils.getPriceAndTick({
    poolInfo,
    price: new Decimal(startPrice),
    baseIn: true,
  })

  const { tick: upperTick } = TickUtils.getPriceAndTick({
    poolInfo,
    price: new Decimal(endPrice),
    baseIn: true,
  })

  const epochInfo = await raydium.fetchEpochInfo()
  const res = await PoolUtils.getLiquidityAmountOutFromAmountIn({
    poolInfo,
    slippage: 0,
    inputA: true,
    tickUpper: Math.max(lowerTick, upperTick),
    tickLower: Math.min(lowerTick, upperTick),
    amount: new BN(new Decimal(inputAmount || '0').mul(10 ** poolInfo.mintA.decimals).toFixed(0)),
    add: true,
    amountHasFee: true,
    epochInfo,
  })

  const { execute, extInfo } = await raydium.clmm.openPositionFromBase({
    poolInfo,
    poolKeys,
    tickUpper: Math.max(lowerTick, upperTick),
    tickLower: Math.min(lowerTick, upperTick),
    base: 'MintA',
    ownerInfo: {
      useSOLBalance: true,
    },
    baseAmount: new BN(new Decimal(inputAmount || '0').mul(10 ** poolInfo.mintA.decimals).toFixed(0)),
    otherAmountMax: res.amountSlippageB.amount,
    txVersion,
    // opcional: configura aquí la comisión de prioridad
    // computeBudgetConfig: {
    //   units: 600000,
    //   microLamports: 100000,
    // },
  })

  const { txId } = await execute({ sendAndConfirm: true })
  console.log('clmm position opened:', { txId, nft: extInfo.nftMint.toBase58() })
}

createPosition()
```

#### Convirtiendo precios a ticks

Las posiciones CLMM usan **ticks** para definir los límites de precio. Usa `TickUtils.getPriceAndTick()` para convertir precios legibles por humanos en valores de tick válidos alineados con el tick spacing del pool.

```typescript
const { tick } = TickUtils.getPriceAndTick({
  poolInfo,
  price: new Decimal(100),
  baseIn: true, // true = precio de mintA expresado en términos de mintB
})
```

#### Cálculo de la cantidad emparejada

Antes de abrir, usa `PoolUtils.getLiquidityAmountOutFromAmountIn()` para previsualizar cuánta cantidad del otro token se necesita para el rango y la cantidad de entrada elegidos.

#### Parámetros de apertura de posición

| Parámetro        | Tipo      | Descripción                                                                  |
| ---------------- | --------- | ---------------------------------------------------------------------------- |
| `poolInfo`       | object    | Información del pool desde API o RPC.                                        |
| `poolKeys`       | object    | Claves del pool. Requeridas para devnet.                                     |
| `tickLower`      | number    | Límite inferior de tick de la posición.                                      |
| `tickUpper`      | number    | Límite superior de tick de la posición.                                      |
| `base`           | string    | `'MintA'` o `'MintB'` — de qué token `baseAmount` hace referencia.           |
| `baseAmount`     | BN        | Cantidad del token base a depositar, en las unidades más pequeñas.           |
| `otherAmountMax` | BN        | Cantidad máxima del otro token (ajustada por slippage).                      |
| `ownerInfo`      | object    | `{ useSOLBalance: true }` para usar el saldo nativo de SOL para el wrapping. |
| `txVersion`      | TxVersion | Versión de la transacción.                                                   |

#### Valor de retorno

La `extInfo` el objeto contiene:

| Campo     | Descripción                                   |
| --------- | --------------------------------------------- |
| `nftMint` | La clave pública mint del NFT de la posición. |

***

### Abriendo una posición desde Liquidity

Usa `raydium.clmm.openPositionFromLiquidity()` como alternativa a `openPositionFromBase()`. En lugar de especificar la cantidad de un token, especificas un valor objetivo de Liquidity y cantidades máximas para ambos tokens.

```typescript
import {
  ApiV3PoolInfoConcentratedItem,
  TickUtils,
  PoolUtils,
  ClmmKeys,
} from '@raydium-io/raydium-sdk-v2'
import BN from 'bn.js'
import Decimal from 'decimal.js'
import { initSdk, txVersion } from '../config'

const createPositionFromLiquidity = async () => {
  const raydium = await initSdk()

  let poolInfo: ApiV3PoolInfoConcentratedItem
  const poolId = '8sN9549P3Zn6xpQRqpApN57xzkCh6sJxLwuEjcG2W4Ji'
  let poolKeys: ClmmKeys | undefined
  const slippage = 0.025

  const data = await raydium.clmm.getPoolInfoFromRpc(poolId)
  poolInfo = data.poolInfo
  poolKeys = data.poolKeys

  const inputAmount = 0.025
  const [startPrice, endPrice] = [560.979027728865622, 647.86110003403338]

  const { tick: lowerTick } = TickUtils.getPriceAndTick({
    poolInfo,
    price: new Decimal(startPrice),
    baseIn: true,
  })

  const { tick: upperTick } = TickUtils.getPriceAndTick({
    poolInfo,
    price: new Decimal(endPrice),
    baseIn: true,
  })

  const epochInfo = await raydium.fetchEpochInfo()
  const res = await PoolUtils.getLiquidityAmountOutFromAmountIn({
    poolInfo,
    slippage: 0,
    inputA: true,
    tickUpper: Math.max(lowerTick, upperTick),
    tickLower: Math.min(lowerTick, upperTick),
    amount: new BN(new Decimal(inputAmount || '0').mul(10 ** poolInfo.mintA.decimals).toFixed(0)),
    add: true,
    amountHasFee: true,
    epochInfo,
  })

  const { execute, extInfo } = await raydium.clmm.openPositionFromLiquidity({
    poolInfo,
    poolKeys,
    tickUpper: Math.max(lowerTick, upperTick),
    tickLower: Math.min(lowerTick, upperTick),
    liquidity: res.liquidity,
    amountMaxA: new BN(new Decimal(inputAmount || '0').mul(10 ** poolInfo.mintA.decimals).toFixed(0)),
    amountMaxB: new BN(
      new Decimal(res.amountSlippageB.amount.toString()).mul(1 + slippage).toFixed(0)
    ),
    ownerInfo: {
      useSOLBalance: true,
    },
    txVersion,
    // opcional: configura aquí la comisión de prioridad
    // computeBudgetConfig: {
    //   units: 600000,
    //   microLamports: 10000,
    // },
  })

  const { txId } = await execute({ sendAndConfirm: true })
  console.log('clmm position opened:', { txId, nft: extInfo.address.nftMint.toBase58() })
}

createPositionFromLiquidity()
```

#### Parámetros (desde Liquidity)

| Parámetro    | Tipo      | Descripción                                                                                   |
| ------------ | --------- | --------------------------------------------------------------------------------------------- |
| `poolInfo`   | object    | Información del pool desde API o RPC.                                                         |
| `poolKeys`   | object    | Claves del pool. Requeridas para devnet.                                                      |
| `tickLower`  | number    | Límite inferior de tick de la posición.                                                       |
| `tickUpper`  | number    | Límite superior de tick de la posición.                                                       |
| `liquidity`  | BN        | Cantidad objetivo de Liquidity.                                                               |
| `amountMaxA` | BN        | Cantidad máxima de token A a depositar (ajustada por slippage), en las unidades más pequeñas. |
| `amountMaxB` | BN        | Cantidad máxima de token B a depositar (ajustada por slippage), en las unidades más pequeñas. |
| `ownerInfo`  | object    | `{ useSOLBalance: true }` para usar el saldo nativo de SOL para el wrapping.                  |
| `txVersion`  | TxVersion | Versión de la transacción.                                                                    |

***

### Cerrando una posición

Usa `raydium.clmm.closePosition()` para cerrar una posición y recuperar el rent de la cuenta. La posición debe tener liquidez cero, comisiones no reclamadas cero y recompensas no reclamadas cero. Retira toda la liquidez y reclama todas las comisiones/recompensas antes de cerrar.

```typescript
import { ApiV3PoolInfoConcentratedItem, ClmmKeys } from '@raydium-io/raydium-sdk-v2'
import { initSdk, txVersion } from '../config'
import { isValidClmm } from './utils'

const closePosition = async () => {
  const raydium = await initSdk()
  let poolInfo: ApiV3PoolInfoConcentratedItem
  const poolId = '2QdhepnKRTLjjSqPL1PtKNwqrUkoLee5Gqs8bvZhRdMv'
  let poolKeys: ClmmKeys | undefined

  if (raydium.cluster === 'mainnet') {
    const data = await raydium.api.fetchPoolById({ ids: poolId })
    poolInfo = data[0] as ApiV3PoolInfoConcentratedItem
    if (!isValidClmm(poolInfo.programId)) throw new Error('target pool is not CLMM pool')
  } else {
    const data = await raydium.clmm.getPoolInfoFromRpc(poolId)
    poolInfo = data.poolInfo
    poolKeys = data.poolKeys
  }

  const allPosition = await raydium.clmm.getOwnerPositionInfo({ programId: poolInfo.programId })
  if (!allPosition.length) throw new Error('user do not have any positions')

  const position = allPosition.find((p) => p.poolId.toBase58() === poolInfo.id)
  if (!position) throw new Error(`user do not have position in pool: ${poolInfo.id}`)

  const { execute } = await raydium.clmm.closePosition({
    poolInfo,
    poolKeys,
    ownerPosition: position,
    txVersion,
  })

  const { txId } = await execute({ sendAndConfirm: true })
  console.log('clmm position closed:', { txId: `https://explorer.solana.com/tx/${txId}` })
}

closePosition()
```

{% hint style="warning" %}
El programa requiere **liquidez cero, comisiones no reclamadas cero y recompensas no reclamadas cero** para cerrar una posición. El enfoque más simple es pasar `closePosition: true` en `decreaseLiquidity()` — el SDK agrupa la reducción (que cobra comisiones y recompensas) y el cierre en una sola transacción. Si se cierra por separado, llama a `harvestAllRewards()` y `decreaseLiquidity()` primero para poner todos los saldos en cero.
{% endhint %}

#### Parámetros para cerrar la posición

| Parámetro       | Tipo      | Descripción                                                |
| --------------- | --------- | ---------------------------------------------------------- |
| `poolInfo`      | object    | Información del pool desde API o RPC.                      |
| `poolKeys`      | object    | Claves del pool. Requeridas para devnet.                   |
| `ownerPosition` | object    | Información de la posición desde `getOwnerPositionInfo()`. |
| `txVersion`     | TxVersion | Versión de la transacción.                                 |
