KyberSwap Docs
  • Introduction to KyberSwap
  • Getting Started
    • Quickstart
      • FAQ
    • Supported Exchanges And Networks
    • Foundational Topics
      • Decentralized Finance
        • Tokens
        • Stablecoins
        • DEX/DeFi Aggregator
        • Slippage
        • Price Impact
        • Zap
        • Maximal Extractable Value (MEV)
      • Decentralized Technologies
        • Wallets
        • Dapps
        • RPC
        • Oracles
        • On-Chain vs Off-Chain Data
      • Other Valuable Resources
  • KyberSwap Solutions
    • KyberSwap Interface
      • User Guides
        • Connect Your Wallet
        • Switching Networks
        • Instantly Swap At Superior Rates
        • Swap At Your Preferred Rates
        • Cross-chain Swap
        • Add Your Favourite Tokens
        • Get Crypto With Fiat
        • Bridge Your Assets Across Multiple Chains
      • Profiles
        • Profile Creation
        • Profile Customization
        • Sync Profile Across Devices
      • Notifications
        • Notification Center
    • KyberSwap Aggregator
      • Concepts
        • Dynamic Trade Routing
      • User Guides
        • Instantly Swap At Superior Rates
      • Developer Guides
        • Execute A Swap With The Aggregator API
        • Upgrading To APIv1
      • Aggregator API Specification
        • EVM Swaps
        • Permit
      • Contracts
        • Aggregator Contract Addresses
      • DEX IDs
      • Subgraphs
      • FAQ
    • KyberSwap Zap as a Service
      • KyberSwap Zap as a Service (ZaaS) API
        • ZaaS HTTP API
        • ZaaS GRPC API
      • KyberSwap Zap Liquidity Widget
      • Zap Fee Model
      • Zap's Supported Chains/Dexes
      • Zap's Deployed Contract Addresses
      • Zap's DEX IDs
    • KyberSwap Widget
      • Developer Guides
        • Integrating The KyberSwap Widget
        • Customizing The KyberSwap Widget
      • iFrame Alternative
      • Widget/iFrame Fee
    • KyberSwap Liquidity Widget
      • Integrating The KyberSwap Liquidity Widget
    • Limit Order
      • Concepts
        • Off-Chain Relay, On-Chain Settlement
        • Gasless Cancellation
      • User Guides
        • Swap At Your Preferred Rates
        • Update Limit Orders
        • Cancel Limit Orders
      • Developer Guides
        • Create Limit Order
        • Gasless Cancel
        • Hard Cancel
        • Fill Limit Order
      • Contracts
        • Limit Order Contract Addresses
      • Limit Order API Specification
        • General APIs
        • Maker APIs
        • Taker APIs
      • FAQ
    • KyberSwap OnChain Price Service
    • Fee Schedule
  • Governance
    • KyberDAO
      • User Guides
        • Participating in KyberDAO
        • Staking
        • Voting
        • Stake KNC And Enjoy Gas Savings
      • Fees to KyberDAO
      • KyberDAO Operator MultiSig
      • Contracts
        • KyberDAO Contract Repo
        • KyberDAO Contract Addresses
      • FAQ - Others
    • KNC Token
      • KNC Tokenomics & Utility
      • Gas Refund Program
      • KNC Contract Addresses
  • Security
    • Audits
  • Reference
    • Legacy
      • KyberSwap Classic
        • Concepts
          • Programmable Pricing Curves
          • Dynamic Auto-Adjusting Fees
          • Virtual Balances
          • Protocol Fees
        • Contracts
          • Classic Contract Repo
          • Classic Contract Addresses
          • Classic Contract Farming Addresses
      • KyberSwap Elastic
        • Concepts
          • Concentrated Liquidity
          • Reinvestment Curve
          • Tick-Range Mechanism
          • Pool Process Flows
          • Anti-Sniping Mechanism
          • Tick-Based Farming
          • Elastic Zap
          • TWAP Oracle
          • Elastic APR Calculations
        • Contracts
          • Elastic Contract Repo
          • Elastic Contract Addresses
          • Elastic Farming Contract Addresses
          • Elastic Zap Contract Addresses
          • Elastic Core Contracts
          • Elastic Core Libraries
          • Elastic Periphery Core Contracts
          • Elastic Peripheral Library Contracts
          • Elastic Peripheral Base Contracts
        • Subgraphs
      • Whitepapers
      • Audits
      • KyberAI
        • KyberScore
        • Concepts
        • On-Chain Indicators
          • Number Of Trades
          • Trading Volume
          • Netflow To Whale Wallets
          • Netflow To CEX
          • Number Of Transfers
          • Volume Of Transfers
          • Number Of Holders
          • Top Holders
        • Technical Indicators
          • Live Charts
          • Support & Resistance Levels
          • Live Trades
          • Funding Rate On CEX
          • Liquidations On CEX
        • Liquidity Analysis
      • Elastic Legacy
        • Elastic Legacy Contract Repo
        • Elastic Legacy Contract Addresses
        • Elastic Legacy Farming Contract Addresses
        • Remove Elastic Legacy Liquidity
      • Protocol
        • Overview
        • Smart Contract Architecture
        • Trust and Security Model
      • Integrations
        • Getting Started
        • Use Cases
        • Integration Types
        • Smart Contracts
        • Ethers JS
        • RESTful API
        • Slippage Rate Protection
        • Price Feed Security
        • Contract Events
        • Platform Fees
      • Reserves
        • Getting Started
          • Overview
          • Why Develop On Kyber
          • Create New Reserve
          • Existing Reserves
          • Customising Existing Reserves
        • Development Guides
          • Fed Price Reserve
          • Automated Price Reserve
          • Reserves with Ganache
          • Orderbook Reserve
        • Operations
          • Listing Policies
          • Reserve IDs
          • Reserve Rebates
          • Sanity Rates
      • Addresses
        • Introduction
        • Mainnet
        • Kovan
        • Rinkeby
        • Ropsten
      • API/ABI
        • Introduction
        • RESTful API
          • RESTful API Overview
          • RESTful API
        • Core Smart Contracts
          • IKyberNetworkProxy
          • KyberNetworkProxy
          • IKyberNetwork
          • ISimpleKyberProxy
          • IKyberMatchingEngine
          • KyberMatchingEngine
          • IKyberHint
          • KyberHintHandler
          • IKyberHintHandler
          • IKyberFeeHandler
          • IKyberStaking
          • KyberStaking
          • IKyberDao
          • KyberDao
          • IKyberStorage
          • KyberStorage
          • IKyberHistory
          • KyberHistory
          • IKyberReserve
          • KyberReserve
          • ConversionRates
          • LiquidityConversionRates
          • EpochUtils
          • IEpochUtils
          • KyberFeeHandler
        • Contract ABIs
          • ABIs
        • Code Snippets
          • Token Quantity Conversion
        • Misc Contracts
          • KyberNetwork
          • ConversionRatesInterface
          • PermissionGroups
          • SanityRates
          • Withdrawable
          • OrderbookReserveInterface
          • OrderbookReserveLister
    • KyberSwap Operator MultiSig
    • Permitable Tokens
    • Third-Party Integrations
    • KyberSwap Analytics
    • KyberSwap App
    • GitHub
    • KyberSwap Analytics
    • KyberSwap Blog
    • Kyber Network Press Kit
  • Socials
    • X
    • Discord
    • Telegram
    • LinkedIn
    • Reddit
    • Instagram
    • Tik Tok
  • Support
    • KyberSwap Help Center
    • Complaints Handling Process
