npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fullsailfinance/sdk

v11.0.0

Published

SDK for FullSail ve(4,4) dex

Downloads

153

Readme

@fullsailfinance/sdk

Overview

TypeScript toolkit for the FullSail ve(4,4) DEX on Sui. It centralizes payload assembly, liquidity management, and token-locking utilities to let applications interact with FullSail mechanics without hand-coding Move transactions.

Prerequisites

  • Node.js ≥ 22
  • @mysten/sui v2 (≥ 2.20) — a peer dependency. Install it alongside the SDK.

Installation

npm:
$ npm i @fullsailfinance/sdk @mysten/sui
yarn:
$ yarn add @fullsailfinance/sdk @mysten/sui
pnpm:
$ pnpm add @fullsailfinance/sdk @mysten/sui

Configuration

import { initFullSailSDK } from '@fullsailfinance/sdk'

const fullSailSDK = initFullSailSDK({
  network: 'mainnet-production',
  // You can also provide your own full node URL and simulation account. It's optional.
  fullNodeUrl: 'https://...',
  simulationAccount: '0x...',
})

After user has connected the wallet, you must set senderAddress to your sdk instance

fullSailSDK.senderAddress = '0x...'
// or
fullSailSDK.setSenderAddress('0x...')

Key Concepts

Gauge

A smart contract that manages staked positions rewards and oSAIL token distribution. Core functions:

  • Position Staking Management: Tracks staked positions, calculates rewards, and enables withdrawal
  • Reward & oSAIL Distribution: Distributes rewards and oSAIL tokens to staked positions. New oSAIL tokens are issued weekly for each epoch
  • Reward Calculation: Calculates earned oSAIL rewards based on staked liquidity and time, and processes reward claims to position owners
  • Fee Collection: Collects and manages fees generated by the associated liquidity pool

Ticks

Discrete price points that define the boundaries of liquidity ranges. Important considerations:

  • Each tick represents a specific price ratio
  • Ticks are spaced at constant intervals determined by the pool's tick spacing

Pool

Initially created without a gauge. It can be added later to enable position staking and voting.

There are two pool entities available through the SDK:

Chain Pool (Pool.getByIdFromChain()): Contains real-time data directly from the blockchain. Data from this entity is always relevant.

Some core/unique properties:

  • currentTickIndex: The tick corresponding to the current price of the pool (determines which liquidity positions are currently active)
  • rewardCoinsInfo: List of reward coins available for this pool
  • currentSqrtPrice: Current sqrt price of the pool

Pool (Pool.getById()): Contains calculated data and metadata from the backend which can be convenient for frontend. It can contain same fields as ChainPool but they may be outdated by a few minutes, especially those that change frequently.

Some core/unique properties:

  • gauge_id: Unique identifier for gauge contract related to this pool
  • gauge_killed: can be true if gauge contract for the pool has been killed. All positions in this pool should be unstaked manually to receive fees.

Use backend pool for stable metadata and gauge_id, use chain pool for relevant current price and reward data.

Position

Represents a range of prices where you provide liquidity to a pool.

Unstaked positions are the default and recommended way to provide liquidity to any pool.
They receive pool fees + pool rewards (regardless of whether the pool has a gauge or not).

When a pool has a gauge, you can choose to stake your position or keep it unstaked:

  • Staked position receives pool rewards + oSAIL tokens.
  • Unstaked position receives pool rewards + pool fees.

For reward abuse protection new positions always have a 20 minutes cooldown before any rewards can be claimed. You can remove liquidity from a position at any time, but if you do so before cooldown period you will not receive a rewards.

Some core properties:

  • tick_lower: The lower bound of your price range (minimum price where your liquidity is active)
  • tick_upper: The upper bound of your price range (maximum price where your liquidity is active)
  • liquidity: The amount of liquidity provided in this price range
  • stake_info: Information about the position stake object within the gauge. Can be undefined if position not staked or no gauge exists for this pool

Position Rewards

