Documentation Index
Fetch the complete documentation index at: https://tech.ramses.xyz/llms.txt
Use this file to discover all available pages before exploring further.
Introduction
This guide will cover how single-side liquidity provisioning can be used to execute Limit Orders on Ramses V3 Pools. An example to showcase this concept can be found in the Range Order example, in the Uniswap code examples repository. To run this example, check out the guide’s README and follow the setup instructions.This guide builds on top of the Pooling Liquidity guides.
We recommend going through this section of the docs before imnplementing Range Orders.
- Understanding Range Orders
- Calculating our Tick Range
- Creating a single-side liquidity position
- Observing the price of the Pool
- Closing the Limit Order
range-order.ts.
Understanding Range Orders
If you have read the Range Order Concept page, you can skip this section. Positions on a V3 Pool are always created with a Tick range in which their liquidity is accessible to swaps on the Pool. Lets look at the return value of the RamsesV3PositionManager contract when calling thepositions function with a Position tokenId.
liquidity value, and a tickLower and tickUpper value that define the range in which the liquidity of the Position can be utilised for Swaps.
The actual amount of token0 and token1 that a Pool owes the Position owner is calculated from the parts of the liquidity position that are to the left and right of the current Tick.
Liquidity left of the current Tick is denominated in token0 and liquidity right of the current Tick is denominated in token1.
If a new Position is created and the Tick Range of the position does not include the current Tick of the Pool, only one of the two Tokens in the Pool can be provided.
We will call this a Single Side Liquidity Position.

token0 and token1 will change, and ultimately inverse if the current Tick moves out of the position on the other side.
We will utilise this behaviour to provide liquidity with token1 and withdraw the position when it has been converted to token0.
Calculating the Tick Range
Our goal for this guide is to create a Take Profit Order that tradestoken0 for token1 when the Price of token0 increases by 5%.
To create our Position, we need to first decide the Tick Range that we want to provide liquidity in.
Upper Tick
We create a Pool that represents the V3 Pool we are interacting with and get thetoken0Price.
We won’t need full tick data in this example.
Price by 5%. We create a new Price with a numerator 5% higher than our current Price:
numerator and denominator parameters are ordered differently in the Fraction and Price constructor.
We have calculated our target Price but we still need to find the nearest usable tick to create our Position.
As Positions can only start and end at initializable Ticks of the Pool, so we can only create a Range Order to a Price that exactly matches an initializable Tick.
priceToClosestTick function to find the closest tick to our targetPrice.
We then use the nearestUsableTick function to find the closest initializable Tick for the tickSpacing of the Pool.
tickSpacing if the initializable Tick is lower or higher than the theoretically closest Tick.
Lower Tick
We now find the lower Tick by subtracting the tickSpacing from the upper Tick:Creating the Single Side Liquidity Position
We will use theRamsesV3PositionManager and Position classes from the v3-sdk to construct our position. We then use an etherJS wallet to mint our Position onchain.
If you are not familiar with liquidity Positions, check out the liquidity position guides.
Minting the Position
We create aPosition object with our ticks and the amount of tokens we want to deposit:
RamsesV3PositionManager Contract an approval to transfer our tokens.
We can find the Contract address from the Ramses contract addresses or the Uniswap GitHub for Ethereum mainnet forks.
For local development, the contract address is the same as the network we are forking from.
So if we are using a local fork of mainnet like described in the Local development guide, the contract address would be the same as on mainnet.
RamsesV3PositionManager:
range-order.ts.
Getting the tokenId
We want to read the response to ourMint function call to get the position id.
We will need the positionId to fetch the Position Info from the NFTPositionManager contract.
We wait for the transaction receipt and fetch the result using trace_transaction:
tokenId:
ethers.Bignumber so we need to cast it to a string to use it with the SDK.
We have created our Range Order Position, now we need to monitor it.
In the code example we use wallet.call to get the position id.
call and trace_call both simulate a transaction on the connected node and return the expected output, trace_call gives us a much more detailed output though.
Depending on the use case, either can be the better choice.
In a production environment you would prefer to wait for the transactionReceipt like described earlier to ensure the transaction was actaully included in the blockchain.
Observing the Price
We need to observe the price of the Pool and withdraw our Position once thetickCurrent has moved across our Position.
We use ethers JS to watch for new blocks and fetch the latest Pool data:
Closing the Limit Order
We call the RamsesV3PositionManager Contract with thetokenId to get all info of our position as we may have gotten fees from trades on the Pool:
RamsesV3PositionManager, the pool, positionInfo and tokenId to create call parameter for a decreaseLiquidity call.
We start with creating CollectOptions:
RemoveLiquidityOptions. We remove all our liquidity so we set liquidityPercentage to 1:
Position object from the updated positionInfo info we fetched:
token1 at the Price we have specified.
We have successfully executed a range order.
Caveats
Executing a range order has certain limitations that may have become obvious during the course of this guide.- If the price of the Pool drops below
tickUpperwhile we already decided to withdraw our liquidity our order may fail and we either receivetoken0,token0andtoken1or our transaction fails depending on our exact implementation. - Range Orders can only be created between initializable ticks and may not exactly represent our limit order Price-Target.
- Depending on the price ratio of the tokens in the Pool the minimum price difference to the current price may be significant.
- The tokens received are the average between the Price of
tickUpperandtickLowerof the Range order. This can be a significant difference for Pools with a tickCurrent far from 0, for example tokens with different decimals (WETH/ USDT, WETH/USDC). The example showcases this behaviour well with the default configuration.
Next Steps
This guide showcases everything you need to implement Range Orders on your own, but only demonstrates creating a Take Profit order intoken0 to token1 direction.
Consider implementing Buy Limit orders as described in the Range Orders concept page.
This is currently the last guide in the v3-sdk series. Consider joining the Ramses Discord or checkout the official GitHub to learn more about Ramses.