Powered by GitBook
On this page
  • Introduction
  • Implementation overview
  • Technical explanation
  • Ticks concentrated liquidity - LinkedList​
  • Range mechanism​
  • Tick-range mechanism​

Was this helpful?

  1. Reference
  2. Legacy
  3. KyberSwap Elastic
  4. Concepts

Tick-Range Mechanism

Sorting And Tracking Liquidity Distribution

PreviousReinvestment CurveNextPool Process Flows

Last updated 1 year ago

Was this helpful?

Introduction

In order to enable LPs to specify liquidity positions with customized price intervals, the protocol needed a way to track the aggregated liquidity across various price points. Uniswap V3 achieved this feat by partitioning the space of possible prices into discrete "ticks" whereby LPs could contribute liquidity between any two ticks.

Whenever you add/remove liquidity to a tick range (including a lowerTick and upperTick), the pool has to record how much virtual liquidity is kicked in/out when crossing these ticks and how many tokens are used to activate the liquidity within the range.

Critically, by defining ticks in relation to the price, it enables neighbouring ticks to scale according to the absolute price (i.e. each tick will always be a fixed % price movement from their neighbouring tick). As a result of this design, LPs are able to add liquidity within the closest tick ranges that approximates their preferred liquidity provision price range.

Implementation overview

The series of diagrams below illustrates the tick-range mechanism for your convenience.

  1. Price space is divided into discrete ticks (refer below for exact formula)

  1. LPs add their positions to selected tick ranges as indicated in the table below. Note that the contributed liquidity is evenly spread across all the tick intervals within the selected tick range.