Different position states receive different types of rewards:

  • Unstaked position (with or without a gauge): Receives pool fees and pool rewards
  • Staked position (in pool with a gauge): Receives oSAIL tokens and pool rewards
  • Staked position (in pool without a gauge): Receives only pool rewards (it's best to unstake)

Vault

A managed liquidity position created by depositing into a port — a protocol-controlled vault that handles position management automatically. Vaults are always staked and benefit from oSAIL token distributions.

The vault continuously adjusts its price range to keep the position in range at all times, so unlike a regular position it will never stop earning fees due to price moving outside the range.

Unlike regular positions, vaults have no 20-minute cooldown.

Some core properties:

  • port.position: The underlying pool position managed by the vault.
  • port.rewards: List of port incentive rewards distributed to vault participants.
  • port.vault_stopped: Whether the vault has been stopped by the protocol. New deposits are not allowed, but liquidity withdrawal is still available.
  • port.is_paused: Whether the vault is fully paused. All operations including withdrawal are unavailable until unpaused.
  • port_entry.ratio: The user's share of the vault's total liquidity. Used to calculate the user's proportional token amounts and withdrawable liquidity.
  • port_entry.destroyed: Whether the vault position has been closed.

Vault Rewards

Since vault positions are always staked, they receive all three types of rewards:

  • oSAIL tokens
  • Pool rewards
  • Port rewards

oSAIL

oSAIL is the emissions token distributed to staked positions. Each epoch has its own oSAIL token with a specific expiration date - 5 weeks from the start of the epoch. In every method which includes claim rewards rewardChoice field is required to determine which path to take. It can be one of the following values:

  1. "vesail" - lock it into veSAIL to participate in governance and earn trading fees.
  2. "sail" - redeem it for liquid SAIL in a 2 to 1 ratio (100 oSAIL will be redeemed for 50 SAIL).
  3. "usd" - redeem it for USDC in a 50% of the current spot price at time of redemption.

Expired oSAIL can be only locked into veSAIL.

Lock (veSAIL)

Represents voting power and used only for voting. Minted by locking SAIL or oSAIL for a fixed duration. Lock amount multiplied by lock duration determines voting power. isPermanent option commits to the longest available lock period for maximum voting power.

Some core properties:

  • permanent: whether lock is permanent or not
  • voting_power: current voting power provided by this lock. Reduces slowly over time if lock is not permanent
  • is_voting_onchain: whether lock is used for voting at current epoch
  • amount: amount of SAIL locked

Locks are transferable, supports merging, splitting, increasing amount/duration. These operations are only available while the lock is non-zero and not expired.

Once a lock expires, the locked SAIL becomes withdrawable and can be redeemed back as liquid SAIL.

After a merge, the source lock becomes nulled. After a split, the original lock becomes nulled and two new locks are created. Nulled locks should be cleaned up: the preferred way is to pass them via nulledLockIds in any of the claim methods so destruction happens in the same transaction. Alternatively, they can be destroyed explicitly via destroyNulledTransaction or destroyNulledListTransaction. In either case, ensure all rewards are claimed from the nulled lock before destroying it.

If a user has any locks with v1_version set, they must migrate all such locks before any other operations are allowed. Until migrated, locks can only be fetched (by ID or as a list) — no additional data should be fetched and no operations should be available.

Epoch

7-day cycle during which veSAIL holders vote and predict trading volumes for pools.

Voting

veSAIL holders can vote and predict trading volumes for pools for the next epoch. Only pools with gauge can be voted.

Voters earn from Trading fees from the pools they vote for. Fees are distributed by prediction accuracy and allocated voting power.

Usage examples

If method name ends with ...Transaction, it returns a transaction that should be signed and executed using your sui client or wallet kit. It also accepts transaction as second parameter if you want to add this transaction to existing batch. There are separate methods to create/add liquidity/remove liquidity/close for both kinds of positions (staked and unstaked) if you want to have more control or stronger types.

Pools

Get pool list

Returns a paginated list of pools with metadata.

const { full_pools, pagination } = await fullSailSDK.Pool.getList({
  pagination: { page: 0, page_size: 20 },
  ...,
})

Get pools by coins

Returns all pools matching the given coin pair and tick spacing.

const pools = await fullSailSDK.Pool.getListByCoins({
  coinTypeA: '0x0...',
  coinTypeB: '0x...',
  tickSpacing: 40,
})

Get pool by ID (offchain)

Returns backend pool data by pool ID.

const poolId = '0x0...'

const { pool } = await fullSailSDK.Pool.getById(poolId)

Get pool by ID (onchain)

Returns real-time pool data directly from the blockchain by pool ID.

const poolId = '0x0...'

const chainPool = await fullSailSDK.Pool.getByIdFromChain(poolId)

Get pools with ports

Returns all pools that have at least one port, along with their ports and the total cumulative yields in USD across all returned ports. Optionally accepts an account address – when provided, each pool's port_exists flag indicates whether the account has port entries for that pool.

const { pools, total_cumulative_yields_usd } = await fullSailSDK.Pool.getListWithPorts({
  account_address: '0x0...', // optional
})

for (const { pool, ports } of pools) {
  // pool — FullPool object with metadata
  // ports — Port[] for this pool
}

Get pools for voting

Returns a paginated list of pools available for voting, and other stats. Pass account_address to include the user's prediction and voting power ratio for each pool.

const { pools, pagination } = await fullSailSDK.Pool.getVotingList({
  account_address: '0x0...', // optional: includes user_prediction and user_voting_power_ratio
  with_votes: true, // optional: only pools that have received votes
  page: 0,
  page_size: 20,
})

for (const { pool } of pools) {
  // pool — Pool object
  // ...and other stats
}

Positions

Get position by ID

Returns position data by position ID.

const positionId = '0x0...'

const position = await fullSailSDK.Position.getById(positionId)

Get positions by account

Returns a list of pools along with the positions and vault positions (strategies) belonging to each pool for a given account address.

const accountAddress = '0x0...'

const { pools } = await fullSailSDK.Position.getListByAccountV2({
  address: accountAddress,
})

for (const { pool, positions, strategies } of pools) {
  // pool       — Pool object
  // positions  — Position[] for this pool
  // strategies — Strategy[] (vault positions) for this pool
}

Get positions by pool

Returns the pool and a paginated list of positions for a given pool ID.

const poolId = '0x0...'

const { pool, positions, pagination } = await fullSailSDK.Position.getListByPool({
  pool_id: poolId,
  page: 0,
  page_size: 20,
})

for (const { position } of positions) {
  // position — Position for this pool
}

Get pool reward amounts for position

Returns the pool reward amounts for a single position.

const poolId = '0x0...'
const positionId = '0x0...'

const { pool } = await fullSailSDK.Pool.getById(poolId)

const rewards = await fullSailSDK.Position.getRewardCoinsWithAmount({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  poolId,
  positionId,
  rewardCoinTypes: pool.rewards?.map(({ token }) => token.address) ?? [],
})

for (const { coinType, amountOwned } of rewards) {
  // coinType    — reward coin type address
  // amountOwned — claimable amount as bigint
}

Get pool reward amounts for multiple positions

Batch version of getRewardCoinsWithAmount — fetches pool reward amounts for multiple positions in a single RPC call.

const poolId = '0x0...'

const { pool } = await fullSailSDK.Pool.getById(poolId)

const rewardsList = await fullSailSDK.Position.getRewardCoinsWithAmountList([
  {
    coinTypeA: pool.token_a.address,
    coinTypeB: pool.token_b.address,
    poolId,
    positionId: '0x0...',
    rewardCoinTypes: pool.rewards?.map(({ token }) => token.address) ?? [],
  },
  // ...more entries
])

// rewardsList[i] — PositionRewardAmountsResultItem[] for the i-th position
for (const positionRewards of rewardsList) {
  for (const { coinType, amountOwned } of positionRewards) {
    // coinType    — reward coin type address
    // amountOwned — claimable amount as bigint
  }
}

Get fee amounts for position

Returns the pool fee amounts for a single unstaked position.

const poolId = '0x0...'
const positionId = '0x0...'

const { pool } = await fullSailSDK.Pool.getById(poolId)

const { feeOwnedA, feeOwnedB } = await fullSailSDK.Position.getFeeAmounts({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  poolId,
  positionId,
})
// feeOwnedA — claimable fee amount for coin A as bigint
// feeOwnedB — claimable fee amount for coin B as bigint

Get fee amounts for multiple positions

Batch version of getFeeAmounts — fetches pool fee amounts for multiple unstaked positions in a single RPC call.

const poolId = '0x0...'

const { pool } = await fullSailSDK.Pool.getById(poolId)

const feesList = await fullSailSDK.Position.getFeeAmountsList([
  {
    coinTypeA: pool.token_a.address,
    coinTypeB: pool.token_b.address,
    poolId,
    positionId: '0x0...',
  },
  // ...more entries
])

// feesList[i] — PositionFeeAmountsResult for the i-th position
for (const { feeOwnedA, feeOwnedB } of feesList) {
  // feeOwnedA — claimable fee amount for coin A as bigint
  // feeOwnedB — claimable fee amount for coin B as bigint
}

Get oSAIL rewards for position

Returns the claimable oSAIL reward amount for a single staked position.

const poolId = '0x0...'
const positionId = '0x0...'

const { pool } = await fullSailSDK.Pool.getById(poolId)
const currentEpochOSail = await fullSailSDK.Coin.getCurrentEpochOSail()

const amount = await fullSailSDK.Position.getOSailRewards({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  poolId,
  gaugeId: pool.gauge_id,
  positionId,
  oSailCoinType: currentEpochOSail.address,
})
// amount — claimable oSAIL amount as bigint

Get oSAIL rewards for multiple positions

Batch version of getOSailRewards — fetches claimable oSAIL reward amounts for multiple staked positions in a single RPC call.

const poolId = '0x0...'

const { pool } = await fullSailSDK.Pool.getById(poolId)
const currentEpochOSail = await fullSailSDK.Coin.getCurrentEpochOSail()

const amounts = await fullSailSDK.Position.getOSailRewardsList([
  {
    coinTypeA: pool.token_a.address,
    coinTypeB: pool.token_b.address,
    poolId,
    gaugeId: pool.gauge_id,
    positionId: '0x0...',
    oSailCoinType: currentEpochOSail.address,
  },
  // ...more entries
])

// amounts[i] — claimable oSAIL amount as bigint for the i-th position

Create position

If the pool has a gauge, you can choose between unstaked and staked when creating a position.

To do this, you need to pass the gaugeId and ensure that pool.gauge_killed is not true.

import { ClmmPoolUtil, Percentage, TickMath } from '@fullsailfinance/sdk'

const poolId = '0x0...'

const amountA = 1000000n
const fixedAmountA = true
const roundUp = true
// 1%
const slippage = Percentage.fromNumber(1)

const { pool } = await fullSailSDK.Pool.getById(poolId)
const chainPool = await fullSailSDK.Pool.getByIdFromChain(poolId)

// get the nearest tick indexes based on current price
const tickLower = TickMath.getPrevInitializableTickIndex(chainPool.currentTickIndex, chainPool.tickSpacing)
const tickUpper = TickMath.getNextInitializableTickIndex(chainPool.currentTickIndex, chainPool.tickSpacing)

// Calculate the opposite coin amount based on the first coin amount and tick range
// This ensures proper liquidity ratio within the specified price range, including slippage
const { maxAmountB } = ClmmPoolUtil.estLiquidityAndCoinAmountFromOneAmounts(
  tickLower,
  tickUpper,
  amountA, // change to amountB if you want to calculate maxAmountA based on amountB
  fixedAmountA, // change to false if you want to calculate maxAmountA based on amountB
  roundUp,
  slippage.toCoefficient(),
  chainPool.currentSqrtPrice,
)

const transaction = await fullSailSDK.Position.openTransaction({
  coinTypeA: chainPool.coinTypeA,
  coinTypeB: chainPool.coinTypeB,
  poolId,
  tickLower,
  tickUpper,
  amountA,
  amountB: maxAmountB,
  slippage,
  fixedAmountA,
  currentSqrtPrice: chainPool.currentSqrtPrice,
  // If you want to create a staked position, pass the gaugeId
  gaugeId: pool.gauge_killed ? undefined : pool.gauge_id,
})

Add liquidity to position

All rewards claimed automatically within this transaction.

import { ClmmPoolUtil, MAX_LOCK_DURATION_DAYS, Percentage } from '@fullsailfinance/sdk'

const poolId = '0x0...'
const positionId = '0x0...'
// your existing permanent lock id
const permanentLockId = '0x0...'
// 'vesail' | 'sail' | 'usd'
const rewardChoice = 'vesail'

const amountA = 1000000n
const fixedAmountA = true
const roundUp = true

// 1%
const slippage = Percentage.fromNumber(1)

const { pool } = await fullSailSDK.Pool.getById(poolId)
const chainPool = await fullSailSDK.Pool.getByIdFromChain(poolId)
const position = await fullSailSDK.Position.getById(positionId)

let currentEpochOSail
let oSailReward
if (position.staked && pool.gauge_id) {
  currentEpochOSail = await fullSailSDK.Coin.getCurrentEpochOSail()
  oSailReward = await fullSailSDK.Position.getOSailRewards({
    poolId,
    coinTypeA: chainPool.coinTypeA,
    coinTypeB: chainPool.coinTypeB,
    positionId,
    oSailCoinType: currentEpochOSail.address,
    gaugeId: pool.gauge_id,
  })
}

// Calculate the opposite coin amount based on the first coin amount and tick range
// This ensures proper liquidity ratio within the specified price range, including slippage
const { maxAmountB } = ClmmPoolUtil.estLiquidityAndCoinAmountFromOneAmounts(
  position.tick_lower,
  position.tick_upper,
  amountA,
  fixedAmountA,
  roundUp,
  slippage.toCoefficient(),
  chainPool.currentSqrtPrice,
)

const transaction = await fullSailSDK.Position.addLiquidityTransaction({
  coinTypeA: chainPool.coinTypeA,
  coinTypeB: chainPool.coinTypeB,
  poolId,
  positionId,
  tickLower: position.tick_lower,
  tickUpper: position.tick_upper,
  amountA,
  amountB: maxAmountB,
  slippage,
  fixedAmountA,
  currentSqrtPrice: chainPool.currentSqrtPrice,
  rewardCoinTypes: chainPool.rewardCoinsInfo.map(({ coinType }) => coinType),
  // params below are required only if position is staked
  gaugeId: pool.gauge_id,
  positionStakeId: position.stake_info?.id,
  oSailCoinType: currentEpochOSail?.address,
  oSailAmount: oSailReward,
  rewardChoice,
  positionUpdatedAt: position.updated_at,
  // if rewardChoice is "vesail", you can provide your existing permanent lock id and oSAIL will be added to it
  // otherwise new lock will be created every call
  permanentLockId,
  newLockDurationDays: MAX_LOCK_DURATION_DAYS,
  newLockIsPermanent: true,
})

Calculate zap-in deposit

Calculates how a single-coin deposit should be split between the part that is added to the position as-is (fixedAmountIn) and the part that is swapped into the other coin (swapAmountIn). The result is consumed by Create position (zap-in) and Add liquidity to position (zap-in).

Set fixedAmountA to true when the user provides coin A and the SDK should swap part of it into coin B, or false for the opposite direction. If the current price is outside the [tickLower, tickUpper] range, no swap is needed and the entire inputAmount is used as-is (the result has swapAmountIn: '0' and swapRoute: undefined).

import { Percentage } from '@fullsailfinance/sdk'

const poolId = '0x0...'

const inputAmount = 1000000n
const fixedAmountA = true

// 1%
const slippage = Percentage.fromNumber(1)

const { pool } = await fullSailSDK.Pool.getById(poolId)
const chainPool = await fullSailSDK.Pool.getByIdFromChain(poolId)

const tickLower = TickMath.getPrevInitializableTickIndex(chainPool.currentTickIndex, chainPool.tickSpacing)
const tickUpper = TickMath.getNextInitializableTickIndex(chainPool.currentTickIndex, chainPool.tickSpacing)

const calculateZapInDepositResult = await fullSailSDK.Position.calculateZapInDeposit({
  coinTypeA: chainPool.coinTypeA,
  coinTypeB: chainPool.coinTypeB,
  coinDecimalsA: pool.token_a.decimals,
  coinDecimalsB: pool.token_b.decimals,
  tickLower,
  tickUpper,
  inputAmount,
  fixedAmountA,
  slippage,
  currentSqrtPrice: chainPool.currentSqrtPrice,
})
// inputAmountIn   — full input amount (as decimal string)
// fixedAmountIn   — part of the input added to the position as-is
// swapAmountIn    — part of the input that will be swapped into the other coin
// targetAmountOut — expected output of the swap
// swapRoute       — resolved swap route (undefined when no swap is needed)
// fixedAmountA    — fixedAmountA flag from params
// slippage        — slippage from params

Create position (zap-in)

Creates a new position by providing a single coin — part of it is swapped into the other coin automatically before opening the position. Pass the result of calculateZapInDeposit as calculateZapInDepositResult.

If the pool has a gauge, you can choose between unstaked and staked when creating a position.

To do this, you need to pass the gaugeId and ensure that pool.gauge_killed is not true.

import { Percentage, TickMath } from '@fullsailfinance/sdk'

const poolId = '0x0...'

const inputAmount = 1000000n
const fixedAmountA = true

// 1%
const slippage = Percentage.fromNumber(1)

const { pool } = await fullSailSDK.Pool.getById(poolId)
const chainPool = await fullSailSDK.Pool.getByIdFromChain(poolId)

const tickLower = TickMath.getPrevInitializableTickIndex(chainPool.currentTickIndex, chainPool.tickSpacing)
const tickUpper = TickMath.getNextInitializableTickIndex(chainPool.currentTickIndex, chainPool.tickSpacing)

const calculateZapInDepositResult = await fullSailSDK.Position.calculateZapInDeposit({
  coinTypeA: chainPool.coinTypeA,
  coinTypeB: chainPool.coinTypeB,
  coinDecimalsA: pool.token_a.decimals,
  coinDecimalsB: pool.token_b.decimals,
  tickLower,
  tickUpper,
  inputAmount,
  fixedAmountA,
  slippage,
  currentSqrtPrice: chainPool.currentSqrtPrice,
})

const transaction = await fullSailSDK.Position.openZapInTransaction({
  coinTypeA: chainPool.coinTypeA,
  coinTypeB: chainPool.coinTypeB,
  coinDecimalsA: pool.token_a.decimals,
  coinDecimalsB: pool.token_b.decimals,
  poolId,
  tickLower,
  tickUpper,
  slippage,
  currentSqrtPrice: chainPool.currentSqrtPrice,
  calculateZapInDepositResult,
  // pass gaugeId to open a staked position
  gaugeId: pool.gauge_killed ? undefined : pool.gauge_id,
})

Add liquidity to position (zap-in)

Adds liquidity to an existing position using a single coin — part of it is swapped into the other coin automatically. Pass the result of calculateZapInDeposit as calculateZapInDepositResult.

All rewards claimed automatically within this transaction.

import { MAX_LOCK_DURATION_DAYS, Percentage } from '@fullsailfinance/sdk'

const poolId = '0x0...'
const positionId = '0x0...'
// your existing permanent lock id
const permanentLockId = '0x0...'
// 'vesail' | 'sail' | 'usd'
const rewardChoice = 'vesail'

const inputAmount = 1000000n
const fixedAmountA = true

// 1%
const slippage = Percentage.fromNumber(1)

const { pool } = await fullSailSDK.Pool.getById(poolId)
const chainPool = await fullSailSDK.Pool.getByIdFromChain(poolId)
const position = await fullSailSDK.Position.getById(positionId)

let currentEpochOSail
let oSailReward
if (position.staked && pool.gauge_id) {
  currentEpochOSail = await fullSailSDK.Coin.getCurrentEpochOSail()
  oSailReward = await fullSailSDK.Position.getOSailRewards({
    poolId,
    coinTypeA: chainPool.coinTypeA,
    coinTypeB: chainPool.coinTypeB,
    positionId,
    oSailCoinType: currentEpochOSail.address,
    gaugeId: pool.gauge_id,
  })
}

const calculateZapInDepositResult = await fullSailSDK.Position.calculateZapInDeposit({
  coinTypeA: chainPool.coinTypeA,
  coinTypeB: chainPool.coinTypeB,
  coinDecimalsA: pool.token_a.decimals,
  coinDecimalsB: pool.token_b.decimals,
  tickLower: position.tick_lower,
  tickUpper: position.tick_upper,
  inputAmount,
  fixedAmountA,
  slippage,
  currentSqrtPrice: chainPool.currentSqrtPrice,
})

const transaction = await fullSailSDK.Position.addLiquidityZapInTransaction({
  coinTypeA: chainPool.coinTypeA,
  coinTypeB: chainPool.coinTypeB,
  coinDecimalsA: pool.token_a.decimals,
  coinDecimalsB: pool.token_b.decimals,
  poolId,
  positionId,
  tickLower: position.tick_lower,
  tickUpper: position.tick_upper,
  slippage,
  currentSqrtPrice: chainPool.currentSqrtPrice,
  calculateZapInDepositResult,
  rewardCoinTypes: chainPool.rewardCoinsInfo.map(({ coinType }) => coinType),
  // params below are required only if position is staked
  gaugeId: pool.gauge_id,
  positionStakeId: position.stake_info?.id,
  oSailCoinType: currentEpochOSail?.address,
  oSailAmount: oSailReward,
  rewardChoice,
  positionUpdatedAt: position.updated_at,
  // if rewardChoice is "vesail", you can provide your existing permanent lock id and oSAIL will be added to it
  // otherwise new lock will be created every call
  permanentLockId,
  newLockDurationDays: MAX_LOCK_DURATION_DAYS,
  newLockIsPermanent: true,
})

Stake position

Can be used at any time for an unstaked position, provided the pool has a gauge (pool.gauge_id exists and pool.gauge_killed is not true).
Only fees claimed automatically within this transaction. Pool rewards are not.

const poolId = '0x0...'
const positionId = '0x0...'

const position = await fullSailSDK.Position.getById(positionId)
const { pool } = await fullSailSDK.Pool.getById(poolId)

const transaction = await fullSailSDK.Position.stakeTransaction({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  poolId: pool.address,
  positionId: position.id,
  gaugeId: pool.gauge_id,
})

Unstake position

Use this in the following cases:

  • If you want to change the position type to "unstaked" even while the pool's gauge is active.
  • If the gauge contract for the pool was killed (pool.gauge_killed is true) and the position is staked. Staked positions within a pool with killed gauge will receive only pool rewards.

Both oSAIL and pool rewards are claimed automatically within this transaction.

import { MAX_LOCK_DURATION_DAYS, Percentage } from '@fullsailfinance/sdk'

const poolId = '0x0...'
const positionId = '0x0...'
// your existing permanent lock id
const permanentLockId = '0x0...'
// 'vesail' | 'sail' | 'usd'
const rewardChoice = 'vesail'

// 1%
const slippage = Percentage.fromNumber(1)

const position = await fullSailSDK.Position.getById(positionId)
const { pool } = await fullSailSDK.Pool.getById(poolId)
const chainPool = await fullSailSDK.Pool.getByIdFromChain(poolId)

const currentEpochOSail = await fullSailSDK.Coin.getCurrentEpochOSail()
const oSailReward = await fullSailSDK.Position.getOSailRewards({
  poolId,
  coinTypeA: chainPool.coinTypeA,
  coinTypeB: chainPool.coinTypeB,
  positionId,
  oSailCoinType: currentEpochOSail.address,
  gaugeId: pool.gauge_id,
})

const transaction = await fullSailSDK.Position.unstakeTransaction({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  poolId: pool.address,
  positionStakeId: position.stake_info.id,
  gaugeId: pool.gauge_id,
  rewardCoinTypes: chainPool.rewardCoinsInfo.map(({ coinType }) => coinType),
  oSailCoinType: currentEpochOSail.address,
  rewardChoice,
  oSailAmount: oSailReward,
  positionUpdatedAt: position.updated_at,
  slippage,
  // if rewardChoice is "vesail", you can provide your existing permanent lock id and oSAIL will be added to it
  // otherwise new lock will be created every call
  permanentLockId,
  newLockDurationDays: MAX_LOCK_DURATION_DAYS,
  newLockIsPermanent: true,
})

Remove liquidity from position

All rewards claimed automatically within this transaction.

import { MAX_LOCK_DURATION_DAYS, Percentage } from '@fullsailfinance/sdk'

const poolId = '0x0...'
const positionId = '0x0...'
// your existing permanent lock id
const permanentLockId = '0x0...'
// 'vesail' | 'sail' | 'usd'
const rewardChoice = 'vesail'

// liquidity amount to remove
const liquidity = 1000000n
// 1%
const slippage = Percentage.fromNumber(1)

const { pool } = await fullSailSDK.Pool.getById(poolId)
const chainPool = await fullSailSDK.Pool.getByIdFromChain(poolId)
const position = await fullSailSDK.Position.getById(positionId)

let currentEpochOSail
let oSailReward
if (position.staked && pool.gauge_id) {
  currentEpochOSail = await fullSailSDK.Coin.getCurrentEpochOSail()
  oSailReward = await fullSailSDK.Position.getOSailRewards({
    poolId,
    coinTypeA: chainPool.coinTypeA,
    coinTypeB: chainPool.coinTypeB,
    positionId,
    oSailCoinType: currentEpochOSail.address,
    gaugeId: pool.gauge_id,
  })
}

const transaction = await fullSailSDK.Position.removeLiquidityTransaction({
  coinTypeA: chainPool.coinTypeA,
  coinTypeB: chainPool.coinTypeB,
  poolId,
  positionId,
  tickLower: position.tick_lower,
  tickUpper: position.tick_upper,
  liquidity,
  slippage,
  currentSqrtPrice: chainPool.currentSqrtPrice,
  rewardCoinTypes: chainPool.rewardCoinsInfo.map(({ coinType }) => coinType),
  // params below are required only if position is staked
  gaugeId: pool.gauge_id,
  positionStakeId: position.stake_info?.id,
  oSailCoinType: currentEpochOSail?.address,
  oSailAmount: oSailReward,
  positionUpdatedAt: position.updated_at,
  rewardChoice,
  // if rewardChoice is "vesail", you can provide your existing permanent lock id and oSAIL will be added to it
  // otherwise new lock will be created every call
  permanentLockId,
  newLockDurationDays: MAX_LOCK_DURATION_DAYS,
  newLockIsPermanent: true,
})

Close position

All rewards claimed automatically within this transaction.

import { MAX_LOCK_DURATION_DAYS, Percentage } from '@fullsailfinance/sdk'

const poolId = '0x0...'
const positionId = '0x0...'
// your existing permanent lock id
const permanentLockId = '0x0...'
// 'vesail' | 'sail' | 'usd'
const rewardChoice = 'vesail'

// 1%
const slippage = Percentage.fromNumber(1)

const { pool } = await fullSailSDK.Pool.getById(poolId)
const chainPool = await fullSailSDK.Pool.getByIdFromChain(poolId)
const position = await fullSailSDK.Position.getById(positionId)

let currentEpochOSail
let oSailReward
if (position.staked && pool.gauge_id) {
  currentEpochOSail = await fullSailSDK.Coin.getCurrentEpochOSail()
  oSailReward = await fullSailSDK.Position.getOSailRewards({
    poolId,
    coinTypeA: chainPool.coinTypeA,
    coinTypeB: chainPool.coinTypeB,
    positionId,
    oSailCoinType: currentEpochOSail.address,
    gaugeId: pool.gauge_id,
  })
}

const transaction = await fullSailSDK.Position.closeTransaction({
  coinTypeA: chainPool.coinTypeA,
  coinTypeB: chainPool.coinTypeB,
  poolId,
  positionId,
  tickLower: position.tick_lower,
  tickUpper: position.tick_upper,
  currentSqrtPrice: chainPool.currentSqrtPrice,
  liquidity: BigInt(position.liquidity),
  slippage,
  // You MUST provide the exact reward coin list to claim all available rewards.
  rewardCoinTypes: chainPool.rewardCoinsInfo.map(({ coinType }) => coinType),
  // params below are required only if position is staked
  gaugeId: pool.gauge_id,
  positionStakeId: position.stake_info?.id,
  oSailCoinType: currentEpochOSail?.address,
  oSailAmount: oSailReward,
  positionUpdatedAt: position.updated_at,
  rewardChoice,
  // if rewardChoice is "vesail", you can provide your existing permanent lock id and oSAIL will be added to it
  // otherwise new lock will be created every call
  permanentLockId,
  newLockDurationDays: MAX_LOCK_DURATION_DAYS,
  newLockIsPermanent: true,
})

Claim fee

Can be used only if position is not staked.

const poolId = '0x0...'
const positionId = '0x0...'

const { pool } = await fullSailSDK.Pool.getById(poolId)

const transaction = await fullSailSDK.Position.claimFeeTransaction({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  poolId,
  positionId,
})

Claim pool rewards for unstaked position

Can be used only if position is not staked.

const poolId = '0x0...'
const positionId = '0x0...'

const chainPool = await fullSailSDK.Pool.getByIdFromChain(poolId)

const transaction = await fullSailSDK.Position.claimUnstakedPoolRewardsTransaction({
  coinTypeA: chainPool.coinTypeA,
  coinTypeB: chainPool.coinTypeB,
  poolId,
  positionId,
  rewardCoinTypes: chainPool.rewardCoinsInfo.map(({ coinType }) => coinType),
})

Claim all unstaked position rewards

Can be used only if position is not staked. It claims both fee and pool rewards.

const poolId = '0x0...'
const positionId = '0x0...'

const chainPool = await fullSailSDK.Pool.getByIdFromChain(poolId)

const transaction = await fullSailSDK.Position.claimFeeAndUnstakedPoolRewardsTransaction({
  coinTypeA: chainPool.coinTypeA,
  coinTypeB: chainPool.coinTypeB,
  poolId,
  positionId,
  rewardCoinTypes: chainPool.rewardCoinsInfo.map(({ coinType }) => coinType),
})

Claim oSAIL

Can be used only if position is staked.

const poolId = '0x0...'
const positionId = '0x0...'

const { pool } = await fullSailSDK.Pool.getById(poolId)
const position = await fullSailSDK.Position.getById(positionId)
const currentEpochOSail = await fullSailSDK.Coin.getCurrentEpochOSail()

const transaction = await fullSailSDK.Position.claimOSailTransaction({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  poolId,
  positionStakeId: position.stake_info.id,
  oSailCoinType: currentEpochOSail.address,
  gaugeId: pool.gauge_id,
})

Claim pool rewards for staked position

Can be used only if position is staked.

const poolId = '0x0...'
const positionId = '0x0...'

const { pool } = await fullSailSDK.Pool.getById(poolId)
const chainPool = await fullSailSDK.Pool.getByIdFromChain(poolId)
const position = await fullSailSDK.Position.getById(positionId)

const transaction = await fullSailSDK.Position.claimStakedPoolRewardsTransaction({
  coinTypeA: chainPool.coinTypeA,
  coinTypeB: chainPool.coinTypeB,
  poolId,
  positionStakeId: position.stake_info.id,
  rewardCoinTypes: chainPool.rewardCoinsInfo.map(({ coinType }) => coinType),
  gaugeId: pool.gauge_id,
})

Claim all staked position rewards

Claims both oSAIL and pool rewards for staked position.

import { MAX_LOCK_DURATION_DAYS, Percentage } from '@fullsailfinance/sdk'

const poolId = '0x0...'
const positionId = '0x0...'
// your existing permanent lock id
const permanentLockId = '0x0...'
// 'vesail' | 'sail' | 'usd'
const rewardChoice = 'vesail'

// 1%
const slippage = Percentage.fromNumber(1)

const { pool } = await fullSailSDK.Pool.getById(poolId)
const chainPool = await fullSailSDK.Pool.getByIdFromChain(poolId)
const position = await fullSailSDK.Position.getById(positionId)

const currentEpochOSail = await fullSailSDK.Coin.getCurrentEpochOSail()
const oSailReward = await fullSailSDK.Position.getOSailRewards({
  poolId,
  coinTypeA: chainPool.coinTypeA,
  coinTypeB: chainPool.coinTypeB,
  positionId,
  oSailCoinType: currentEpochOSail.address,
  gaugeId: pool.gauge_id,
})

const transaction = await fullSailSDK.Position.claimOSailAndStakedPoolRewardsTransaction({
  coinTypeA: chainPool.coinTypeA,
  coinTypeB: chainPool.coinTypeB,
  poolId,
  rewardCoinTypes: chainPool.rewardCoinsInfo.map(({ coinType }) => coinType),
  positionStakeId: position.stake_info.id,
  gaugeId: pool.gauge_id,
  slippage,
  oSailCoinType: currentEpochOSail.address,
  oSailAmount: oSailReward,
  positionUpdatedAt: position.updated_at,
  rewardChoice,
  // if rewardChoice is "vesail", you can provide your existing permanent lock id and oSAIL will be added to it
  // otherwise new lock will be created every call
  permanentLockId,
  newLockDurationDays: MAX_LOCK_DURATION_DAYS,
  newLockIsPermanent: true,
})

Claim all rewards for multiple positions

The combined method for claiming all possible rewards from multiple positions.

import { MAX_LOCK_DURATION_DAYS, Percentage } from '@fullsailfinance/sdk'

const poolId = '0x0...'
const positionId = '0x0...'
// your existing permanent lock id
const permanentLockId = '0x0...'
// 'vesail' | 'sail' | 'usd'
const rewardChoice = 'vesail'

// 1%
const slippage = Percentage.fromNumber(1)

const { pool } = await fullSailSDK.Pool.getById(poolId)
const chainPool = await fullSailSDK.Pool.getByIdFromChain(poolId)
const position = await fullSailSDK.Position.getById(positionId)
const sailCoin = await fullSailSDK.Coin.getByType(fullSailSDK.config.sailCoinType)

let currentEpochOSail
let oSailReward
if (position.staked && pool.gauge_id) {
  currentEpochOSail = await fullSailSDK.Coin.getCurrentEpochOSail()
  oSailReward = await fullSailSDK.Position.getOSailRewards({
    poolId,
    coinTypeA: chainPool.coinTypeA,
    coinTypeB: chainPool.coinTypeB,
    positionId,
    oSailCoinType: currentEpochOSail.address,
    gaugeId: pool.gauge_id,
  })
}

const transaction = await fullSailSDK.Position.claimAllTransaction([
  {
    coinTypeA: chainPool.coinTypeA,
    coinTypeB: chainPool.coinTypeB,
    poolId,
    positionId,
    rewardCoinTypes: chainPool.rewardCoinsInfo.map(({ coinType }) => coinType),
    // params below are required only if position is staked
    gaugeId: pool.gauge_id,
    positionStakeId: position.stake_info?.id,
    oSailCoinType: currentEpochOSail?.address,
    oSailAmount: oSailReward,
    oSailDecimals: sailCoin.decimals,
    sailPrice: sailCoin.current_price,
    positionUpdatedAt: position.updated_at,
    rewardChoice,
    slippage,
    // if rewardChoice is "vesail", you can provide your existing permanent lock id and oSAIL will be added to it
    // otherwise new lock will be created every call
    permanentLockId,
    newLockDurationDays: MAX_LOCK_DURATION_DAYS,
    newLockIsPermanent: true,
  },
])

Vaults

Get vault by ID

Returns vault (port entry) data along with its associated port by port entry ID.

const portEntryId = '0x0...'

const { port_entry, port } = await fullSailSDK.PortEntry.getById(portEntryId)
// port_entry — PortEntry object
// port      — Port object associated with this vault

Get vault positions by account

Returns a list of pools along with the positions and vault positions (strategies) belonging to each pool for a given account address.

const accountAddress = '0x0...'

const { pools } = await fullSailSDK.Position.getListByAccountV2({
  address: accountAddress,
})

for (const { pool, positions, strategies } of pools) {
  // pool       — Pool object
  // positions  — Position[] for this pool
  // strategies — Strategy[] where each item has port_entry (PortEntry) and port (Port)
}

Get pool rewards

Returns the claimable pool reward amount for each reward coin type for a given vault position.

const poolId = '0x0...'
const portEntryId = '0x0...'

const { pool } = await fullSailSDK.Pool.getById(poolId)
const { port_entry, port } = await fullSailSDK.PortEntry.getById(portEntryId)

const rewards = await fullSailSDK.PortEntry.getPoolRewards({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  poolId,
  gaugeId: pool.gauge_id,
  portId: port.id,
  portEntryId: port_entry.id,
  poolRewardCoinTypes: pool.rewards?.map(({ token }) => token.address) ?? [],
})

for (const { coinType, amountOwned } of rewards) {
  // coinType    — reward coin type address
  // amountOwned — claimable amount as bigint
}

Get pool rewards list

Batch version of getPoolRewards — fetches claimable pool reward amounts for multiple vault positions in a single RPC call.

const poolId = '0x0...'
const portEntryId = '0x0...'

const { pool } = await fullSailSDK.Pool.getById(poolId)
const { port_entry, port } = await fullSailSDK.PortEntry.getById(portEntryId)

const rewards = await fullSailSDK.PortEntry.getPoolRewardsList([
  {
    coinTypeA: pool.token_a.address,
    coinTypeB: pool.token_b.address,
    poolId,
    gaugeId: pool.gauge_id,
    portId: port.id,
    portEntryId: port_entry.id,
    poolRewardCoinTypes: pool.rewards?.map(({ token }) => token.address) ?? [],
  },
  // ...more entries
])

// rewards[i] — PortEntryGetPoolRewardsResultItem[] for the i-th entry
for (const entryRewards of rewards) {
  for (const { coinType, amountOwned } of entryRewards) {
    // coinType    — reward coin type address
    // amountOwned — claimable amount as bigint
  }
}

Get port rewards

Returns the claimable port (vault incentive) reward amount for each reward coin type for a given vault position.

const portEntryId = '0x0...'

const { port_entry, port } = await fullSailSDK.PortEntry.getById(portEntryId)

const rewards = await fullSailSDK.PortEntry.getPortRewards({
  portId: port.id,
  portEntryId: port_entry.id,
  portRewardCoinTypes: port.rewards.map(({ token }) => token.address),
})

for (const { coinType, amountOwned } of rewards) {
  // coinType    — reward coin type address
  // amountOwned — claimable amount as bigint
}

Get port rewards list

Batch version of getPortRewards — fetches claimable port reward amounts for multiple vault positions in a single RPC call.

const portEntryId = '0x0...'

const { port_entry, port } = await fullSailSDK.PortEntry.getById(portEntryId)

const rewards = await fullSailSDK.PortEntry.getPortRewardsList([
  {
    portId: port.id,
    portEntryId: port_entry.id,
    portRewardCoinTypes: port.rewards.map(({ token }) => token.address),
  },
  // ...more entries
])

// rewards[i] — PortEntryGetPortRewardsResultItem[] for the i-th entry
for (const entryRewards of rewards) {
  for (const { coinType, amountOwned } of entryRewards) {
    // coinType    — reward coin type address
    // amountOwned — claimable amount as bigint
  }
}

Get oSAIL types to claim

Returns a list of oSAIL coin types that are available to claim for a given vault position.

const poolId = '0x0...'
const portEntryId = '0x0...'

const { pool } = await fullSailSDK.Pool.getById(poolId)
const { port_entry, port } = await fullSailSDK.PortEntry.getById(portEntryId)
const currentEpochOSail = await fullSailSDK.Coin.getCurrentEpochOSail()

const oSailTypes = await fullSailSDK.PortEntry.getOSailTypesToClaim({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  poolId,
  gaugeId: pool.gauge_id,
  currentOSailCoinType: currentEpochOSail.address,
  portId: port.id,
  portEntryId: port_entry.id,
})

// oSailTypes — string[] of oSAIL coin type addresses available to claim

Get oSAIL types to claim list

Batch version of getOSailTypesToClaim — fetches claimable oSAIL coin types for multiple vault positions in a single RPC call.

const poolId = '0x0...'
const portEntryId = '0x0...'

const { pool } = await fullSailSDK.Pool.getById(poolId)
const { port_entry, port } = await fullSailSDK.PortEntry.getById(portEntryId)
const currentEpochOSail = await fullSailSDK.Coin.getCurrentEpochOSail()

const oSailTypesList = await fullSailSDK.PortEntry.getOSailTypesToClaimList([
  {
    coinTypeA: pool.token_a.address,
    coinTypeB: pool.token_b.address,
    poolId,
    gaugeId: pool.gauge_id,
    currentOSailCoinType: currentEpochOSail.address,
    portId: port.id,
    portEntryId: port_entry.id,
  },
  // ...more entries
])

// oSailTypesList[i] — string[] of oSAIL coin type addresses for the i-th entry

Get oSAIL rewards

Returns the claimable oSAIL amount for each available oSAIL coin type for a given vault position.

const poolId = '0x0...'
const portEntryId = '0x0...'

const { pool } = await fullSailSDK.Pool.getById(poolId)
const { port_entry, port } = await fullSailSDK.PortEntry.getById(portEntryId)
const currentEpochOSail = await fullSailSDK.Coin.getCurrentEpochOSail()

const oSailRewards = await fullSailSDK.PortEntry.getOSailRewards({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  poolId,
  gaugeId: pool.gauge_id,
  currentOSailCoinType: currentEpochOSail.address,
  portId: port.id,
  portEntryId: port_entry.id,
})

for (const { coinType, amount } of oSailRewards) {
  // coinType — oSAIL coin type address
  // amount   — claimable amount as bigint
}

Get oSAIL rewards list

Batch version of getOSailRewards — fetches claimable oSAIL amounts for multiple vault positions in a single RPC call.

const poolId = '0x0...'
const portEntryId = '0x0...'

const { pool } = await fullSailSDK.Pool.getById(poolId)
const { port_entry, port } = await fullSailSDK.PortEntry.getById(portEntryId)
const currentEpochOSail = await fullSailSDK.Coin.getCurrentEpochOSail()

const oSailRewardsList = await fullSailSDK.PortEntry.getOSailRewardsList([
  {
    coinTypeA: pool.token_a.address,
    coinTypeB: pool.token_b.address,
    poolId,
    gaugeId: pool.gauge_id,
    currentOSailCoinType: currentEpochOSail.address,
    portId: port.id,
    portEntryId: port_entry.id,
  },
  // ...more entries
])

// oSailRewardsList[i] — PortEntryGetOSailRewardsResultItem[] for the i-th entry
for (const entryRewards of oSailRewardsList) {
  for (const { coinType, amount } of entryRewards) {
    // coinType — oSAIL coin type address
    // amount   — claimable amount as bigint
  }
}

Create vault position

Creates a new vault position by depositing both coins into the vault.

const poolId = '0x0...'
const portId = '0x0...'

const amountA = 1000000n
const amountB = 1000000n

const { pool } = await fullSailSDK.Pool.getById(poolId)
const currentEpochOSail = await fullSailSDK.Coin.getCurrentEpochOSail()

const transaction = await fullSailSDK.PortEntry.createPortEntryTransaction({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  amountA,
  amountB,
  poolId,
  gaugeId: pool.gauge_id,
  portId,
  pythPriceIdA: pool.token_a.pyth_feed,
  aggregatorPriceIdA: pool.token_a.switchboard_aggregator,
  pythPriceIdB: pool.token_b.pyth_feed,
  aggregatorPriceIdB: pool.token_b.switchboard_aggregator,
  currentOSailCoinType: currentEpochOSail.address,
  rewardCoinTypes: pool.rewards?.map(({ token }) => token.address) ?? [],
})

Add liquidity to vault position

Adds liquidity to an existing vault position.

All rewards claimed automatically within this transaction.

import { MAX_LOCK_DURATION_DAYS, Percentage } from '@fullsailfinance/sdk'

const poolId = '0x0...'
const portEntryId = '0x0...'
// your existing permanent lock id
const permanentLockId = '0x0...'
// 'vesail' | 'sail' | 'usd'
const rewardChoice = 'vesail'

const amountA = 1000000n
const amountB = 1000000n

// 1%
const slippage = Percentage.fromNumber(1)

const { pool } = await fullSailSDK.Pool.getById(poolId)
const { port_entry, port } = await fullSailSDK.PortEntry.getById(portEntryId)
const currentEpochOSail = await fullSailSDK.Coin.getCurrentEpochOSail()
const sailCoin = await fullSailSDK.Coin.getByType(fullSailSDK.config.sailCoinType)
const oSailMap = await fullSailSDK.Coin.getOSailMap()

const oSailRewards = await fullSailSDK.PortEntry.getOSailRewards({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  poolId,
  gaugeId: pool.gauge_id,
  currentOSailCoinType: currentEpochOSail.address,
  portId: port.id,
  portEntryId: port_entry.id,
})

const transaction = await fullSailSDK.PortEntry.addLiquidityTransaction({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  amountA,
  amountB,
  poolId,
  gaugeId: pool.gauge_id,
  portId: port.id,
  portEntryId: port_entry.id,
  pythPriceIdA: pool.token_a.pyth_feed,
  aggregatorPriceIdA: pool.token_a.switchboard_aggregator,
  pythPriceIdB: pool.token_b.pyth_feed,
  aggregatorPriceIdB: pool.token_b.switchboard_aggregator,
  currentOSailCoinType: currentEpochOSail.address,
  portRewardCoinTypes: port.rewards.map(({ token }) => token.address),
  poolRewardCoinTypes: pool.rewards?.map(({ token }) => token.address) ?? [],
  oSailRewards: oSailRewards.map((reward) => ({
    ...reward,
    expired: oSailMap[reward.coinType]?.expired ?? true,
  })),
  rewardChoice,
  slippage,
  oSailDecimals: sailCoin.decimals,
  sailPrice: sailCoin.current_price,
  // if rewardChoice is 'vesail', you can provide your existing permanent lock id and oSAIL will be added to it
  // otherwise a new lock will be created every call
  permanentLockId,
  newLockDurationDays: MAX_LOCK_DURATION_DAYS,
  newLockIsPermanent: true,
})

Create vault position (zap-in)

Creates a new vault position by providing a single coin — part of it is swapped into the other coin automatically before depositing. Pass the result of calculateZapInDeposit as calculateZapInDepositResult.

The vault's current tick range is read from port.position (loaded via Pool.getById) and passed into calculateZapInDeposit.

import { Percentage } from '@fullsailfinance/sdk'

const poolId = '0x0...'
const portId = '0x0...'

const inputAmount = 1000000n
const fixedAmountA = true

// 1%
const slippage = Percentage.fromNumber(1)

const { pool, ports } = await fullSailSDK.Pool.getById(poolId)
const chainPool = await fullSailSDK.Pool.getByIdFromChain(poolId)
const currentEpochOSail = await fullSailSDK.Coin.getCurrentEpochOSail()

const port = ports.find(({ id }) => id === portId)

if (!port?.position) {
  throw new Error('Vault position not found')
}

const calculateZapInDepositResult = await fullSailSDK.Position.calculateZapInDeposit({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  coinDecimalsA: pool.token_a.decimals,
  coinDecimalsB: pool.token_b.decimals,
  tickLower: port.position.tick_lower,
  tickUpper: port.position.tick_upper,
  inputAmount,
  fixedAmountA,
  slippage,
  currentSqrtPrice: chainPool.currentSqrtPrice,
})

const transaction = await fullSailSDK.PortEntry.createZapInPortEntryTransaction({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  coinDecimalsA: pool.token_a.decimals,
  coinDecimalsB: pool.token_b.decimals,
  calculateZapInDepositResult,
  poolId,
  gaugeId: pool.gauge_id,
  portId,
  pythPriceIdA: pool.token_a.pyth_feed,
  aggregatorPriceIdA: pool.token_a.switchboard_aggregator,
  pythPriceIdB: pool.token_b.pyth_feed,
  aggregatorPriceIdB: pool.token_b.switchboard_aggregator,
  currentOSailCoinType: currentEpochOSail.address,
  rewardCoinTypes: pool.rewards?.map(({ token }) => token.address) ?? [],
})

Add liquidity to vault position (zap-in)

Adds liquidity to an existing vault position using a single coin — part of it is swapped into the other coin automatically. Pass the result of calculateZapInDeposit as calculateZapInDepositResult.

All rewards claimed automatically within this transaction.

import { MAX_LOCK_DURATION_DAYS, Percentage } from '@fullsailfinance/sdk'

const poolId = '0x0...'
const portEntryId = '0x0...'
// your existing permanent lock id
const permanentLockId = '0x0...'
// 'vesail' | 'sail' | 'usd'
const rewardChoice = 'vesail'

const inputAmount = 1000000n
const fixedAmountA = true

// 1%
const slippage = Percentage.fromNumber(1)

const { pool } = await fullSailSDK.Pool.getById(poolId)
const chainPool = await fullSailSDK.Pool.getByIdFromChain(poolId)
const { port_entry, port } = await fullSailSDK.PortEntry.getById(portEntryId)
const currentEpochOSail = await fullSailSDK.Coin.getCurrentEpochOSail()
const sailCoin = await fullSailSDK.Coin.getByType(fullSailSDK.config.sailCoinType)
const oSailMap = await fullSailSDK.Coin.getOSailMap()

if (!port.position) {
  throw new Error('Vault position not found')
}

const calculateZapInDepositResult = await fullSailSDK.Position.calculateZapInDeposit({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  coinDecimalsA: pool.token_a.decimals,
  coinDecimalsB: pool.token_b.decimals,
  tickLower: port.position.tick_lower,
  tickUpper: port.position.tick_upper,
  inputAmount,
  fixedAmountA,
  slippage,
  currentSqrtPrice: chainPool.currentSqrtPrice,
})

const oSailRewards = await fullSailSDK.PortEntry.getOSailRewards({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  poolId,
  gaugeId: pool.gauge_id,
  currentOSailCoinType: currentEpochOSail.address,
  portId: port.id,
  portEntryId: port_entry.id,
})

const transaction = await fullSailSDK.PortEntry.addZapInLiquidityTransaction({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  coinDecimalsA: pool.token_a.decimals,
  coinDecimalsB: pool.token_b.decimals,
  calculateZapInDepositResult,
  poolId,
  gaugeId: pool.gauge_id,
  portId: port.id,
  portEntryId: port_entry.id,
  pythPriceIdA: pool.token_a.pyth_feed,
  aggregatorPriceIdA: pool.token_a.switchboard_aggregator,
  pythPriceIdB: pool.token_b.pyth_feed,
  aggregatorPriceIdB: pool.token_b.switchboard_aggregator,
  currentOSailCoinType: currentEpochOSail.address,
  portRewardCoinTypes: port.rewards.map(({ token }) => token.address),
  poolRewardCoinTypes: pool.rewards?.map(({ token }) => token.address) ?? [],
  oSailRewards: oSailRewards.map((reward) => ({
    ...reward,
    expired: oSailMap[reward.coinType]?.expired ?? true,
  })),
  rewardChoice,
  slippage,
  oSailDecimals: sailCoin.decimals,
  sailPrice: sailCoin.current_price,
  // if rewardChoice is 'vesail', you can provide your existing permanent lock id and oSAIL will be added to it
  // otherwise a new lock will be created every call
  permanentLockId,
  newLockDurationDays: MAX_LOCK_DURATION_DAYS,
  newLockIsPermanent: true,
})

Remove liquidity from vault position

Removes liquidity from an existing vault position.

All rewards are claimed automatically within this transaction.

port_entry.ratio is the user's share of the vault position. Multiply the vault position's token amounts and liquidity by ratio to get the user's maximum withdrawable amounts, then scale by the desired withdrawal percentage (withdrawPercent must be greater than 0 and less than 100, not inclusive — to withdraw 100% use closeTransaction instead).

import Decimal from 'decimal.js'
import { MAX_LOCK_DURATION_DAYS, Percentage } from '@fullsailfinance/sdk'

const poolId = '0x0...'
const portEntryId = '0x0...'
// your existing permanent lock id
const permanentLockId = '0x0...'
// 'vesail' | 'sail' | 'usd'
const rewardChoice = 'vesail'
// percentage of the position to withdraw (1–100)
const withdrawPercent = 100

// 1%
const slippage = Percentage.fromNumber(1)

const { pool } = await fullSailSDK.Pool.getById(poolId)
const { port_entry, port } = await fullSailSDK.PortEntry.getById(portEntryId)
const chainPool = await fullSailSDK.Pool.getByIdFromChain(poolId)
const currentEpochOSail = await fullSailSDK.Coin.getCurrentEpochOSail()
const sailCoin = await fullSailSDK.Coin.getByType(fullSailSDK.config.sailCoinType)
const oSailMap = await fullSailSDK.Coin.getOSailMap()

const oSailRewards = await fullSailSDK.PortEntry.getOSailRewards({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  poolId,
  gaugeId: pool.gauge_id,
  currentOSailCoinType: currentEpochOSail.address,
  portId: port.id,
  portEntryId: port_entry.id,
})

if (!port.position) {
  throw new Error('Vault position not found')
}

const ratio = new Decimal(port_entry.ratio)
const amountA = BigInt(
  new Decimal(port.position.amount_token_a).mul(ratio).mul(withdrawPercent).div(100).floor().toString(),
)
const amountB = BigInt(
  new Decimal(port.position.amount_token_b).mul(ratio).mul(withdrawPercent).div(100).floor().toString(),
)
const liquidity = BigInt(
  new Decimal(port.position.liquidity).mul(ratio).mul(withdrawPercent).div(100).floor().toString(),
)

const transaction = await fullSailSDK.PortEntry.removeLiquidityTransaction({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  poolId,
  gaugeId: pool.gauge_id,
  portId: port.id,
  portEntryId: port_entry.id,
  volume: BigInt(port_entry.volume),
  pythPriceIdA: pool.token_a.pyth_feed,
  aggregatorPriceIdA: pool.token_a.switchboard_aggregator,
  pythPriceIdB: pool.token_b.pyth_feed,
  aggregatorPriceIdB: pool.token_b.switchboard_aggregator,
  currentOSailCoinType: currentEpochOSail.address,
  portRewardCoinTypes: port.rewards.map(({ token }) => token.address),
  poolRewardCoinTypes: pool.rewards?.map(({ token }) => token.address) ?? [],
  oSailRewards: oSailRewards.map((reward) => ({
    ...reward,
    expired: oSailMap[reward.coinType]?.expired ?? true,
  })),
  rewardChoice,
  slippage,
  oSailDecimals: sailCoin.decimals,
  sailPrice: sailCoin.current_price,
  tickLower: port.position.tick_lower,
  tickUpper: port.position.tick_upper,
  liquidity,
  currentSqrtPrice: chainPool.currentSqrtPrice,
  amountA,
  amountB,
  // if rewardChoice is 'vesail', you can provide your existing permanent lock id and oSAIL will be added to it
  // otherwise a new lock will be created every call
  permanentLockId,
  newLockDurationDays: MAX_LOCK_DURATION_DAYS,
  newLockIsPermanent: true,
})

Close vault position

Removes all liquidity and closes the vault position entirely. Use this instead of removeLiquidityTransaction when withdrawing 100%.

All rewards are claimed automatically within this transaction.

import Decimal from 'decimal.js'
import { MAX_LOCK_DURATION_DAYS, Percentage } from '@fullsailfinance/sdk'

const poolId = '0x0...'
const portEntryId = '0x0...'
// your existing permanent lock id
const permanentLockId = '0x0...'
// 'vesail' | 'sail' | 'usd'
const rewardChoice = 'vesail'

// 1%
const slippage = Percentage.fromNumber(1)

const { pool } = await fullSailSDK.Pool.getById(poolId)
const { port_entry, port } = await fullSailSDK.PortEntry.getById(portEntryId)
const chainPool = await fullSailSDK.Pool.getByIdFromChain(poolId)
const currentEpochOSail = await fullSailSDK.Coin.getCurrentEpochOSail()
const sailCoin = await fullSailSDK.Coin.getByType(fullSailSDK.config.sailCoinType)
const oSailMap = await fullSailSDK.Coin.getOSailMap()

const oSailRewards = await fullSailSDK.PortEntry.getOSailRewards({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  poolId,
  gaugeId: pool.gauge_id,
  currentOSailCoinType: currentEpochOSail.address,
  portId: port.id,
  portEntryId: port_entry.id,
})

if (!port.position) {
  throw new Error('Vault position not found')
}

const ratio = new Decimal(port_entry.ratio)
const amountA = BigInt(new Decimal(port.position.amount_token_a).mul(ratio).floor().toString())
const amountB = BigInt(new Decimal(port.position.amount_token_b).mul(ratio).floor().toString())
const liquidity = BigInt(new Decimal(port.position.liquidity).mul(ratio).floor().toString())

const transaction = await fullSailSDK.PortEntry.closeTransaction({
  coinTypeA: pool.token_a.address,
  coinTypeB: pool.token_b.address,
  poolId,
  gaugeId: pool.gauge_id,
  portId: port.id,
  portEntryId: port_entry.id,
  volume: BigInt(port_entry.volume),
  pythPriceIdA: pool.token_a.pyth_feed,
  aggregatorPriceIdA: pool.token_a.switchboard_aggregator,
  pythPriceIdB: pool.token_b.pyth_feed,
  aggregatorPriceIdB: pool.token_b.switchboard_aggregator,
  currentOSailCoinType: currentEpochOSail.address,
  portRewardCoinTypes: port.rewards.map(({ token }) => token.address),
  poolRewardCoinTypes: pool.rewards?.map(({ token }) => token.address) ?? [],
  oSailRewards: oSailRewards.map((reward) => ({
    ...reward,
    expired: oSailMap[reward.coinType]?.expired ?? true,
  })),
  rewardChoice,
  slippage,
  oSailDecimals: sailCoin.decimals,
  sailPrice: sailCoin.current_price,
  tickLower: port.position.tick_lower,
  tickUpper: port.position.tick_upper,
  liquidity,
  currentSqrtPrice: chainPool.currentSqrtPrice,
  amountA,
  amountB,
  // if rewardChoice is 'vesail', you can provide your existing permanent lock id and oSAIL will be added to it
  // otherwise a new lock will be created every call
  permanentLockId,
  newLockDurationDays: MAX_LOCK_DURATION_DAYS,
  newLockIsPermanent: true,
})

Claim all vault rewards

Claims all available rewards (oSAIL, pool rewards, and port rewards) for one or more vault positions in a single transaction.

import { MAX_LOCK_DURATION_DAYS, Percentage } from '@fullsailfinance/sdk'

const poolId = '0x0...'
const portEntryId = '0x0...'
// your existing permanent lock id
const permanentLockId = '0x0...'
// 'vesail' | 'sail' | 'usd'
const rewardChoice = 'vesail'

// 1%
const slippage = Percentage.fromNumber(1)

const { pool } = await fullSailSDK.Pool.getById(poolId)
const { port_entry, port } = await fullSailSDK.PortEntry.getById(portEntryId)
const currentEpochOSail = await fullSailSDK.Coin.getCurrentE