Position
Total Value
Tick Range
Tick Intervals
TVL Per Tick Interval

1

500

5

100

2

1200

4

300

3

500

5

100

  1. From the protocol's perspective, it is now able to track the total liquidity per tick interval as well as the proportional split between all positions within that interval.

Tick Interval
TVL
P1 Proportion
P2 Proportion
P3 Proportion

100

100 (100%)

-

-

100

100 (100%)

-

-

100

100 (100%)

-

-

400

100 (25%)

300 (75%)

-

500

100 (20%)

300 (60%)

100 (20%)

400

-

300 (75%)

100 (25%)

400

-

300 (75%)

100 (25%)

100

-

-

100 (100%)

100

-

-

100 (100%)

As long as the market continues to trade within a particular tick interval, the fees will be distributed to the LP according to the proportion of liquidity that the LP added to the active tick. As the market moves towards either extremes of the tick interval, the corresponding liquidity in the pool will be gradually depleted until only a single token is left in that interval. Upon crossing a tick, the protocol then activates the liquidity within the next interval and the process is repeated according to the direction of price movement.

The amplitude of the tick range can be controlled by the protocol by specifying a tick spacing which determines the potential active ticks. Based on the price correlation between assets in the pool, capital can be more efficiently utilized by setting a smaller tick interval (as more liquidity is concentrated into a smaller interval to support trades). The flipside of this is that additional gas is consumed whenever a tick is crossed as the liquidity within the activated tick has to be activated. Due to these factors, pools with various fee tiers have been created to cater to the different liquidity preferences of LPs.

Technical explanation

A tick is approximated as the square root price that is an integer power of 1.0001\sqrt{1.0001}1.0001​:

p(i)=1.0001i\sqrt{p(i)} = 1.0001^ip(i)​=1.0001i

Through the above equation, it allowed each tick to be defined as a ~0.5 basis point price movement from its neighbouring ticks. By specifying the tick spacing, the protocol can then determine the potential active ticks for each pool. For example, a tick spacing of 2 would result in each neighbouring tick being within ~1 basis point of the active tick.

KyberSwap implements this tick range mechanism via a linked list data structure. As mentioned above, we need to store information about each tick to track the amount of net liquidity that should be added and removed when the tick is crossed. As a result, the tick's variables must contain the net liquidity of liquidity providers and the fee outside of the tick.

All these information is stored in the TickData struct

struct TickData
    uint128 liquidityGross;
    int128 liquidityNet;
    uint256 feeGrowthOutside;
    uint128 secondsPerLiquidityOutside;
 

Indeed, when swapping, the total amount of liquidity that is added or removed when the tick is crossed goes left and right. So we need to track the liquidityNet, which is the total amount of liquidity that is added and removed when the tick is crossed. This value is one signed integer: the amount of liquidity added when price moves rightward, and if we're moving leftward, we interpret liquidityNet as the opposite sign.

Between several ticks, there remains a possibility that we will not have any liquidity. As a result, to optimize, we only initialize a tick if and only if it has liquidity referencing the tick. The liquidityGross ensures that if net liquidity at a tick is 0, we can still know if a tick is referenced by at least one underlying position, which tells us whenever to initialize the tick.

We use feeGrowthOutside to track how much fees were accumulated within a given range. feeGrowthOutside needs to be updated each time the tick is crossed.

There could be several ticks that are not initialized and we only access/process data of initalized ticks, we need a data structure to store all initialized ticks with the ability to move from one initialized tick to its 2 adjacent initialized ticks optimally. To do so, we use a doubly linked list to store all initialized ticks.

The doubly linked list of initialized ticks is stored in the initializedTicks from the PoolStorage, each Linkedlist data consists of 2 variables:

struct Data
    int24 previous; // the previous data in the doubly linked list
    int24 next;     // the next data in the doubly linked list
Field
Type
Explanation

previous

int24

the previous data in the doubly linked list

next

int24

the next data in the doubly linked list

Initiallly, its HEAD is MIN_TICK and its TAIL is MAX_TICK, they will never be removed from the initializedTicks.

When adding a new tick X, users will need to specify the tick Y that is initialized. To save on gas, the tick Y will have to be the tick that is closest and lower than the added tick X. However, due to on-chain delay/human error, there are few scenarios that could happen:

  • X has been initialized, ignore this case.

  • Y has been removed, the transaction will be reverted.

  • There are several ticks have been initialized/added between X and Y, to avoid reverting, we allow to jump at most MAX_TICK_TRAVEL from Y to the right to find the nearest initialized tick that is lower than X in the current linked list, i.e: Y < X < Y.next.

Logic

if Y is removed -> revert
While (Y.next <= X && iteration <= MAX_TICK_TRAVEL):
    Y = Y.next
    iteration++;
if Y >= X or Y.next <= X -> revert - invalid lower value

// Add new tick X between Y and Y.next
X.next = Y.next
X.previous = Y
Y.next.previous = X
Y.next = X

When removing a tick X, there are 3 cases:

  • X is MIN_TICK or MAX_TICK, ignore this case.

  • X has been removed, the transaction will be reverted.

  • Otherwise, remove X by linking its 2 adjacent ticks to each other, and return the previous tick.

Logic:

if X == X.previous -> return X; // X is the lowest tick
if X == X.next -> return X.previous; // X is the highest tick
// Remove tick X by linking its 2 adjacent ticks
X.previous.next = X.next;
X.next.previous = X.previous;
delete X

The goal is to accurately account a position's accured fees accured and duration for which it is active. A position is defined to be active if the current pool tick lies within the lower and upper ticks of the position.

The feeGrowthGlobal and secondsPerLiquidityGlobal variables represent the total amount for 1 unit of unbounded liquidity.

The outside value (feeGrowthOutside and secondsPerLiquidityOutside) for a tick represents the accumulated value relative to the currentTick. By definition, we can visually represent it to be as such:

  • tick <= currentTick

  • tick > currentTick

When a tick is initialized, all growth is assumed to happen below it. Hence, the outside value is initialized to the following values under these cases:

  • tick <= currentTick: outside value := global value

  • tick > currentTick: outside value := 0

Due to the definition of the outside value, a tick's outside value is reversed whenever the pool tick crosses it. Specifically, outside value := global value - outside value.

When swapping downtick, the current tick is further decremented by 1. This is to ensure a tick's outside value is interpreted correctly. swapData.currentTick = willUpTick ? tempNextTick : tempNextTick - 1;

The current tick can be

  1. below a position (currentTick < lowerTick) The value inside can therefore be calculated as tickLower's outside value - tickUpper's outside value

  2. within a position (lowerTick <= currentTick < upperTick)

    The value inside can therefore be calculated as global value - (lower + upper tick's outside values)

  3. above a position (upperTick <= currentTick)

    The value inside can therefore be calculated as tickUpper's outside value - tickLower's outside value

  • To represent liquidity mapping, Kyberswap Elastic uses linked list, the linked list always starts by MIN_TICK and ends by MAX_TICK.

  • The pool also need to track to the highest initialized tick which is lower than or equal to currentTick (aka nearestCurrentTick).

For example:

  • the pool is initialized at tick 5, the linked list will be [MIN_TICK, MAX_TICK] andnearestCurrentTick will be MIN_TICK.

  • A adds liquidity at tick [-5, 10], the linked list will be [MIN_TICK, -5, 10, MAX_TICK] and nearestCurrentTick will be -5.

  • C adds liquidity at tick [0, 100], the linked list will be [MIN_TICK, -5, 0, 10, 100, MAX_TICK] and nearestCurrentTick will be 0.

  • B swaps and currentTick = 15, then nearestCurrentTick will be 10

  • A remove all liquidity at tick [-5, 10], the linked list will be [MIN_TICK, 0, 100, MAX_TICK] andnearestCurrentTick will be 0.

Ticks concentrated liquidity - LinkedList

Add new tick to the linked list

Remove a tick from the linked list

Range mechanism

Objective

Global value

Outside value

Initialization

Crossing ticks

Note

Calculating value inside ticks

Tick-range mechanism

T1−T6T_1 - T_6T1​−T6​
T4−T8T_4 - T_8T4​−T8​
T5−T10T_5 - T_{10}T5​−T10​
T1−T2T_1 - T_2T1​−T2​
T2−T3T_2 - T_3T2​−T3​
T3−T4T_3 - T_4T3​−T4​
T4−T5T_4 - T_5T4​−T5​
T5−T6T_5 - T_6T5​−T6​
T6−T7T_6 - T_7T6​−T7​
T7−T8T_7 - T_8T7​−T8​
T8−T9T_8 - T_9T8​−T9​
T9−T10T_9 - T_{10}T9​−T10​
​
​
​
​
​
​
​
​
​
​
​
​
Technical explanation
Discrete ticks
3 separate liquidity positions
Tick TVL with each position shown