What is TONCO
Toncoin is now Gram. Following the community vote concluded on June 8, 2026, the native token of The Open Network is renamed from Toncoin (TON) to Gram (GRAM) as of June 15, 2026. During the transition period the asset is displayed as "Gram (prev. Toncoin)".
This is a display name, ticker, and logo change only: the TON blockchain keeps its name, balances are unchanged (10 TON = 10 GRAM), and all addresses, smart contracts, and trading pairs remain valid. No action is required from users. There is no migration, swap, bridge, or claim process — any platform asking you to "claim GRAM" or "convert TON to GRAM" is a scam.
TONCO is a decentralized exchange (DEX) on TON Blockchain that introduces concentrated liquidity, enabling liquidity providers (LPs) to maximize capital efficiency and offer traders better swaps with lower slippage.
Key highlights:
- First CLAMM DEX on TON. Capital is used efficiently: higher yields for LPs, lower slippage for traders.
- Accessible to All Levels. Advanced strategies for experienced users and an intuitive interface for beginners.
- Developed by Algebra Team. Built by seasoned infrastructure experts with a strong reputation across EVM chains.
Bringing Concentrated Liquidity to TON
AMM Limitations on TON
Decentralized exchanges (DEXs) on TON have adopted some of the same principles that revolutionized DeFi on Ethereum, particularly the Automated Market Maker (AMM) v2 model. While this model, first popularized by Uniswap, was groundbreaking in its time, allowing users to trade tokens without needing an order book or centralized authority, it now shows its age.
The XYK model (X * Y = K), which balances liquidity pools by ensuring the value of one token remains equal to the other regardless of price, worked well for early DeFi. It was a simple, effective system that helped bootstrap liquidity in the early days of decentralized finance. But today, this model struggles to keep up with evolving needs in liquidity provision and trading efficiency.
Current AMM models fall short of unlocking the full potential of TON Blockchain. These models suffer from inefficient capital usage, higher slippage, and lower yields for liquidity providers, all of which hinder the growth of DeFi on TON. To truly harness the power of TON, the outdated AMM v2 model needs to evolve.
The New Liquidity Pooling Approach
This is where V3 technology comes into play with the introduction of Concentrated Liquidity, and TONCO is at the forefront of bringing this innovation to TON blockchain. But TONCO isn’t just copying existing solutions from other networks—it's enhancing and adapting them specifically for the unique needs of DeFi on TON.
Unlike the traditional XYK model, TONCO allows liquidity providers (LPs) to allocate liquidity within specific price ranges—a concept known as concentrated liquidity position. This approach enables LPs to open multiple positions within a single pool, each customized to their desired price ranges and risk preferences. The result is deeper, more efficient liquidity.
LPs can choose from a list of preset price ranges or manually define their own by selecting minimum and maximum price points.
When the market price falls within an LP’s selected range, their liquidity is used for trades, earning them a share of the trading fees relative to their contribution within that range. As prices fluctuate, different LPs’ liquidity is utilized, optimizing capital usage and ensuring liquidity is concentrated where it’s needed most.
Learn more about Concentrated Liquidity: https://blog.tonco.io/en/posts/understanding-clamm-new-liquidity-model-ton
\
Team
TONCO is developed by the experienced Algebra team, renowned for building secure, high-performance DeFi infrastructure across over 30 DEXs on EVM-compatible chains, including platforms like Camelot and QuickSwap.
With over $75 billion in cumulative trading volume and rigorous security audits from top firms, Algebra brings a strong foundation of trust and innovation.
By partnering with prominent liquidity managers and DeFi aggregators, Algebra ensures that TONCO’s DEX offers reliability, efficiency, and advanced features that set new standards for DeFi on TON Blockchain.
Learn more about TONCO team: https://blog.tonco.io/en/posts/whos-behind-tonco-dex-why-you-can-trust-us

Social Media
Stay Connected with TONCO
💎 Join the community on Telegram: t.me/tonco_welcome
💻 Learn more on our website: https://tonco.io/
🫛 Experience concentrated liquidity in our web app: https://app.tonco.io/
🐤 Stay up to date by following us on Twitter: x.com/Tonco_io
Audits
TONCO has been audited by Beosin: https://tonco.io/static/audits/beosin.pdf
https://tonco.io/static/audits/beosin.pdf
Roadmap

Roadmap Overview
Q4 2024-Q1 2025
• Telegram Mini App: Launch of the DEX integrated within a Telegram app for easy access
• Migrator Tool: A tool to help users migrate liquidity from other TON DEXes to TONCO
• Multi-hop Routing: If there is no pool for your swap, a route will be built through several hops
• Exact Out Function: Allows users to specify the exact amount of output tokens they want to receive
• Limit Orders: Enable traders to set buy orders at specific price points
• Zaps: One-click transactions to open any LP position with only one token
Q2 2025
• Advanced Presets for LPs: New ready-options when creating LP position
• APR Calculator: Tool to estimate potential returns on liquidity positions
• Cross-chain Swaps: Swaps across different blockchains to onboard new users to TON
• Dynamic Fees: Adjustable trading fees based on market conditions. More info
More to come...
Brand Kit
TONCO logo set: https://drive.google.com/drive/folders/1UZiqsAbMBIAmiGte2whO1TOPbNrMplwq?usp=sharing
Glossary
Glossary
AMM (Automated Market Maker): A decentralized protocol used on DeFi platforms to automate liquidity provision for trading pairs. AMMs remove the need for traditional market makers by using smart contracts to manage liquidity.
Capital Efficiency: A measure of how effectively capital is used to generate returns. Greater capital efficiency means a trader is maximizing the potential return on their invested capital.
Slippage: The difference between the expected trade price and the price at which the trade actually executes. Slippage is common in fast-moving markets or when liquidity is low.
Concentrated liquidity: Liquidity that is focused within a specific price range, rather than being spread across the entire market. This approach allows for greater liquidity depth where it’s most needed, leading to higher trading volumes and narrower spreads.
Custom Liquidity Ranges: Specific price intervals selected by liquidity providers to focus their capital. These ranges are set according to market conditions and ensure liquidity is available in targeted areas for trading.
Impermanent Loss: A temporary loss that occurs when the price of assets in a liquidity pool changes after a liquidity provider deposits their tokens. This loss happens when the token price deviates from its original value in the pool, impacting the LP’s returns.
NFT LP (Non-fungible Liquidity): A unique contract that represents a liquidity provider’s position, including the associated data and fees. In TONCO NFT LPs replace traditional LP tokens.
Price Impact: Unfavorable change in price. If you are swapping in a pool with very low liquidity, you may receive a very poor price for your swap.
Price Range: The defined range within which a liquidity provider or trader operates. It represents the price window where liquidity is concentrated and trades are executed.
Dynamic Fees: A fee structure that adjusts based on the liquidity and market conditions within a specific pool. This flexible approach incentivizes liquidity providers to contribute when liquidity is low.
Trading Tick: The smallest possible price increment (up or down) for an asset. It defines the granularity of price changes within an AMM system.
Tick Spacing: Tick spacing sets the intervals between price points (or “ticks”) in a liquidity pool. For example, with a tick spacing of 8, each tick is 0.08% apart from the next price level, while a spacing of 128 means each tick is 1.28% apart.
\
Fees
Swap Fees
Swap fees are distributed pro-rata to all active liquidity positions at the time of the swap. If the spot price moves out of a position’s range, that liquidity becomes inactive and stops generating fees. When the spot price reenters the position’s range, the liquidity becomes active again and generates fees.
Swap fees are not automatically reinvested. Instead, they are collected separately from the pool and must be manually redeemed by the owner of LP NFT to collect their fees.
Custom Fees
TONCO allows pool creators to set the pool fee with three options available during pool creation:
- 0.05% fee with tick spacing of 10
Best for stable pairs, as the price risk for liquidity providers holding these assets is very low - 0.3% fee with tick spacing of 60
The most balanced option for the majority of pairs - 1% fee with tick spacing of 200
Can be used only if you have a highly volatile token (during updates or a launch) and you expect significant trading activity where traders won’t be concerned about the fee
Tick spacing sets the intervals between price points (or “ticks”) in a liquidity pool. For example, with a tick spacing of 60, each tick is 0.6% apart from the next price level.
 (1) (1).png)
TONCO retains 20% of fees generated, with the remaining 80% going to liquidity providers.
If you need to set a different fee, TONCO team can create a pool with any fee or change the fee in your created pool. To do so, please contact us at TONCO Support.
The tick to price formula
On TONCO, liquidity positions are represented as NFTs, and each NFT contains metadata specifying the price range selected by the liquidity provider:
.png)
LP NFT on TONCO DEX
📌 Example:
Take a look at this position: View on TonViewer
Its tick range: [-62160 → -56100]
We use the following formula to convert ticks to price:

Tick to price formula on TONCO DEX
Where:
• tick = tick value from the NFT
• decimals_S0 = decimal places of token 0 (base asset)
• decimals_S1 = decimal places of token 1 (quote asset)
Finding Token Decimals
You need to check the decimal places of Token 0 and Token 1.
For example, in the GRAM/USDT pair:
• GRAM has 9 decimals
• USDT has 6 decimals
.png)
USDT jetton metadata
Applying the Formula
Let’s calculate the prices for tick -62160 and -56100:
.png)
Tick -62160
Price = 1.9978 USDT per GRAM
.png)
Tick -56100
Price = 3.6621 USDT per GRAM
When you look at your position in the TONCO UI, the selected range appears like this:

GRAM/USDT pool on TONCO DEX
The dynamic fee system is expected to launch in 2025
TONCO brings a dynamic fee structure that automatically adjusts in response to market volatility, significantly improving efficiency. The platform dynamically modifies fees for each liquidity pool based on the current level of volatility, enhancing both fee generation and trading activity.
There will be the ability to set different volatility-based fee ranges for both buying and selling. This allows each liquidity pool to implement a customized fee structure that aligns with specific market conditions. As a result, fees become more reflective of the market’s risk profile, leading to increased trading volume and higher fee generation.
NFT LP Tokens
Once you provide liquidity on TONCO, you’ll receive an NFT LP instead of traditional LP tokens.
Because a liquidity position can be opened at any price range, including both in range and out of range positions, each position has a unique ID and is represented by a unique NFT.
This NFT acts as a smart contract that contains all the data and accumulated fees associated with your liquidity position. There’s no need to stake this NFT in order to participate in V3 Farms.
As the owner of the NFT, you have full control over your position, allowing you to modify or burn (remove liquidity) it at any time. These NFTs are fully tradable, and any earned fees can be claimed by the holder whenever they choose.
An NFT contains key details about your LP position, including the price range, pool address, and token addresses associated with the pool. If an NFT is lost, or sold, the liquidity it represents will also be lost!

NFTs for position at USDT/GRAM pool on TONCO: https://getgems.io/collection/EQD25vStEwc-h1QT1qlsYPQwqU5IiOhox5II0C_xsDNpMVo7
You can view your NFT in any blockchain explorer, marketplace, wallet or directly within the TONCO platform. The NFT description provides key details about your liquidity position, including the pair symbols, and the minimum and maximum price ranges set by the liquidity provider.\
Farming
LP positions minted in the pool automatically participate in farming (if the farming is available for this pool). On the Pools page, use the “Active Farming” toggle to highlight pools involved in farming, or view them directly under the Farms tab.
 (1) (1).png)
Farming rewards can be claimed on either the Pools or Farms pages, with eligible positions shown in both locations. To collect rewards, simply click Approve to create a contract, then Claim to receive rewards. Both actions are required for successful claim completion.
Farming on TONCO acts as a boost to the current pool. If liquidity is out of the selected range and not used, no farming rewards are earned. Additionally, farming rewards are not distributed if there is no trading volume.
Farming Rewards Calculation on TONCO
 (1).png)
 (1) (1) (1) (1) (1) (1).png)
Definitions:
• farming.rewardRate: The amount of reward token distributed per unit time interval
• INTERVAL: The duration of the interval
• rewardToken.derivedTON: The value of one reward token in terms of GRAM token
• feeCollectedTotalTON: The total fees collected in the pool during the interval (in GRAM token)
Example:
• farming.rewardRate: $500/day in USDT
• INTERVAL: 1 day
• rewardToken.derivedTON: 1 GRAM = 6 USDT
• TVL of the pool: $1,000,000
• Pool Trading Volume: $500,000/day
• Pool Commission Percentage: 0.3%
• Fee Collected: $1,500
In this case, the farming multiplier is 1.33, meaning liquidity providers will receive additional 0.33 GRAM for each GRAM of fees collected, provided liquidity is within the active price range.
Farming FAQ
How to participate in farming on TONCO?
Minted LP positions automatically participate in farming if it is available for that pool.
No additional action is required.
What is a farming "Boost"?
Farming on TONCO acts as a boost to LP fees in the pool. The farming multiplier means liquidity providers will receive an additional percentage of GRAM for each GRAM of fees collected, provided liquidity is within the active price range. See example
How is the farming boost calculated?
The farming boost is calculated based on the farming multiplier:
Definitions:
• farming.rewardRate: The amount of reward token distributed per unit time interval
• INTERVAL: The duration of the interval
• rewardToken.derivedTON: The value of one reward token in terms of GRAM token
• feeCollectedTotalTON: The total fees collected in the pool during the interval (in GRAM token)
Do farming rewards depend on pool fees collected?
Yes, farming rewards are not distributed if there is no trading volume.
What happens if my liquidity position goes out of range?
On TONCO, only active (in-range) liquidity positions will earn farming rewards. If the price moves out of the range, the position stops receiving rewards.
Once the price moves back in range, the position resumes earning rewards automatically, with no additional actions required.
What if I have multiple LP positions?
Each of your liquidity positions will participate in farming individually.
You will need to claim rewards separately for each position. There is no maximum number of LP positions that can participate in farming.
How to get farming rewards?
Farming rewards can be claimed on either the Pools or Farms pages. Eligible positions will be shown in both locations.
If you have multiple LP positions, you will need to claim rewards separately for each position. Remember, gas fees apply for each claim, so always factor in gas costs when performing yield operations.
What is the claim fee?
For each claim, you will need to pay network fee, which is approximately 0.15 GRAM (around $1).
How often should I claim my rewards?
While you can claim rewards whenever you’d like, it’s important to note that each claim incurs a small blockchain fee.
We recommend waiting until the end of the farming period to claim all your rewards at once in the “Ended” tab to minimize transaction costs.
Does price range affect farming rewards?
Yes, the price range significantly affects farming rewards. The narrower the range, the higher the concentration of liquidity, which boosts the relative share of the total active liquidity within the pool.
A smaller price range means your liquidity is used more efficiently, increasing your share of the pool and earning more farming rewards. In contrast, a wider range results in a lower concentration and fewer rewards.
When do the first rewards to claim appear?
Farming updates in TONCO happen every 3 hours. Once a position is added to farming, rewards will be available after the first trading fees are collected.
Meaning of Ranges
On TON Blockchain liquidity providers can choose between two main options for deploying their liquidity: V2 pools (DeDust/STONfi) or V3 concentrated liquidity pools (TONCO DEX)
V2 Pools
In V2 pools, assets are deposited in a ‘full range’ from 0 to ∞, covering every price point where they can be traded.
• Set and Forget: Once you deposit, it’s on autopilot
• Constant Asset Ratio: Assets always maintain a 50:50 ratio
• Uniform APR: All participants earn the same APR
V3 ‘Concentrated Liquidity’ Pools
TONCO DEX allows users to concentrate liquidity in specific price ranges.
Price Range represents the price window where liquidity is concentrated and trades are executed.
• Customizable Price Ranges: Liquidity can be provided within specific price bands
&#xNAN;• Dynamic Asset Ratios: The asset ratio changes with price movements
&#xNAN;• Active Management: Positions generate fees only if they cover the current price, requiring active position management
&#xNAN;• Variable APR: The narrower the range, the higher the share of generated fees, but also the higher the susceptibility to impermanent loss
How does it work?
 (1) (1) (1) (1) (1).png)
Let’s say you want to provide liquidity to GRAM/USDT pool. The current GRAM price is $6.87, and you LP in a $6.17–$7.65 range 50:50 (6.87 USDT + 1 TON). The deposited liquidity will be used only within this range and you will gather fees only within this range. When it moves out of the range, the position remains open but inactive, earning no fees unless the price gets back to the range.
When the price moves within the range, the ratio in which you own the tokens changes. You started 50:50, but it changes until it’s 100:0 at the edges of the range.
Back to the example. The price of GRAM drops from $6.87 to $6.17, changing the token ratio from 50:50 to 100:0 and having a bit over 2 GRAM and 0 USDT in the LP position (as the ratio changes along the way, you end up with slightly more than 2x GRAM). If the price continues falling and you don’t adjust the position, you are fully exposed to the GRAM price and experience permanent loss.
The same applies when the price rises but in the opposite direction. If the GRAM price increases to $7.65, you are fully in USDT and have apx. $15 USDT in the LP position (as the ratio changes along the way, you end up with less than 2x USDT). But if the price continues to rise to $8, you get no additional gains because you are only in USDT.
Because of this, it’s important to constantly watch the prices and actively manage your position.
Pros of Ranges
- Less slippage
By providing liquidity in specific ranges, LPing becomes more capital efficient as the liquidity is concentrated in narrower ranges compared to the infinite spectrum in V2. This results in less slippage when trading. - Increased Returns
A narrower range means more assets available for trades and less competition within that range, translating into collecting more fees and potentially higher returns. This is why APR is higher than in V2 DEXes on TON.
 (1) (1) (1) (1).png)
- Flexible Risk Management
LPs can manage their risk more efficiently by setting a range that aligns with their risk tolerance and market view.
Range Presets
 (1).png)
Presets Available on TONCO
Liquidity providers allocate their crypto to a specific price range. They set the range between the highest and lowest prices where their liquidity will be used.
TONCO provides presets for selecting a range: Narrow, Common, Wide, and Full. The Full range simulates V2 behavior, allowing LPs to cover the entire price spectrum.
Preset Ranges
- Narrow Range: -5% to +10% of the current price.
- Common Range: -10% to +20% of the current price.
- Wide Range: -20% to +40% of the current price.
TONCO DEX ranges: Narrow vs. Wide

Narrow Ranges
- High Fee Generation: Generate more fees due to concentrated liquidity.
- Constant Monitoring: Require frequent rebalancing to stay within the active range.
- High Impermanent Loss: Heavily exposed to rapid price movements and IL.
Wide Ranges
- Lower Fee Generation: Generate fewer fees as liquidity is spread out.
- Less Frequent Rebalancing: Require less manual intervention.
- Reduced Impermanent Loss: Less affected by price movements.
What price range to choose?
Volatile Pairs
The volatile category applies to most pairs, where a a project token paired with GRAM or USDT.
If you’re concentrating liquidity on a volatile pair (like GRAM/USDT), you run the risk of your position falling out of range. If your position falls out of range, you’ll need to rebalance. Rebalancing costs you in swap fees, slippage and gas.
For a concentrated position on a low volume pair, it’s very unlikely that the gains from trading fees will exceed the loss from impermanent loss.
For these two key reasons, it’s likely that setting a wide range is the dominant option for low and mid volume volatile pairs.
On higher volume pairs (like GRAM/USDT) you can consider narrower ranges to maximize profit. By “high volume” we mean the point at which the LP fees exceeds the costs of impermanent loss and rebalancing.
Pegged Pairs
These pairs involve assets with stable prices relative to each other (e.g., USDT/AquaUSDT, GRAM/stTON).
Notably, pegged pairs all but eliminate the risk of impermanent loss. Of course, if the peg breaks, that’s a different story.
Scenario:
You have an AquaUSD/USDT position centered at $1 with a ±0.1% range.
AquaUSD drops to $0.98. Your entire position shifts to AquaUSD.
Instead of waiting for AquaUSD to recover, you decide to act.
Close Position: Withdraw your current position.
Rebalance Assets: Swap 50% of your AquaUSD to USDT.
Open a new position centered at $0.98 with the same ±0.1% range to maximize fee collection.
AquaUSD returns to $1, you close the $0.98 position, swap assets back, and reopen a position centered at $1.
A single relocation reduces the liquidity of the position by approximately 1%, since sqrt(0.98) ~= 0.99. You better hope that the LP fees collected during the single day is grater than 2% of the principal. Otherwise it would have been better to keep the initial position unmoved.
Generally, LPs should be comfortable setting a narrow range. Perhaps as narrow as a 0.1% price deviation on each side of the pegged price (and possibly even narrower). For lower volume pairs, a slightly wider range might be necessary to account for minor depegging.
Advanced Range Presets
Concentrated liquidity allows LPs to go beyond the standard 50/50 token distribution and apply advanced strategies to maximize returns. In the “Advanced LP Strategy Presets” tab, you’ll find ready-made strategies designed to optimize liquidity deployment.

Advanced presets on TONCO DEX
This guide assumes you have experience managing concentrated liquidity positions. If you’re new to this, we recommend first exploring:
• Perks for Projects Launching on TONCO
Advanced strategies often require active position management. If you’re using these presets, make sure you understand the risks—you are fully responsible for your funds
Choosing the Right Liquidity Strategy

Advanced presets on TONCO DEX
Each preset serves different goals, whether you’re a user looking to maximize earnings or a project launching a token on TONCO. There’s no single “best” strategy—performance depends on market conditions and how actively you manage your position.
Presets are divided into two categories:
• For LPs – Optimized strategies for liquidity providers
• For Projects – Liquidity structuring for token launches
Currently, advanced presets are available for USDT and GRAM-paired pools only.
Advanced Presets for LPs
Curve — Balanced Exposure

Curve preset on TONCO DEX
Best for low volatility period, stable pairs or key price levels in volatile pairs.
— Provides steady fee earnings from short-term price movements.
— Liquidity is spread over wide & narrow ranges, ensuring participation in trades across price fluctuations.
💡 Setup:
• Position 1 (Wide Range): [-15%, +15%] – 60% of liquidity
• Position 2 (Narrow Range): [-5%, +5%] – 40% of liquidity
📌 Total Range: [-15%, +15%]
DCA Buy / DCA Sell – Gradual Position Building

DCA Buy preset on TONCO DEX
Perfect for buying or selling assets over time while earning fees.
— Works like a limit order, but also generates trading fees.
— Helps reduce slippage for large buys or exits.
💡 DCA Buy Setup:
• Position 1: [-10%, +2%] – 80% of liquidity
• Position 2: [-2%, +10%] – 20% of liquidity
📌 Total Range: [-10%, +10%]

DCA Sell preset on TONCO DEX
💡 DCA Sell Setup:
• Position 1: [-10%, +2%] – 20% of liquidity
• Position 2: [-2%, +10%] – 80% of liquidity
📌 Total Range: [-10%, +10%]
Advanced Presets for Projects
Buy Walls / Sell Walls – Price Protection and Market Depth

Buy Wall preset on TONCO DEX
Used to stabilize price movements and create resistance/support levels.
— Helps projects control token price floors & ceilings.
— Can be used for automated buybacks during price dips.
💡 Buy Wall Setup:
• Position 1: [-10%, 0%] – 20% of liquidity
• Position 2: [0%, +5%] – 80% of liquidity
📌 Total Range: [-10%, +5%]

Sell Wall preset on TONCO DEX
💡 Sell Wall Setup:
• Position 1: [-5%, 0%] – 80% of liquidity
• Position 2: [0%, +10%] – 20% of liquidity
📌 Total Range: [-5%, +10%]
Steps — Controlled Price Progression

Steps preset on TONCO DEX
Gradually increases price levels to manage early-stage price growth while preventing sudden surges.
— Encourages early adopters by offering better prices initially.
— As demand grows, price moves in structured increments.
💡 Setup:
• Position 1: [-10%, +5%] – 5% of liquidity
• Position 2: [+5%, +15%] – 10% of liquidity
• Position 3: [+15%, +25%] – 25% of liquidity
• Position 4: [+25%, +35%] – 60% of liquidity
📌 Total Range: [-10%, +35%]
Risks of Liquidity Provisioning
Providing liquidity involves risk. Key risks include:
⚠️ Impermanent Loss – If token prices shift significantly, LPs may lose value compared to simply holding the assets.
⚠️ Market Volatility – Sudden price swings can push positions out of range, stopping fee accrual.
⚠️ Smart Contract Risk – As with any DeFi protocol, security risks exist.
Advanced strategies can increase yield, but also require active management. DYOR before deploying liquidity.
Price Moves in Ranges
Once you set a range, it is important to track the market and current prices to make sure that your liquidity is active. If you are unwilling to track the market and check your position regularly, you can add liquidity for a wide/full range
On TONCO DEX users can't edit the position settings (price range, the amount of tokens) after they have provided liquidity. All the parameters are set during the initial liquidity provision. If they wish to change the position, it is necessary to withdraw the liquidity from a particular pool and provide it once again for the new preferable parameters.
Once users provide liquidity, it can be in two stages—active or inactive corresponding to the current price.
Active Liquidity
When the range set by a Liquidity Provider intersects or matches the real price diapason where trades occur, their provided liquidity starts to bring profit. Once liquidity is active, users receive LP rewards and automatically participate in V3 Farms, which also brings profit.
Example:
You are providing liquidity in the GRAM/USDT pair. Let's say the GRAM price is 6 USDT. You have provided 1000 USDT and 166 GRAM (~$2000), then you have set a narrow price range for GRAM/USDT of -5% to +10%.
This means you will receive a fees when the price ranges from $5.7 to $6.6 USDT per 1 GRAM.
Assuming an initial APR of 100% for this narrow range, you will earn rewards as long as the price stays within this range.
If the price fluctuates within the range of $5.7 to $6.6 USDT, as an LP provider for 1 month, you will approximately receive:
 (1).png)
📍 If the price moves outside the range, APR will not be earned.
📍 If the price exceeds the upper limit (6.6 USDT per GRAM), the position will only consist of USDT. If the price falls to the lower limit (5.7 USDT per GRAM), the position will fully transition to the volatile asset — GRAM.
The narrower the range you set, the higher your APR becomes, as its liquidity is used much more effectively. But the greater the risk that the price will move out of the range.
Inactive Liquidity
Crypto prices can move outside the range set by the Liquidity Provider because of market volatility. In such a case, the liquidity stops earning LP rewards from fees and becomes inactive.

Inactive liquidity scenario at TONCO DEX
1. Hold Position (Passive Management)

Passive liquidity management on TONCO DEX
You can simply leave your liquidity as is, even if it moves out of range.
Pros:
✅ No transaction fees (gas costs) from rebalancing.
✅ If the price returns to your range, you'll start earning fees again automatically.
Cons:
❌ No fees earned while out of range.
❌ Your position shifts to mostly USDT, which may not be ideal if you expect GRAM to keep rising.
2. Adjust Liquidity Range (Active Management)

Active liquidity management on TONCO DEX
Reposition liquidity to a higher range ($4.2–$4.9 for example) by withdrawing and redepositing. This may require swapping or using single-sided liquidity while awaiting a retracement.
Pros:
✅ Start earning fees again if the price stays in the new range.
✅ Maintain exposure to GRAM if you expect the uptrend to continue.
Cons:
❌ Incurs transaction costs.
❌ If the price quickly drops back, you might miss out on fees and pay extra to readjust.
3. Withdraw and Hold (Exit the Pool)

"Withdraw and hold" management strategy on TONCO DEX
You can withdraw your liquidity entirely and either hold your assets or reinvest them elsewhere.
Pros:
✅ Avoid potential impermanent loss if you expect GRAM to keep rising.
✅ Flexibility to explore other investment opportunities.
Cons:
❌ No longer earning trading fees.
❌ May incur transaction fees and potential tax implications.
4. Add More Liquidity (Increase Exposure)

"Add more liquidity" management strategy on TONCO DEX
If you have extra capital, you can add liquidity at higher price ranges without altering your existing position.
Pros:
✅ Earn fees across a wider range if volatility increases.
✅ Diversify your liquidity positions within the pool.
Cons:
❌ Greater exposure to price swings if the market moves against you.
❌ More capital locked in the liquidity pool.
For this reason, users should track the state of their liquidity regularly to notice inactive states on time
⚠️ Get instant alerts when your position goes out of range and stops earning fees. Simply attach your wallet, select the positions to track & receive 24/7 notifications.
Try it using TONCO Notifications Bot: t.me/tonco_notifications_bot
Impermanent Loss
What is Impermanent Loss?
Impermanent loss occurs when the value of your assets in a liquidity pool is lower at withdrawal than at the time of deposit. It’s also known as unrealized profit, as sometimes it may be more profitable to simply hold your assets in your wallet.
However, impermanent loss doesn’t become a real loss until you close the position, and it could recover if prices return to their original levels.
Why does it happen?
Impermanent loss happens in liquidity pools that contain volatile tokens. When you provide liquidity on TONCO in V3 pools, you supply two tokens in a specific ratio based on price range. If the price of one of the tokens changes, the pool adjusts and rebalances, changing the asset ratio.
Impermanent Loss in TONCO DEX
- The minimal IL — is a loss that occurs when the price in a pool changes, causing the asset ratios to change as well. The position continues to earn fees, but the dollar value of the current asset amount and the initial position differs. This type of IL is unavoidable but can be reduced by narrowing the position price range.
- The out-of-range IL — is a loss that the position suffered when the pool price exceeds the set price range. When the price goes out of the range, the position, there is only one asset left and not earning trading fees until the price reenters the range. While the position ratio is frozen in overweight to the falling asset, the position does not bring any LP rewards.
The difference between the dollar value of the initially supplied position and the frozen position is the out-of-range impermanent loss.
This loss can be avoided without any losses in profit by withdrawing the position immediately after it falls out of range and redepositing it back with a new ratio.
HODL or Provide liquidity?
When deciding whether to hold tokens or provide liquidity to any pool on TONCO, it depends on the market’s movement and your strategy.
Holding may outperform LP during a bull run, but LP can provide consistent fees during sideways or down markets. LP offering potential gains through fees while mitigating some market risks.
Rebalancing is essential, and LP can protect against price drops by earning fees, which may offset impermanent loss depending on the pair and range.
Choosing a Strategy
The table below shows the PnL of various positions relative to the 50:50 HODL position, under decreasing️️ 📉, mean-reverting️, and increasing️ 📈prices. “Cash” means a position in stablecoins.
 (1).png)
Expected profits relative to a 50:50 HODL position as a benchmark
Research & Provide Liquidity Wisely
Once you become a TONCO DEX investor, you get responsibility for your deposit. Thus, it is highly important to conduct deep research before supplying liquidity. Pay special attention to the following aspects.
- Tokens volatility
The pool that consists of non-stable tokens (like GRAM/USDT) is more likely to be affected by impermanent loss. But it doesn't mean that such types of pools are not worth the investments because other factors also play a role: total amount in the pool, trading fees, and trading activity. - Market tendencies
The general market moods directly impact trading volumes and price changes. - Amount in the pool
The more liquidity in the pool, the less sensitive liquidity provider will be to the impermanent loss. - Price range
The narrower the price range, the more likely a permanent loss will occur because it is more likely to exit the narrow price range than a slightly larger one. On the other hand, the narrower range means greater capital efficiency, which in turn, can cover the impact of impairment loss.
Apply your own strategy based on analyses and profit from concentrated liquidity on TONCO.
Liquidity Scenarios
We have simulated a few calculations to give you a better understanding of how concentrated liquidity works under constant management, static range, and wide range strategies
Let’s assume the GRAM/USDT price starts at 6.00 USDT per GRAM. An LP deploys a position at the center price of $6.00, with a narrow price range of $5.40 to $6.60. If the price goes beyond this range, the LP redeploys the position, adjusting the price boundaries.
Active LP competes with a few other strategies:
- Static: Deploy and forget. The price range and center are fixed
- Wide-range: Uses a broader price range ($4.80 to $7.20)
The initial value of assets is $1000.
This simulation does not account for LP rewards. It solely focuses on the impermanent loss and value changes of the assets in different scenarios.
Scenario #1: Upwards Momentum
The price increases by 10% twice (to 7.26 USDT per GRAM), triggering the LP to rebalance once.
• Active LP (Rebalances at 6.60 USDT, setting new range 5.94-7.26 USDT): $1049.40 (+4.94%)
• Static LP (No rebalancing): $1024.40 (+2.44%)
• Wide-range LP: $1050.00 (+5.00%)
The active LP beats the static narrow-range LP, but loses to wide-range strategy.
Scenario #2: Downwards Momentum
The price decreases by 10% twice (to 4.86 USDT per GRAM), triggering the LP to rebalance once.
• Active LP (Rebalances at 5.40 USDT, setting new range 4.86-5.94 USDT): $867.28 (-13.27%)
• Static LP (No rebalancing): $846.62 (-15.34%)
• Wide-range LP: $867.77 (-13.22%)
The active LP loses less than the static LP, but is still outperformed by wide-range LP.
Scenario #3: Oscillations
Price moves up to 6.60 USDT and then back to 6.00 USDT
• Active LP (Rebalances once at 6.60 USDT): $954.00 (-4.6%)
• Static LP (No rebalancing): $1000.00 (0.0%)
• Wide-range LP: $1000.00 (0.0%)
In this scenario, the active LP strategy results in a permanent loss, while the static and wide-range LP strategies maintain the initial asset value.
This simulation highlights the importance of choosing the right liquidity strategy based on market conditions and personal risk tolerance.
Perks for Liquidity Providers
Higher Capital Efficiency
Concentrated liquidity enables liquidity providers (LPs) to focus their capital within targeted price ranges where trading is most likely to happen. This makes the provided liquidity more effective and better utilized. By concentrating liquidity in specific ranges, the same capital can facilitate larger trading volumes, resulting in a more efficient use of assets and higher returns for LPs.
Concentrated liquidity boosts capital efficiency up to 20x by placing liquidity within specific price ranges.

Increased Fee Earnings
With the ability to concentrate liquidity in the most active trading zones, LPs can earn more fees than they would by spreading liquidity across the entire price spectrum. This flexibility encourages LPs to engage more strategically, positioning their liquidity where it can generate the highest returns.

Alice and Bob are providing liquidity to a GRAM/USDT pool. They each have $1 million in Gram (prev. Toncoin) and USDT. Alice invests her whole stash of tokens across the entire price range, which is 500,000 USDT and 100,000 GRAM at the price of Gram equaling 5 USDT.
Bob, on the other hand, takes a concentrated position, investing only 91,750 USDT and 18,350 GRAM (worth ~$183,500) within the price range of 2.5 to 7.5.
Despite the fact that Alice has deposited 5.44x as much capital as Bob, as long as the GRAM/USDT price stays within the 2.5 to 7.5 range, they are going to earn the same amount of fee rewards. Basically, it means that Bob’s capital is more efficient and can earn 5.44x more than Alice’s (per dollar deposited).
In case the price breaks out of this price range, Bob can no longer earn fees, and his funds will be converted to the less valuable token. At the same time, Alice’s liquidity, or liquidity on v2 DEXs, will be exposed to impermanent loss to a lesser extent. In this sense, we can imagine a full-range position on the decentralized exchange, with concentrated liquidity equal to the usual position on a v2 exchange. The smaller the range, the faster liquidity gets converted while the price moves. At the same time, choosing a concentrated position and taking on more risk of impermanent loss is remunerated fairly by increasing the LP effectiveness.
As in the worst-case scenario when one token loses all its value and its price falls to 0, both Alice and Bob will end up with the asset being worth nothing. However, Bob will lose only ~$183,500 (~16% of his capital), with Alice’s capital being gone in its entirety.
Customizable Risk Management
Liquidity providers (LPs) have the flexibility to manage their risk by selecting price ranges that align with their risk tolerance. For example, an aggressive LP may choose a narrower range near the current market price, which offers the potential for higher fee earnings but comes with increased risk if the price moves outside the range. Conversely, a more conservative LP might opt for a wider range, reducing the risk of being out of range while still earning steady fees.\
 (1) (1) (1) (1) (1) (1) (1).png)
Perks for Traders
Lower slippage
One of the key benefits for traders is reduced slippage. With concentrated liquidity, deeper liquidity is available exactly where it’s needed most—within active trading ranges. This ensures that trades can be executed with minimal price impact, providing better pricing and a smoother trading experience. Because liquidity is focused in specific price ranges, rather than spread across an infinite spectrum as in traditional AMM v2 models, traders face less slippage and enjoy more accurate pricing. By incentivizing greater liquidity depth around the current market price, concentrated liquidity allows for better trade execution and reduced costs for traders.
💡 Example: You can trade USDT for 1,000 GRAM withoth any slippage. Add 5,500 USDT to a narrow price range in the GRAM/USDT pool. As the price moves, your USDT automatically converts to GRAM with zero slippage—a limit order in action
Be sure to withdraw your liquidity afterward to secure your tokens
 (1) (1) (1) (1) (1) (1).png)
Improved Trade Execution
With more liquidity concentrated in active price ranges, trades are executed more efficiently. This means fewer failed or delayed transactions during high volatility periods, as the liquidity pools are designed to support higher trading volumes without disrupting the flow of trades. The result is a faster and more reliable trading experience for users.
Better Price Stability
Concentrated liquidity also leads to greater price stability. Since liquidity is focused within specific price bands, the price impact of individual trades is minimized, creating more consistent and predictable price movements. Traders benefit from more stable pricing, even during high trading volumes, which can help them manage their trades with greater confidence.
\
Perks for Projects
Why launch tokens on TONCO?
Concentrated liquidity enables the creation of any price curve, offering limitless flexibility for designing token launch and maintenance strategies. Projects can fine-tune the balance between price, supply volume, and profit.
You can use ready-made advanced presets on TONCO to apply these strategies
Let's check a few strategies using TONCO DEX:
Strategy 1: "One Price for All"
In this strategy, the entire supply (or a large portion of it) of tokens is placed within one or several price ranges. This allows all users to purchase tokens at the same price, regardless of when they buy.
 (1) (1) (1) (1) (1).png)
Strategy 2: "The Steps"
Liquidity is arranged in steps, requiring more purchases to reach the next price level. This strategy accelerates early-stage price growth when it’s most important and then slows down as the project matures, allowing it to profit without causing sudden price swings.
 (1) (1) (1).png)
Strategy 3: "Price Drop Protection"
With a large enough liquidity pool at launch (or by using one of the above strategies), a project can safeguard its token against price drops. By placing maximum liquidity (e.g., USDT/GRAM) within the nearest price range, users would need to sell a massive volume of tokens to shift the price, making a significant drop nearly impossible.
 (1) (1) (1).png)
Strategy 4: "Dynamic Fees"
Beyond liquidity management, projects can adjust trading fees for their token pairs. This fee serves as an additional revenue stream for LPs and can reach millions of dollars. Setting a low fee in early stages attracts traders, but this parameter can be adjusted based on project goals.
Concentrated liquidity enables a range of launch and maintenance strategies, which can be used in combination or in succession to create dynamic, efficient frameworks.
Rug-Pull Protection (coming Q4 2025)
To protect against a “rug pull”—where a project withdraws liquidity, leaving investors without a way to sell their tokens—projects can use token position (NFT) locks. This ensures tokens can’t be withdrawn, either fully or partially, until a predetermined date. Lock types include:
- Permanent: The project won’t access the liquidity at all.
- Temporary: Liquidity becomes movable only after a set date.
Lock durations can be customized based on strategy, providing both flexibility and security for users.
Farming
Projects providing liquidity on TONCO can motivate users to buy tokens and supply liquidity themselves through a farming program. Rewards are fairly distributed only to those whose liquidity is actively traded, attracting users and encouraging effective liquidity placement, reducing price impact during trades and driving up trading volume.
Apply for free listing on TONCO DEX to create efficient liquidity pools with your tokens.
The TONCO team is here to assist every step of the way—from strategy selection within our CLAMM model to co-marketing.
Fill out the form to get started: forms.gle/n4rgcjZvPnomdGye6
Liquid Staking Tokens (LST)
Liquid Staking Tokens (LSTs), like tsTON from Tonstakers or stTON from bemo, represent the value of your staked assets (TON) but are portable and accessible, allowing you to use them in DeFi protocols like TONCO DEX to earn additional yield while still receiving staking rewards.
On TONCO DEX, you can provide liquidity with LSTs in a concentrated liquidity pool. This means you can target specific price ranges, maximizing your capital efficiency and earning more trading fees compared to traditional V2 pools on other DEXes like STONfi and DeDust.
Сoncentrated Liquidity vs. Full-Range Liquidity
In a V2 pool, liquidity is spread across the entire price range from 0 to ∞, but with TONCO’s concentrated liquidity, you can set a narrow range around the current price, ensuring more of your liquidity is used for trades, resulting in higher trading fees and a higher APR.
tsTON and stTON are reward-bearing tokens, that represents your staked GRAM, including returns from staking. As staking rewards are received, tsTON (stTON) increases in value, without any change to the quantity of tokens.
For example, if the current price of stTON is 1.2 GRAM and the net staking yield is 5% per year, after one year, 1 stTON will be worth 1.26 GRAM (1.2 + 0.06)
tsTON and stTON are fully backed by the staking pool, consisting of GRAM tokens participating in validation. This ensures there is no risk of a depeg, meaning the price deviation from its fundamental value is eliminated. This makes concentrated liquidity especially effective for the tsTON/GRAM and stTON/GRAM pair, where you can confidently set a narrow range around the current price to maximize capital efficiency.
Liquidity provision cases for LSTs on TONCO
tsTON/GRAM and stTON/GRAM pairs
With tsTON or stTON being a yield-bearing token that grows in value, its price is directly linked to GRAM. By setting a narrow price range around the current price, you ensure that more of your liquidity is active in trades:
 (1).png)
Narrow range in stTON/GRAM pair on TONCO
This approach leads to higher trading commissions and a higher APR compared to V2 DEXs.
tsTON/USDT and stTON/USDT pairs
Liquidity in ranges
Price range represents the price window where liquidity is concentrated and trades are executed. If the price moves outside the selected range, the position stops earning fees and becomes inactive.
— Use the presets or manually set minimum and maximum prices according to your strategy.
— A smaller range offers higher yield but higher risk of the price moving outside the selected range.
— The closer your range is to one side of the market price, the more of that specific asset you’ll provide.
When choosing a price range, consider how much you expect the prices to fluctuate while holding your position. Also, think about how much time and effort you’re willing to spend managing your position as market conditions change, and be aware of any network fees associated with making adjustments
 (1).png)
Choosing price range in stTON/USDT pair
Single-sided liquidity
Single-sided liquidity on TONCO is a powerful feature that lets you allocate liquidity on one side of the market, either above or below the current spot price, depending on your strategy.
With this option you can apply a range order strategy. A range order is like setting a limit order but with an added advantage—you earn fees instead of paying them. This strategy works well with volatile/stable asset pairs (like tsTON/USDT or stTON/USDT).
When you want to sell, input single-sided liquidity in the volatile asset (tsTON or stTON). If the price rises above your set range, your assets will convert to the stable asset (USDT), and you can exit (close your position):

Single-sided liquidity on TONCO for selling stTON
For buying tsTON or stTON at a specific price, reverse the process: use single-sided liquidity in the stable asset, and set your buying range. When the price drops below your range, your assets convert to the volatile asset (tsTON or stTON), allowing you to exit:
.png)
Single-sided liquidity on TONCO for buying stTON
This flexibility, combined with concentrated liquidity, maximizes your capital efficiency and earnings potential. Explore the detailed strategies in the following pages and check out our guide on hedging risks by using EVAA lending platform.
any token/tsTON (stTON)
Creating a pool for any token paired with tsTON or stTON is highly profitable on TONCO. The reason? The value of tsTON and stTON is tied to TON, and it grows with the staking APY (approx. 3.50% as of December 27th).
You benefit from the consistent increase in tsTON's (stTON’s) value as it reflects the staking returns of GRAM. This means higher capital efficiency and increased yield for liquidity providers.
.png)
Pool creation using stTON on TONCO
By choosing a token/tsTON (stTON) pair instead of token/GRAM when creating a pool, liquidity providers can earn additional income without taking on new risks.
At TONCO, we offer free token listing and additional listing bonuses. We’ll guide you in placing liquidity efficiently to maximize your returns. Fill out the form to connect with us.
Benefits for traders and LPs
— TONCO ensures minimal price impact, allowing you to trade large volumes of LSTs with almost zero slippage due to its concentrated liquidity in narrow price ranges.
— TONCO significantly reduces impermanent loss for tsTON/GRAM or stTON/GRAM pair, making it a safer and more profitable choice. Concentrated liquidity offers a more secure environment by maintaining stable asset values, protecting LPs from market volatility.
— High trading volumes due to low slippage result in higher fee earnings for liquidity providers, making it an attractive option for both traders and LPs.
Stablecoins
Stablecoins like USDT, USDe (plus its rebasing asset tsUSDe), and tgUSD are a natural fit for TONCO’s concentrated liquidity model, where liquidity is deployed in specific price ranges rather than spread from 0 to ∞. This model unlocks a new level of efficiency and profitability for both traders and liquidity providers.
Why TONCO is perfect for stable pairs
TONCO is the first concentrated liquidity DEX on TON, offering clear advantages over traditional V2 AMMs. Stablecoin pairs, which typically trade within tight price ranges around $1, benefit the most from concentrated liquidity model:
- Ultra-low slippage, even at high trade volumes
- Tighter spreads and better execution for swappers
- Higher capital efficiency, since liquidity is only placed where trades actually occur
- Stronger fee APRs for LPs, as more trades are concentrated within narrow ranges
Example: In the tgUSD/USDT pool, liquidity can be concentrated in the price range of 0.999–1.001. This enables up to 2,000x higher capital efficiency compared to traditional V2 DEXs, where liquidity is spread across a wide, mostly unused price spectrum.
With TONCO, LPs can set liquidity around narrow ranges like 0.995–1.005 or even tighter, depending on the expected volatility of the stable pair. This structure:
- Absorbs larger trades without causing price impact
- Enables high-frequency trading strategies
- Creates reliable, predictable execution for aggregators and bridges
Examples of liquidity strategies
USDT/tgUSD (stable-stable pair)
Both tokens are dollar-pegged, meaning their prices hover close to $1. LPs can confidently provide liquidity in narrow range, capturing more trades and earning higher fees.
• Basic strategy: 0.997 – 1.003 (covers mild fluctuations in peg, low maintenance)
.png)
Basic strategy for stable-stable pair on TONCO DEX
• Advanced split: Tight range at 0.999 – 1.001 + backup at 0.995 – 1.005 (maximizes fee capture while reducing risk of going out of range)
This split strategy allows LPs to:
- Maximize yield from tightly packed trade volume
- Maintain uptime even during brief peg shifts
- Reduce idle capital and improve return on liquidity
.png)
Advanced split for stable-stable pair on TONCO DEX
USDe/tsUSDe (rebasing stable-yield pair)
Unlike traditional stable pairs, tsUSDe is a rebasing token that gradually increases in value relative to USDe as it accrues yield.
• Split strategy: 0.995 – 1.005 (short-term) and 1.005 – 1.03 (longer-term exposure to tsUSDe yield growth)
.png)
Split strategy for rebasing stable-yield pair on TONCO DEX
This approach allows LPs to:
- Capture fee volume while the pair is near parity
- Earn yield passively as tsUSDe appreciates
- Avoid frequent repositioning thanks to the slow-moving upward peg
Who benefits from stablecoin liquidity on TONCO?
- Traders: Enjoy better execution, lower slippage, and tighter spreads
- Cross-chain bridges: Achieve high-volume, low-impact transfers across chains
- Retail and institutional LPs: Earn consistent income from fees with less risk
- Wallets, bots, aggregators: Tap into deep liquidity and predictable routing
Stablecoins power payments, savings, transfers, and cross-chain operations.
For these systems to scale efficiently, they need precision infrastructure. That’s exactly what TONCO delivers:
- High liquidity density with low slippage
- Minimal idle capital through range-based provisioning
- Optimized yield, execution, and LP profitability
Basic Strategies
TONCO’s concentrated liquidity is set to transform the DEX landscape on TON, offering new liquidity pools. If you’re new to this liquidity model, here are some basic strategies to help you understand how it works.
You can use ready-made advanced presets on TONCO to apply these strategies
1. APR-focused strategy
The key to this strategy is adjusting your position regularly to stay within the most profitable price range. Narrower ranges lead to higher APR, as liquidity is used more effectively:
.png)
Narrow range on TONCO
Focus on high-performing pools without considering asset appreciation. Keep track of APR, fees, trading volume, TVL, and other stats on the dedicated APR graph for each pool: app.tonco.io/#/explore:
 (1).png)
GRAM/USDT Max Apr Graph
2. Range order strategy (buy/sell)
A range order is like setting a limit order but with an added advantage—you earn fees instead of paying them. This strategy works well with volatile/stable asset pairs (like GRAM/USDT).
When you want to sell, input single-sided liquidity in the volatile asset (GRAM). If the price rises above your set range, your assets will convert to the stable asset (USDT), and you can exit (close your position):
 (1) (1) (1).png)
Single-sided liquidity on TONCO for selling volatile asset
For buying, reverse the process: use single-sided liquidity in the stable asset, and set your buying range. When the price drops below your range, your assets convert to the volatile asset (GRAM), allowing you to exit:
 (1) (1).png)
Single-sided liquidity on TONCO for buying volatile asset
3. Dollar Cost Average (buy/sell)
The DCA strategy is similar to range orders but operates within a wider range. It’s suitable for all market conditions—bullish, bearish, or neutral. This strategy allowing you to buy more assets as prices dip or sell as prices rise, all while earning fees.
Assuming the price of GRAM is 6, you might create a GRAM/USDT pool with a range of 5.5 to 8.5 to sell GRAM as the price moves up:
.png)
Dollar Cost Averaging (DCA) strategy on TONCO (sell GRAM)
Alternatively, to buy GRAM you might create a pool with a range shifted in the other direction to buy GRAM as the price moves down:
.png)
Dollar Cost Averaging (DCA) strategy on TONCO (buy GRAM)
4. Covered call (focus on sell)
The Covered call strategy in TONCO resembles options trading, where you “sell” the underlying asset at a predetermined price. To use this strategy, place single-sided liquidity in a volatile/stable pool at the price you want to sell:
.png)
Covered call strategy on TONCO
It’s crucial to burn the position (withdraw liquidity) when the set price is reached, otherwise, it can go backward
This single-tick range eliminates impermanent loss, and the fees you earn are similar to the premium in options trading. It’s a great way to earn fees while setting your exit price for the underlying asset.
Hedging with EVAA (Lending)
.jpg)
TONCO x EVAA hedging strategy
Entering unprotected LP positions, especially within a narrow range, can be high-risk. While APR on TONCO can be as high as 3-digits on GRAM/USDT pair, the impermanent loss can eat into your profits.
It may be a good idea to hedge the position in order to protect your investment.
A hedged LP position involves using additional funds as collateral to borrow the volatile asset like GRAM, using the EVAA Finance protocol (hedging the downside risk on price decrease).
Example
You have $15,000 and want to open a position in the GRAM/USDT pair on TONCO DEX. Assuming the APR for a GRAM/USDT pair is 100% and Gram (prev. Toncoin) is traded at 5 USDT.
You put 1500 GRAM and 7,500 USDC into a liquidity pool on TONCO DEX. Let's say you put range between 4.5 and 5.5. As long as the GRAM price stays within your designated range, you’ll earn trading fees.
One day, the price of GRAM drops beyond your range to 4 USDT. You end up with a position consisting of ≈ 3,060 GRAM (as the ratio changes along the way, you end up with slightly more than 2x GRAM) and 0 USDT. You are now fully exposed to the GRAM price and experience permanent loss.
After selling the tokens received for providing liquidity for 30 days while being in-range, the cumulative yield stands at 1,232 USDT (calculated as 7,500 * 2 * 100% / 365 * 30). You decide to stop providing liquidity.
If you sell the 3,060 GRAM at 4 USDT each and combine the proceeds with the 1,232 USDT, you’ll see a total loss of 1,528 USDT ((3,060 * 4 + 1,232) - 7,500 * 2) due to impermanent loss.
Hedging vs. No Hedging
• Without Hedging
If you had opened the original position without any hedging ($15,000: 7,500 USDT + 1,500 GRAM), you would have ≈3,060 GRAM and 0 USDT. If you decide to lock in your loss and withdraw 3,060 GRAM, you would receive:
— 3,060 * 4 = $12,240 (if the price of GRAM dropped to $4)
— Trading fees ($1,232 from the example above)
The total would be:
$12,240 + $1,232 = $13,530, a -10.18% loss
• With Hedging on EVAA (the downside risk on price decrease)
In the hedged scenario, you supply 10,000 USDT to borrow the volatile asset (1,500 GRAM) from EVAA. After the GRAM price drops, you withdraw ≈ 2,040 GRAM. You sell 540 GRAM for $2,160 and repay the 1,500 GRAM borrowed from EVAA. You also retrieve your $10,000 collateral from EVAA and have an additional $2,500 from the initial GRAM-USDT trade at $5.
The total would be:
— $2,160 (from the sale of 540 GRAM at $4)
— $2,500 (from the remaining GRAM traded at $5)
— $10,000 (collateral returned)
This gives you:
$2,160 + $2,500 + $10,000 = $14,660, plus:
— Trading fees ($1,232 from the example above)
— EVAA net APR (+6.92%, which equals $58)
The total would be:
$14,660 + $1,232 + $58 = $15,950, a +6.4% profit
This means that by hedging, you have completely mitigated the loss from the drop in the GRAM price and made a profit from the position.
Step-by-step strategy for hedging
Here’s a step-by-step strategy for hedging the downside risk of a GRAM price decrease.
There is a symmetrical strategy that aims to protect the gains in case the asset price goes up. Instead of borrowing GRAM, you’d either buy it and borrow USDT against it.
1. Supply USDT to EVAA
Start by supplying $10,000 worth of USDT to EVAA. As of December 18th, you’ll earn an APR of over 9.4% on USDT.
.png)
EVAA markets as of Dec 18th, 2024
2. Borrow GRAM
Pledge your $10,000 USDT as collateral to borrow 1,500 GRAM (assuming the price of 1 GRAM = 5 USDT). You will pay a borrowing APR of around 2.4% on GRAM. Your net APR on EVAA will be ≈ +6.9% (as of December 18th).
3. Open LP position
You now have your initial $5,000 in USDT plus the 1,500 GRAM borrowed from EVAA.
Open an LP position with 5,000 USDT and 1,000 GRAM (worth $5,000). This brings your total position value to $10,000. After opening the position, you can sell the remaining 500 GRAM you borrowed, converting it into 2,500 USDT.
The main disadvantage of this hedging strategy is that it is capital-intensive. You need enough funds to supply on EVAA to make it viable
Hedging with Storm Trade (Perp DEX)

Storm Trade x TONCO DEX hedging strategy
An alternative way to hedge your LP position using lending platforms is by short-selling the volatile asset via margin trading on Storm Trade, which allows leverage up to x75 on GRAM/USDT pair.
This method is less capital-intensive compared to using lending, as DeFi lending requires overcollateralized loans. With short-selling on Storm, you can use margin as collateral, meaning you don’t need as much initial capital to open your position.
This makes it a more flexible option for hedging, especially if you want to avoid locking up significant funds in collateral. However, the downside of this approach is the risk of liquidation. If you’re using a high leverage, a small price movement against your position can lead to a liquidation, causing you to lose your collateral.
Example
You have $15,000 and want to open a position in the GRAM/USDT pair on TONCO DEX. Assuming the APR for a GRAM/USDT pair is 100% and Gram (prev. Toncoin) is traded at 5 USDT.
You put 1500 GRAM and 7,500 USDC into a liquidity pool on TONCO DEX. Let's say you put range between 4.5 and 5.5. As long as the GRAM price stays within your designated range, you’ll earn trading fees.
One day, the price of GRAM drops beyond your range to 4 USDT. You end up with a position consisting of ≈ 3,060 GRAM (as the ratio changes along the way, you end up with slightly more than 2x GRAM) and 0 USDT. You are now fully exposed to the GRAM price and experience permanent loss.
After selling the tokens received for providing liquidity for 30 days while being in-range, the cumulative yield stands at 1,232 USDT (calculated as 7,500 * 2 * 100% / 365 * 30). You decide to stop providing liquidity.
If you sell the 3,060 GRAM at 4 USDT each and combine the proceeds with the 1,232 USDT, you’ll see a total loss of 1,528 USDT or ((3,060 * 4 + 1,232) - 7,500 * 2) due to impermanent loss.
Hedging vs. No Hedging
• Without Hedging
If you had opened the original position without any hedging ($15,000: 7,500 USDT + 1,500 GRAM), you would have ≈3,060 GRAM and 0 USDT. If you decide to lock in your loss and withdraw 3,060 GRAM, you would receive:
— 3,060 * 4 = $12,240 (if the price of GRAM dropped to $4)
— Trading fees ($1,232 from the example above)
The total would be:
$12,240 + $1,232 = $13,472, a -10.18% loss
• With Hedging on Storm Trade (the downside risk on price decrease)
We use 5,000 USDT as collateral and open a short position for 1,000 GRAM (assuming GRAM = $5). No leverage is used in this example, but it can be adjusted according to your risk tolerance.
You can set a stop loss at the minimum tick of your price range on TONCO to protect against a significant price rise. This ensures the position on Storm Trade closes if the price moves unfavorably.
After the GRAM price drops to $4 you withdraw ≈2,040 GRAM from TONCO. But since TON’s price dropped, the short position you opened on Storm Trade became a profitable trade for you. You should profit 1,000 USDT (1,000*1) after closing the short position and withdraw initial 5,000 USDT collateral back.
You have to pay $6 service fee (0.12% of the position size).
But your earn additional $189 from positive funding rates over 30 days (assuming a 2.52% additional yield from the funding rate of 0.0035% per hour; as for Dec 19th).
The total would be:
— $8,160 (from the sale of 2,040 GRAM at $4)
— $1,000 (profit from short position)
— $5,000 (collateral returned)
This gives you:
$8,160 + $1,000 + $5,000 = $14,160, plus:
— Trading fees ($1,232 from the example above)
— Funding rate profit: $183 (net profit from the positive funding rate minus service fees)
Grand total:
$14,160 + $1,232 + $183 = $15,575, a +3.82% profit
This means that by hedging, you have completely mitigated the loss from the drop in the GRAM price and made a profit from the position.
Step-by-step strategy for hedging
Here’s a step-by-step strategy for hedging the downside risk of a GRAM price decrease.
There is a symmetrical strategy that aims to protect the gains in case the asset price goes up. Instead of shorting GRAM, you’d long it.
1. Open a Short Position
Use 5,000 USDT as collateral on Storm Trade to open a short position for 1,000 GRAM (assuming GRAM = $5). You can place a stop loss at the minimum of your price range to protect against price increases.
 (1) (1) (1).png)
Shorting GRAM on Storm Trade
2. Open LP position on TONCO
Provide liquidity with $5,000 USDT and 1,000 GRAM on TONCO DEX.
This brings your total position value to $10,000.
 (1) (1).png)
Hedging with Tradoor (Perpetual futures)

TONCO x Tradoor hedging strategy
An alternative way to hedge the downside risk of price decrease on your LP position using lending platforms is to short the volatile asset via decentralized perpetual futures on Tradoor, which allows leverage up to x100.
Perpetuals are crypto derivative contracts that let traders buy or sell assets without an expiration date
This method is less capital-intensive compared to using lending, as DeFi lending requires overcollateralized loans. With short-selling on Tradoor, you can use margin as collateral, meaning you don’t need as much initial capital to open your position.
This makes it a more flexible option for hedging, especially if you want to avoid locking up significant funds in collateral. However, the downside of this approach is the risk of liquidation. If you’re using a high leverage, a small price movement against your position can lead to a liquidation, causing you to lose your collateral.
Example
You have $15,000 and want to open a position in the GRAM/USDT pair on TONCO DEX. Assuming the APR for a GRAM/USDT pair is 100% and Gram (prev. Toncoin) is traded at 5 USDT.
You put 1500 GRAM and 7,500 USDC into a liquidity pool on TONCO DEX. Let's say you put range between 4.5 and 5.5. As long as the GRAM price stays within your designated range, you’ll earn trading fees.
One day, the price of GRAM drops beyond your range to 4 USDT. You end up with a position consisting of ≈ 3,060 GRAM (as the ratio changes along the way, you end up with slightly more than 2x GRAM) and 0 USDT. You are now fully exposed to the GRAM price and experience permanent loss.
After selling the tokens received for providing liquidity for 30 days while being in-range, the cumulative yield stands at 1,232 USDT (calculated as 7,500 * 2 * 100% / 365 * 30). You decide to stop providing liquidity.
If you sell the 3,060 GRAM at 4 USDT each and combine the proceeds with the 1,232 USDT, you’ll see a total loss of 1,528 USDT ((3,060 * 4 + 1,232) - 7,500 * 2) due to impermanent loss.
Hedging vs. No Hedging
• Without Hedging
If you had opened the original position without any hedging ($15,000: 7,500 USDT + 1,500 GRAM), you would have ≈3,060 GRAM and 0 USDT. If you decide to lock in your loss and withdraw 3,060 GRAM, you would receive:
— 3,060 * 4 = $12,240 (if the price of GRAM dropped to $4)
— Trading fees ($1,232 from the example above)
The total would be:
$12,240 + $1,232 = $13,472, a -10.18% loss
• With Hedging on Tradoor
We use 5,000 USDT as collateral and open a short position for 1,000 GRAM (assuming GRAM = $5). No leverage is used in this example, but it can be adjusted according to your risk tolerance.
For a short hedge, set the stop-loss at the upper bound of your LP range ($5.5 from the example above) to cap losses if price rises. Optionally set a take-profit at the lower bound ($4.5) to lock in hedge gains as your LP exits the range on the downside.
After the GRAM price drops to $4 you withdraw ≈2,040 GRAM from TONCO. But since TON’s price dropped, the short position you opened on Tradoor became a profitable trade for you. You should profit 1,000 USDT (1,000*1) after closing the short position and withdraw initial 5,000 USDT collateral back.
You have to pay $4.5 service fee (0.09% of the position size).
The total would be:
— $8,160 (from the sale of 2,040 GRAM at $4)
— $1,000 (profit from short position)
— $5,000 (collateral returned)
This gives you:
$8,160 + $1,000 + $5,000 = $14,160, plus:
— Trading fees ($821.92)
Grand total:
$14,160 + $821.95 = $14,977, a -0.15% net loss
(hedge nearly neutralizes the downside vs. −10.18% unhedged.)
Step-by-step strategy for hedging
Here’s a step-by-step strategy for hedging the downside risk of a GRAM price decrease.
There is a symmetrical strategy that aims to protect the gains in case the asset price goes up. Instead of shorting GRAM, you’d long it.
1. Open a Short Position
Use 5,000 USDT as collateral on Tradoor to open a short position for 1,000 GRAM (assuming GRAM = $5). You can place a stop loss at the minimum of your price range to protect against price increases.
.png)
Opening short position on Tradoor
2. Open LP position on TONCO
Provide liquidity with $5,000 USDT and 1,000 GRAM on TONCO DEX.
This brings your total position value to $10,000.
 (1) (1).png)
Opening LP position on TONCO DEX
Adding Liquidity
Concentrated Liquidity Market Maker (CLMM) pools allow liquidity providers to select specific price ranges for their liquidity, unlike constant product Automated Market Maker (AMM) pools like DeDust or STONfi, where liquidity is spread from 0 to ∞.
In CLMM pools, LPs choose a specific price range to provide liquidity. They earn fees proportionate to their share at the current price, incentivizing active management to keep the pool’s price within their range.
If the price moves outside the selected range, the position stops earning fees and becomes inactive.
How to add liquidity to a pool on TONCO DEX
TONCO's liquidity pools allow anyone to provide liquidity by adding their assets to a pool.
1. Choose a Pool
— Go to the ‘Pools’ tab and select the desired pool, for example, GRAM/USDT.
 (1) (1).png)
Note: if the pool doesn’t exist, create one by clicking the ‘Create pool’ button. You’ll need to provide liquidity in a 50/50 ratio to set the primary price. Pool creation is free, and you can choose the pool fee (more about fees)
2. Create a Position
— On the Pools page, select a pool to view its details: TVL, Volume (24h), Fees (24h), and APR (24h).
— Click “Create position” to add your liquidity.
.png)
3. Set Range and Enter Amounts
.png)
Set a range
— Use the four presets or manually enter minimum and maximum prices according to your strategy.
— A smaller range offers higher yield but higher risk of the price moving outside the selected range.
Note: the price you enter will be rounded to the nearest tick. It is not necessary to enter a round number, as this is a characteristic of how ticks function
When choosing a price range, consider how much you expect the prices to fluctuate while holding your position. Also, think about how much time and effort you’re willing to spend managing your position as market conditions change, and be aware of any costs associated with making adjustments.
Input amounts
— Enter the amount for one asset, and the corresponding amount for the other asset will auto-fill based on your price range relative to the market price.
If your price range is closer to one side of the market price, you’ll provide more of that specific asset. By selecting the Full range button, you can distribute liquidity across the entire range, similar to AMM V2.
— You can allocate liquidity on one side of the market, either above or below the current spot price, depending on your strategy (more about perks for LPs).
4. Approve a Transaction
— Click the ‘Create position’ button and approve the transaction in your wallet.\
.png)
— View the current status of your transaction on the screen.
Once processed, you will see a ‘Success’ message and the page will refresh.
Your assets are now actively contributing liquidity and accruing trading fees.
For more details on claiming fees and removing liquidity, refer to the “Managing a Position” page.
If your funds are stuck when creating a pair and the position hasn’t been added, follow these instructions: Refund Instructions
Managing a Position
You'll be able to view all of your open positions in the "My pools" tab of the Pools Page.
.png)
You can access the following features:
- Collect fees
- Remove liquidity
Click on any open position to view detailed data (total value, tokens deposited, price range, pending fees/rewards, etc.) on the position management panel.
Note: your earned fees may appear on the positions page up to 6 hours after your deposit and it may even take longer depending on the size of your position
.png)
To remove liquidity and burn an LP NFT, select the ‘Remove Liquidity’ tab and choose the percentage of withdrawal.
.png)
Liquidity Migration Guide

Migrate your tsTON/USDT positions from other DEXes by 5th March, 2025 and get a chance to win a share of 1000 USDT.
Details below
Overview
If you’re considering moving your liquidity from other DEXes like DeDust or STONfi to TONCO, our migrator tool simplifies the process. The migrator is designed to help you transfer your liquidity safely and efficiently to TONCO DEX without the hassle of manual processes.
Migrating to TONCO allows liquidity providers to concentrate liquidity within a custom price range, potentially boosting your APR and increasing your earnings
If you have a position that participates in farming, you will be able to claim your rewards and unstake it through our interface.
Step-by-Step Guide
1. See the difference
Open the Migrator page and connect your wallet.
Discover how much more you can earn by concentrating liquidity in price ranges:
.png)
View all your positions and a potential boost (if known) to your current APR if you migrate liquidity to TONCO.
Click “Let’s Migrate” to start the process.
2. Withdraw liquidity from the pool
Withdraw your liquidity from another DEX:

Confirm the withdrawal of your liquidity position using your wallet (requires a blockchain fee).
3. Open a new position on TONCO
Your liquidity has been withdrawn to your wallet. Now you can open a new position on TONCO:

Customize your price range for concentrated liquidity. Learn more about choosing ranges.
Using your wallet, confirm the creation of your new liquidity position (requires a blockchain fee).
An LP NFT representing your ownership of the liquidity position will be sent to your wallet. Learn more about LP NFTs here.
Congrats on Migrating!
View and manage your new liquidity position. For more info, visit "Managing a Position".
💰 Migration Contest
We've partnered with Tonstakers for a tsTON/USDT migration campaign.
💰 Prize pool: 1000 USDT
🏆 30 winners: 33 USDT each
🗓 Dates: Feb 19 – Mar 5, 2025
📢 Results: Announced on Mar 5 on our Telegram channel
Terms:
- 1 wallet = 1 chance to win
- Any position amount is counted as migration (larger amounts boost your chances)
- Liquidity must remain in TONCO for eligibility
- No abuse; violators will be disqualified
How APR is Сalculated
In TONCO’s concentrated liquidity model, each LP position has its own LP fee. The total APR combines the LP fee APR and farming boost (if farming is active).
LP Fee APR
.png)
• dayFees: daily fees in the pool (USD)
• liquidity: total liquidity in the pool
• positionLiquidity: liquidity provided by the position (in range)
• amount0USD: value of token 0 in USD
• amount1USD: value of token 1 in USD
Farming boost
Farming on TONCO acts as a boost to the current pool. If liquidity is out of the selected range and not used, no farming rewards are earned.
 (1).png)
 (1) (1) (1) (1) (1) (1).png)
• farming.rewardRate: The amount of reward token distributed per unit time interval
• INTERVAL: The duration of the interval
• rewardToken.derivedTON: The value of one reward token in terms of GRAM token.
• feeCollectedTotalTON: The total fees collected in the pool during the interval (in GRAM token).
Learn more about Farming on TONCO
LPs FAQ
Where is the yield coming from?
The yield on TONCO pools comes from two main sources:
1. Swap Fees: When users swap tokens within the pool, a fee is collected and distributed to liquidity providers.
2. Farming Rewards: If farming is active, additional rewards are provided to liquidity providers for their contribution.
These combined sources create the total yield displayed for the pool.
Where can I find my position?
You can find your position on the “Pool” page.
Can I provide liquidity over the whole range?
Yes, you can. By providing liquidity across the full range, you will get a significantly lower rate of return than with a similar position with a narrower price range. Conversely, the potential of falling out of range will be lower in this case.
You can provide liquidity across the full range by clicking the Full Range button.
Can I withdraw my liquidity at any time?
Yes, you can burn LP NFT for a proportional share of the pool at any time.
How do I choose a price range for providing liquidity?
Choose based on market outlook and risk tolerance—narrower ranges around current prices yield higher fees but require more adjustments; wider ranges are more passive but may earn fewer fees.
What if my position is out of range?
If the price of the assets you're providing liquidity for goes outside of the range you've specified, then your position will become focused on one asset or the other. You won't earn any trading fees until the price returns to within your specified range.
Where are my LP tokens?
For CLMM pools you won't receive LP tokens per se. Instead, you'll get a position NFT that represents your position in the pool (liquidity and price range). If it is burned or otherwise lost, the associated liquidity cannot be withdrawn or retrieved. It is possible to send or trade a pool position NFTs. However, if sold any associated liquidity is also sold and will no longer be held by the original owner.
What should I do if my funds are stuck when creating a pair?
If your funds are stuck and the position hasn’t been added, please follow the instructions provided in our Refund Instructions document.
Introduction
TONCO POINTS RECAP: ROUND 1 COMPLETE
Season 1 concluded on July 9 at 23:59 (UTC)
No more points will be generated after it ends until the start of a new season.

Our loyalty program was created to reward users for meaningful activity on TONCO—from early testnet support to real onchain actions like swaps and liquidity provision.
Round 1 highlights
👛 13,646 unique wallets
💠 7.1 billion points distributed
🎯 4.2 billion points in the biggest single airdrop
🔁 134,000+ swaps
🌊 8,000 liquidity providers
We’ll soon reveal the rewards for your points 💎
Mechanics
Users can earn TONCO points through key interactions that contribute to the protocol’s growth, such as executing swaps, providing liquidity, and inviting others. The program recognizes and incentivizes meaningful actions that help build the ecosystem, with every activity contributing to your rank on the leaderboard and rewards.
How to Earn Points
1. Retroactive Drop (closed)
Testnet participants will receive points based on their activity, automatically converted and credited within 7 days of TONCO’s mainnet launch (Nov 19).
Activities and Points:
— Logged into Testnet: 5,000 points
— Completed a swap: 1,000 points
— Provided liquidity: 2,500 points
— Claimed farming rewards: 1,500 points
— Submitted feedback: 1,000 points
— Submitted informative feedback: 5,000 points
— Invited a user: 1,000 points per invite
2. Onchain Actions
Swap
Execute any trade on TONCO DEX
Earn 250 points for every $1 of fees paid on the platform
For example, swapping $1,000 with a 0.3% pool fee results in a $3 fee, granting you 750 points. No minimum swap amount is required.
Providing liquidity
Add liquidity to any pool on TONCO DEX
You receive 10 points per day for each $1 of liquidity
For example, adding $1,000 to the USDT/GRAM pool earns you 10,000 points daily.
More tasks are coming…
3. Referral system
Invite users through your unique referral link and earn points.
For each user you refer, you’ll receive 5,000 points
Users who joined TONCO DEX without a referral link can enter one later.
Future updates will include additional point accruals: referrers will earn a percentage of their referrals’ points, while those using a referral code will get a points boost.
4. Ambassador Activity
Extra points for our ambassadors: https://docs.tonco.io/ambassador-program/introduction
📇 Indexer
📇 Indexer
Query Examples
This doc will teach you how to query TONCO analytics by writing GraphQL queries on the subgraph.
Explore
Explorer page: https://indexer.tonco.io
GraphQL endpoint: https://indexer.tonco.io
Global Data
An example querying total pool count, transaction count, and total volume in USD and GRAM for 2 days. Same way you can query historical data.
{
dexData(from: 1733173200000, to: 1733259600000, groupBy: DAY) {
poolCount
txCount
totalVolumeTon
totalVolumeUsd
totalValueLockedUsd
}
}
Pool Data
To get data about a certain pool, pass in the pool address. Reference the full pool schema and adjust the query fields to retrieve the data points you want.
All Possible Pools
The query below returns the fee, spot price and liquidity for all pools
{
pools {
fee
liquidity
priceSqrt
jetton0 {
address
symbol
decimals
}
jetton1 {
address
symbol
decimals
}
}
}
Specific Pool Query
The query below returns the fee, spot price and liquidity for the GRAM-USDT pool.
{
pools (where: { address: "0:f6e6f4ad13073e875413d6a96c60f430a94e4888e868c79208d02ff1b0336931" }) {
fee
liquidity
priceSqrt
jetton0 {
address
symbol
decimals
}
jetton1 {
address
symbol
decimals
}
}
}
Most Liquid Pools
Retrieve the top 10 most liquid pools. You can use this similar set up to orderBy other variables.
{
pools ( filter: { first: 10, orderBy: "liquidity", orderDirection: desc }) {
liquidity
}
}
Swap Data
General Swap Data
To query data about a particular swap, input the transaction hash
{
swaps (where: { hash: "9de6d148d6f6e5479bb50741c401b0f492d378ec798bc97b6ca5140b753c8ab4" } ) {
hash
time
from
to
amount
toRefund0
toRefund1
isZeroToOne
pool {
address
jetton0 {
address
symbol
}
jetton1 {
address
symbol
}
}
}
}
Recent Swaps Within a Pool
You can set the where field to filter swap data by pool address. This example fetches data about multiple swaps for the GRAM-USDT pool, ordered by time.
{
swaps (where: { pool: "0:f6e6f4ad13073e875413d6a96c60f430a94e4888e868c79208d02ff1b0336931" }, filter: { orderBy: "time", orderDirection: desc }) {
hash
time
from
to
amount
toRefund0
toRefund1
isZeroToOne
pool {
address
jetton0 {
address
symbol
}
jetton1 {
address
symbol
}
}
}
}
Jetton Data
Input the jetton contract address to fetch jetton data. Any jetton that exists in at least one TONCO pool can be queried. The output will aggregate data across all pools that include the jetton.
General Jetton Data
This queries the decimals, symbol, name and volume in USD for the USDT token.
{
jettons (where: { address: "0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe" }) {
symbol
name
decimals
volumeUsd
}
}
Jetton Daily Aggregated
You can fetch aggregate data about a specific token over a 24-hour period. This query gets 10-days of the 24-hour volume data for the TONCO token ordered from oldest to newest.
{
jettonData(id: "0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe", from: 1732395600000, to: 1733173200000, groupBy: DAY) {
time
totalValueLocked
}
}
All Jettons
{
jettons {
address
symbol
name
decimals
}
}
Position Data
General Position Data
To get data about a specific position, input the NFT ID in the "tokenId:poolAddress" format. This queries the collected fees for token0 and token1 and current liquidity for the position with tokedId 502 in GRAM-USDT pool.
{
positions (where: { id: "502:0:f6e6f4ad13073e875413d6a96c60f430a94e4888e868c79208d02ff1b0336931" }) {
id
collectedFeesJetton0
collectedFeesJetton1
liquidity
jetton0 {
symbol
decimals
}
jetton1 {
symbol
decimals
}
}
}
GraphQL Schema
Basic Entiies
Jetton Entity
type Jetton {
"Raw address of the jetton on the blockchain"
address: String!
"Bounceable address of the jetton on the blockchain"
bounceableAddress: String!
"Jetton symbol"
symbol: String!
"Jetton name"
name: String!
"Jetton decimals"
decimals: Int!
"Jetton image"
image: String!
"Jetton description"
description: String!
"Jetton wallet"
wallet: String!
"Jetton total supply across all pools that include this token"
totalSupply: Float!
"Transactions across all pools that include this token"
txCount: Int!
"Jetton volume across all pools that include this token in token units"
volume: Float!
"Jetton volume across all pools that include this token in USD"
volumeUsd: Float!
"Jetton fees across all pools that include this token in USD"
feesUsd: Float!
"Jetton TVL across all pools that include this token"
totalValueLocked: Float!
"Jetton TVL across all pools that include this token in USD"
totalValueLockedUsd: Float!
"Jetton price derived in TON"
derivedTon: Float!
"Jetton price derived in USD"
derivedUsd: Float!
}
Pool Entity
type Pool {
"Raw address of the pool on the blockchain"
address: String!
"Pool name"
name: String!
"Pool internal ID"
id: String!
"Pool all time positions count"
positionsCount: Int!
"Pool Jetton 0"
jetton0: Jetton!
"Pool Jetton 1"
jetton1: Jetton!
"Pool creation unix"
creationUnix: Int!
"Pool admin address"
adminAddress: String!
"Pool last update time"
lastUpdateTime: Date!
"Was pool initialized"
isInitialized: Boolean!
"Pool last update unix"
unix: Date!
"Pool address"
poolInfo: String!
"Fee amount, where 1 = 0.001%"
fee: Int!
"In range liquidity"
liquidity: String!
"Current tick"
tick: Int!
"Pool tick spacing"
tickSpacing: Int!
"Pool current tick"
priceSqrt: String!
"Pool current APR"
apr: Float
"Tracker for global fee growth"
feeGrowthGlobal0X128: String!
"Tracker for global fee growth"
feeGrowthGlobal1X128: String!
"Jetton 0 per token1"
jetton0Price: Float!
"Jetton 1 per token0"
jetton1Price: Float!
"All time Jetton 0 swapped"
volumeJetton0: Float!
"All time Jetton 1 swapped"
volumeJetton1: Float!
"All time Jetton 0 fees generated"
feesJetton0: Float!
"All time Jetton 1 fees generated"
feesJetton1: Float!
"All time volume in USD"
volumeUsd: Float!
"All time fees generated in USD"
feesUsd: Float!
"24H Volume"
volume24HUsd: Float!
"24H generated fees"
fees24HUsd: Float!
"48H Volume"
volume48HUsd: Float!
"48H generated fees"
fees48HUsd: Float!
"All time transactions count"
txCount: Int!
"All time Jetton 0 collected fees"
collectedFeesJetton0: Float!
"All time Jetton 1 collected fees"
collectedFeesJetton1: Float!
"All time collected fees in USD"
collectedFeesUsd: Float!
"All time Jetton 0 TVL"
totalValueLockedJetton0: Float!
"All time Jetton 1 TVL"
totalValueLockedJetton1: Float!
"All time TVL in USD"
totalValueLockedUsd: Float!
"All time TVL in Ton"
totalValueLockedTon: Float!
}
Position Entity
type Position {
"Position ID <position index in pool>:<pool adddress>"
id: String!
"Position owner"
owner: String!
"Position pool address"
pool: String!
"Position Jetton 0 entity"
jetton0: Jetton!
"Position Jetton 1 entity"
jetton1: Jetton!
"Position tick lower"
tickLower: Int!
"Position tick upper"
tickUpper: Int!
"Position current liquidity"
liquidity: String!
"Position current Jetton 0 amount"
amount0: String!
"Position current Jetton 1 amount"
amount1: String!
"Position initially deposited Jetton 0"
depositedJetton0: String!
"Position initially deposited Jetton 1"
depositedJetton1: String!
"Position all time withdrawn Jetton 0"
withdrawnJetton0: String!
"Position all time withdrawn Jetton 1"
withdrawnJetton1: String!
"Position all time collected Jetton 0"
collectedJetton0: String!
"Position all time collected Jetton 1"
collectedJetton1: String!
"Position all time collected fees in Jetton 0"
collectedFeesJetton0: String!
"Position all time collected fees in Jetton 1"
collectedFeesJetton1: String!
"Var needed for fee computation"
feeGrowthInside0LastX128: String!
"Var needed for fee computation"
feeGrowthInside1LastX128: String!
"NFT address in blockchain"
nftAddress: String!
"NFT image"
nftImage: String!
"Was position migrated from Stonfi or Dedust or no"
migratedFrom: String
}
Historical Data Entities
Historical DEX Data Entity
type DexData {
"Record time"
time: Date!
"Pool count at given time"
poolCount: Int!
"Transaction count at given time"
txCount: Int!
"All time volume in USD at given time"
totalVolumeUsd: Float!
"All time volume in TON at given time"
totalVolumeTon: Float!
"All time fees in USD at given time"
totalFeesUsd: Float!
"All time fees in TON at given time"
totalFeesTon: Float!
"TVL across all pools in USD at given time"
totalValueLockedUsd: Float!
"TVL across all pools in TON at given time"
totalValueLockedTon: Float!
"TON price in USD at given time"
tonPriceUsd: Float!
}
Historical Jetton Data Entity
type JettonData {
"Record time"
time: Date!
"Jetton address in blockchain"
jettonInfo: String!
"Jetton total supply at given time"
totalSupply: Float!
"Jetton transactions count at given time"
txCount: Int!
"Jetton all time volume in jetton units at given time"
volume: Float!
"Jetton all time volume in USD at given time"
volumeUsd: Float!
"Jetton all time generated fees in USD at given time"
feesUsd: Float!
"Jetton TVL in jetton units at given time"
totalValueLocked: Float!
"Jetton TVL in USD at given time"
totalValueLockedUsd: Float!
"Jetton derived TON at given time"
derivedTon: Float!
"Jetton derived USD at given time"
derivedUsd: Float!
}
Historical Pool Data Entity
type PoolData {
"Record time"
unix: Date!
"Pool address in blockchain"
poolInfo: String!
"Pool internal ID"
id: String!
"Pool positions count at given time"
positionsCount: Int!
"Pool all time fees at given time"
fee: Int!
"Pool in range liquidity at given time"
liquidity: String!
"Pool tick at given time"
tick: Int!
"Pool tick spacing at given time"
tickSpacing: Int!
"Pool price at given time"
priceSqrt: String!
"Var needed for fee computation"
feeGrowthGlobal0X128: String!
"Var needed for fee computation"
feeGrowthGlobal1X128: String!
"Jetton 0 per token1 at given time"
jetton0Price: Float!
"Jetton 1 per token0 at given time"
jetton1Price: Float!
"All time Jetton 0 swapped at given time"
volumeJetton0: Float!
"All time Jetton 1 swapped at given time"
volumeJetton1: Float!
"All time Jetton 0 fees generated at given time"
feesJetton0: Float!
"All time Jetton 1 fees generated at given time"
feesJetton1: Float!
"Pool all time volume in USD at given time"
volumeUsd: Float!
"Pool all time fees generated in USD at given time"
feesUsd: Float!
"Pool all time transacitions count at given time"
txCount: Int!
"All time Jetton 0 collected fees at given time"
collectedFeesJetton0: Float!
"All time Jetton 1 collected fees at given time"
collectedFeesJetton1: Float!
"All time collected fees in USD at given time"
collectedFeesUsd: Float!
"Jetton 0 TVL at given time"
totalValueLockedJetton0: Float!
"Jetton 1 TVL at given time"
totalValueLockedJetton1: Float!
"Pool TVL in USD at given time"
totalValueLockedUsd: Float!
"Pool TVL in TON at given time"
totalValueLockedTon: Float!
"Pool APR at given time"
apr: Float!
}
Historical Position Data Entity
type PositionData {
"Record time"
time: Date!
"Position ID"
positionInfo: Int!
"Position tick lower"
tickLower: Int!
"Position tick upper"
tickUpper: Int!
"Position liquidity at given time"
liquidity: String!
"Position initially deposited jetton 0 amount"
depositedJetton0: String!
"Position initially deposited jetton 1 amount"
depositedJetton1: String!
"Position withdrawn jetton 0 amount at given time"
withdrawnJetton0: String!
"Position withdrawn jetton 1 amount at given time"
withdrawnJetton1: String!
"Position collected jetton 0 amount at given time"
collectedJetton0: String!
"Position collected jetton 1 amount at given time"
collectedJetton1: String!
"Position collected fees jetton 0 amount at given time"
collectedFeesJetton0: String!
"Position collected fees jetton 1 amount at given time"
collectedFeesJetton1: String!
"Var needed for fee computation"
feeGrowthInside0LastX128: String!
"Var needed for fee computation"
feeGrowthInside1LastX128: String!
"Position TVL jetton 0 at given time"
totalValueLockedJetton0: Float!
"Position TVL jetton 1 at given time"
totalValueLockedJetton1: Float!
}
Transaction Entities
Swap Transaction Enitity
type Swap {
"Transaction unix time"
time: Date!
"Transaction hash"
hash: String!
"Swap pool entity"
pool: Pool!
"Swap recipient address"
to: String!
"Swap sender address"
from: String!
"Swap input amount"
amount: Float!
"Swap price value"
sqrtPriceLimitX96: String!
"Jetton 0 entity"
jetton0: Jetton!
"Jetton 0 to refund"
toRefund0: String!
"Jetton 1 entity"
jetton1: Jetton!
"Jetton 1 to refund"
toRefund1: String!
"Is swap being Jetton 0 to Jetton 1 or Jetton 1 to Jetton 0"
isZeroToOne: Boolean!
}
Mint Transaction Entity
type Mint {
"Transaction unix time"
time: Date!
"Transaction hash"
hash: String!
"Mint pool entity"
pool: Pool!
"Mint Jetton 0 amount"
amount0: String!
"Mint Jetton 1 amount"
amount1: String!
"NFT position recipient"
recipient: String!
"Position liquidity"
liquidity: String!
"Position tick lower"
tickLower: Int!
"Position tick upper"
tickUpper: Int!
"Var needed for fee computation"
feeGrowthInside0X128: String!
"Var needed for fee computation"
feeGrowthInside1X128: String!
}
Burn Transaction Entity
type Burn {
"Transaction hash"
hash: String!
"Transaction unix time"
time: Date!
"Burn pool entity"
pool: Pool!
"Burn jettons recipient"
recipient: String!
"Position ID to burn"
positionId: Int!
"Position current liquidity"
liquidity: String!
"Position tick lower"
tickLower: Int!
"Position tick upper"
tickUpper: Int!
"Position liquidity to burn"
liquidity2Burn: String!
"Var needed for fee computation"
feeGrowthInside0LastX128: String!
"Var needed for fee computation"
feeGrowthInside1LastX128: String!
"Burn Jetton 0 amount"
amount0: String!
"Burn Jetton 1 amount"
amount1: String!
}
Collect Transaction Entity
type Collect {
"Transaction hash"
hash: String!
"Transaction unix time"
time: Date!
"Collect pool entity"
pool: Pool!
"Collect jettons recipient"
recipient: String!
"Position ID"
positionId: Int!
"Var needed for fee computation"
feeGrowthInside0LastX128: String!
"Var needed for fee computation"
feeGrowthInside1LastX128: String!
"Collect Jetton 0 amount"
amount0: String!
"Collect Jetton 1 amount"
amount1: String!
}
Integration FAQ
Toncoin → Gram rename. As of June 15, 2026, the native token is displayed as Gram (GRAM) instead of Toncoin (TON). This is a display-only change: same asset, same contracts, same addresses, and 10 TON = 10 GRAM. The TON blockchain itself is not renamed, so network-level identifiers (endpoints, contract addresses, field names such as derivedTON) are unaffected. Do not create new asset listings or pools — existing ones are simply relabeled.
Questions
- What is your infrastructure? Do you have SDK?
- How do you retrieve all the pools you have?
- Now I have a pool, how do I get an APR value?
- How do you retrieve all the positions?
- How do I perform a swap from my code?
- How do I predict the result of the swap?
- How do I mark the swap transactions to be able to trace referrals?
- How do I mint position from my code?
Indexer and SDK
SDK
Github - https://github.com/cryptoalgebra/tonco-sdk/
Examples and references
Github - https://github.com/cryptoalgebra/tonco-demo
Mainnet
- Explorer page: https://indexer.tonco.io
- GraphQL endpoint: https://indexer.tonco.io
- Farming Backend: https://api-farming.tonco.io
- Router Contract - EQC_-t0nCnOFMdp7E7qPxAOCbCWGFz-e3pwxb6tTvFmshjt5
Testnet
- Explorer page: https://testnet-indexer.tonco.io
- GraphQL endpoint: https://testnet-indexer.tonco.io
- Router Contract - EQDnfag9lHlc0rS6YeI7WwRq-3ltcKSsYxLiXmveB7gNUzNO
Retrieve pool data
The best way to get all the pools and general information about them is to use our indexer.
// Some code
import { ApolloClient, InMemoryCache, gql} from "@apollo/client/core";
import { Command, OptionValues } from "commander";
export const POOLS_QUERY = gql`
query PoolsQuery {
pools {
name
address
jetton0 {
address
symbol
decimals
}
jetton1 {
address
symbol
decimals
}
}
}
`;
async function queryPools(options: OptionValues) {
const appoloClient = new ApolloClient({
uri: "https://indexer.tonco.io/",
credentials: 'same-origin',
cache: new InMemoryCache(),
});
const response = await appoloClient.query({ query: POOLS_QUERY });
const appoloPoolList = response.data.pools
console.log(appoloPoolList);
}
Indexer is critical for our system, and we keep it highly available, however, we encourage caching the pool list.
Alternatively, if you don’t want to depend on our infrastructure, you can rescan the blockchain in search of messages POOL_INIT sent by the router
Getting pool APR
For information about the APR of the pool in farming, you need to refer to the URL:
GET api-farming.tonco.io/apr?pool=<pool address>
The answer has “apr” as the base apr and an array of farmings. Farming is considered active if rewardsLeft is not equal to zero. Farming has a property - multiplier, it is a coefficient that denotes how many times farming increases the base apr.
In most cases, a pool has only one active farming, but it is possible that it will have several in the future.
Example
Request:
https://api-farming.tonco.io/apr?pool=EQD25vStEwc-h1QT1qlsYPQwqU5IiOhox5II0C_xsDNpMVo7
Response:
{
"apr": 65.96151567163085,
"farmings": [
{
"pool": "0:f6e6f4ad13073e875413d6a96c60f430a94e4888e868c79208d02ff1b0336931",
"rewardsLeft": "0",
"rewardToken": "0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe",
"rewardRate": "208334",
"id": 5,
"multiplier": 1.3277778973971204
}
]
}
Retrieving positions
There are several ways to get all positions and positions per person.
Getting positions by index
If you want to manually scan and enumerate all the positions for a particular pool you can first use the method getPoolStateAndConfiguration and get "Number of active NFT positions" from it. Then iterate from 0 as the index of NFT and call get_nft_address_by_index()
Getting positions with NFT API's
Position NFT is a real NFT so you can use TonConsole Api and TonCenter API to get NFT address info and metadata. For position parameters, however, you would need to call the NFT get-method - GetPositionInfo()
Getting positions from TONCO indexer
Please address the GraphQL schema documents for more details - GraphQL Schema
Getting collected fees - https://docs.tonco.io/technical-reference/indexer#position-data
Here is a small snippet that uses our indexer\
import { ApolloClient, InMemoryCache, gql} from "@apollo/client/core";
import { Address, TonClient4 } from "@ton/ton";
import { getHttpV4Endpoint } from "@orbs-network/ton-access";
import { PoolV3Contract } from "../wrappers/PoolV3Contract";
export const POSITION_QUERY = gql`
query PositionQuery($where: PositionWhere) {
positions(where: $where) {
id
owner
pool
nftAddress
tickLower
tickUpper
liquidity
feeGrowthInside0LastX128
feeGrowthInside1LastX128
}
}
`;
async function queryPositions(options: OptionValues) {
const appoloClient = new ApolloClient({
uri: "https://indexer.tonco.io/", // Replace with your GraphQL endpoint
credentials: 'same-origin',
cache: new InMemoryCache(),
});
const poolAddress = Address.parse("EQD25vStEwc-h1QT1qlsYPQwqU5IiOhox5II0C_xsDNpMVo7")
const ownerAddress = Address.parse("EQC2nUFN69DWcdgiuvSKXI6P3vHF9Gu_zW3OnQf0s5DgYBmJ")
console.log(poolAddress.toRawString())
console.log(ownerAddress.toString({bounceable: true}))
const response = await appoloClient.query({ query: POSITION_QUERY, variables: {
"where": {
"pool" : poolAddress.toRawString(),
"owner": ownerAddress.toString({bounceable: true})
}
} });
const appoloPositionsList = response.data.positions
const client = new TonClient4({ endpoint : await getHttpV4Endpoint() })
const poolOpened = client.open(new PoolV3Contract(poolAddress))
for (let [i, positionInfo] of appoloPositionsList.entries()) {
console.log(`# ${i} :`);
console.log(positionInfo);
const fees = await poolOpened.getCollectedFees(positionInfo.tickLower, positionInfo.tickUpper, positionInfo.liquidity, positionInfo.feeGrowthInside0LastX128, positionInfo.feeGrowthInside1LastX128)
console.log(` Fees Jetton0 : ${fees.amount0}`)
console.log(` Fees Jetton1 : ${fees.amount1}`)
const reserves = await poolOpened.getMintEstimate(positionInfo.tickLower, positionInfo.tickUpper, positionInfo.liquidity)
console.log(` Reserves Jetton0 : ${reserves.amount0}`)
console.log(` Reserves Jetton1 : ${reserves.amount1}`)
}
}
Retrieving position data using SDK
Below is an example of how to retrieve data for a specific liquidity position using the Position entity from the SDK. The Position instance calculates the current token amounts based on liquidity and price range, while accumulated fees can be fetched directly from the smart contract via the getCollectedFees method.
import { TonClient } from '@ton/ton';
import { Address } from '@ton/core';
import {
Jetton,
Pool,
PoolV3Contract,
Position,
PositionNFTV3Contract,
pTON_MINTER,
} from '@toncodex/sdk';
const client = new TonClient({
endpoint: 'https://toncenter.com/api/v2/jsonRPC',
});
const poolAddress = 'EQD25vStEwc-h1QT1qlsYPQwqU5IiOhox5II0C_xsDNpMVo7'; // GRAM - USDT
const poolContract = client.open(
new PoolV3Contract(Address.parse(poolAddress)),
);
const poolData = await poolContract.getPoolStateAndConfiguration();
const jetton0 = new Jetton(
pTON_MINTER,
9,
'GRAM',
'Gram',
'https://cache.tonapi.io/imgproxy/0boBDKrVQY502vqLLXqwwZTS87PyqSQq0hke-x11lqs/rs:fill:200:200:1/g:no/aHR0cHM6Ly90b25jby5pby9zdGF0aWMvdG9rZW4vVE9OX1RPS0VOLndlYnA.webp',
);
const jetton1 = new Jetton(
'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs',
6,
'USD₮',
'Tether USD',
'https://tether.to/images/logoCircle.png',
);
const pool = new Pool(
jetton0,
jetton1,
poolData.lp_fee_current,
poolData.price_sqrt.toString(),
poolData.liquidity.toString(),
poolData.tick,
poolData.tick_spacing,
);
const positionNFTAddress = 'EQAy5YMXX7e3916Io3Mi9DG3Xf7UAz2bKMMioYCOeYlDm7Ry'; // #3143 LP Position: [ -62160 -> -56100 ]
const positionContract = client.open(
new PositionNFTV3Contract(Address.parse(positionNFTAddress)),
);
const positionInfo = await positionContract.getPositionInfo();
const liquidity = positionInfo.liquidity.toString();
const tickLower = positionInfo.tickLow;
const tickUpper = positionInfo.tickHigh;
const feeGrowthInside0LastX128 = positionInfo.feeGrowthInside0LastX128;
const feeGrowthInside1LastX128 = positionInfo.feeGrowthInside1LastX128;
const position = new Position({
pool, // pool instance
tickLower,
tickUpper,
liquidity,
});
// position amounts
const { amount0, amount1 } = position;
// fee amounts
const { amount0: feeAmount0, amount1: feeAmount1 } =
await poolContract.getCollectedFees(
tickLower,
tickUpper,
BigInt(liquidity),
feeGrowthInside0LastX128,
feeGrowthInside1LastX128,
);
Forming messages for the swap
Sending swap can be done with our SDK - https://github.com/cryptoalgebra/tonco-sdk
An example of swap preparation can be found here - https://github.com/cryptoalgebra/tonco-sdk/blob/36eb0d7feadfcdcd60583ebe4098eb25fcc02591/src/classes/PoolMessageManager.ts#L649
A swap request is created as a payload in the jetton transfer and is sent to the router. The general logic of the input parameters can be derived from the SDK example above and the doc of the pool swap message POOLV3_SWAP
Swap estimate
This question depends on your requirements, limitations, and infrastructure of choice. Here are 4 options
- The simplest and most precise way is to call the pool contract get method - https://docs.tonco.io/technical-reference/contracts/pool#getswapestimategas
- If you have ever implemented integration with other Algebra EVM projects written in your language of choice - then the output simulation can be bit-precise
- You can use our Typescript implementation of user-side swap simulation using TONCO DEX SDK). It includes 2 ways to estimate a swap: on-chain and off-chain. Before actual blockchain execution please check that the estimates you made with TS match the contract call.
a) Estimate Swap 1 GRAM -> USDT Example (on-chain)
import { Address, toNano, TonClient4 } from "@ton/ton";
import { getHttpV4Endpoint } from "@orbs-network/ton-access";
import { getSwapEstimate, PoolV3Contract, pTON_MINTER } from "@toncodex/sdk";
const POOL_ADDRESS = "EQD25vStEwc-h1QT1qlsYPQwqU5IiOhox5II0C_xsDNpMVo7"; // GRAM - USDT
const endpoint = await getHttpV4Endpoint();
const client = new TonClient4({ endpoint });
const poolV3Contract = client.open(new PoolV3Contract(Address.parse(POOL_ADDRESS)));
const inputJettonAddress = Address.parse(pTON_MINTER); // GRAM
const amountIn = toNano(1); // 1 GRAM
/* pool.jetton0_minter and pool.jetton1_minter from poolState are always sorted, so jetton0 is always first */
const { jetton0_minter } = await poolV3Contract.getPoolStateAndConfiguration(); // GRAM
const zeroToOne = inputJettonAddress.equals(jetton0_minter); // true
/* estimate 1 GRAM to USDT swap on-chain */
const result = await getSwapEstimate(amountIn, POOL_ADDRESS, zeroToOne, client);
return result;
b) Estimate Swap 1 GRAM -> USDT Example (off-chain)
import { Address, toNano, TonClient4 } from "@ton/ton";
import { getHttpV4Endpoint } from "@orbs-network/ton-access";
import { PoolV3Contract, pTON_MINTER, SwapSimulator, TickConstructorArgs } from "@toncodex/sdk";
const POOL_ADDRESS = "EQD25vStEwc-h1QT1qlsYPQwqU5IiOhox5II0C_xsDNpMVo7"; // GRAM - USDT
const endpoint = await getHttpV4Endpoint();
const client = new TonClient4({ endpoint });
const poolV3Contract = client.open(new PoolV3Contract(Address.parse(POOL_ADDRESS)));
const inputJettonAddress = Address.parse(pTON_MINTER); // GRAM
const amountIn = toNano(1); // 1 GRAM
const { jetton0_minter, price_sqrt, tick, tick_spacing, lp_fee_current, liquidity } =
await poolV3Contract.getPoolStateAndConfiguration();
/* pool.jetton0_minter and pool.jetton1_minter from poolState are always sorted, so jetton0 is always first */
const zeroToOne = inputJettonAddress.equals(jetton0_minter); // true
const poolTicks = await poolV3Contract.getTickInfosAll();
const tickList: TickConstructorArgs[] = poolTicks.map((tick) => ({
index: tick.tickNum,
liquidityGross: tick.liquidityGross.toString(),
liquidityNet: tick.liquidityNet.toString(),
}));
const swapSimulator = new SwapSimulator(price_sqrt, tick, tick_spacing, liquidity, lp_fee_current, tickList);
/* estimate 1 GRAM to USDT swap off-chain */
const result = await swapSimulator.swapExactIn(zeroToOne, amountIn);
return result;
- You can use our Kotlin implementation of user-side swap simulation.
# Usage example
# Expecting you have JVM and kotlinc
git clone [email protected]:cryptoalgebra/tonco-demo.git
cd tonco-demo
cd swap_kotlin
./run.sh
Marking transactions for referral tracking
With TONCO v1 It is possible to mark transactions to be able to index them for needs of referral tracking
- You have 64 bits of query_id at your disposal. We don't alter it and copy it in the outgoing messages
- A swap request is created as a payload for TRANSFER_NOTIFICATION. The current version of the swap request - Swap Cell Building uses at most 850bits and 1ref. In the current version, all remaining cell part is ignored. However, to be future-proof proof we recommend to occupy second maybe_ref (1 ref and 1 bit). This cell starts with 4 byte of your service id and any content you need as remaining data.
Forming Messages for Mint
Messages can be constructed using our SDK (@toncodex/sdk@mainnet) The source code for message construction can be found here: (https://github.com/cryptoalgebra/tonco-sdk/blob/36eb0d7feadfcdcd60583ebe4098eb25fcc02591/src/classes/PoolMessageManager.ts#L141).
Example: Minting a Position in the GRAM/USDT Pool Below is an example of mint preparation for the GRAM/USDT pool within the price range [3.1, 6.5], based on an input amount of 1 GRAM.
⚠️ Important ⚠️: The validation step inside the example is crucial. Skipping it may result in a loss of funds.
You can find it the code here - https://github.com/cryptoalgebra/tonco-demo/blob/main/sdk_examples/mint/message/createMintMessage.ts
Core Logic
🧺 Pool overview
One pool for each pair
The liquidity pool is a key component of the TONCO protocol. This smart contract implements the most important functions that provide swaps, liquidity management, and other protocol functionality.
Due to the nature of TON, the actual pool contract controls the consistency of the math and data structures for swaps, mints, and burns. Actual funds are stored in the wallets that belong to the router contract. The pool only monitors and updates the reserves that belong to it.
For each pair of tokens(jettons) in TONCO, one unique pool is created. This approach minimizes liquidity fragmentation, simplifies the construction of optimal routes for swaps, and streamlines liquidity management for liquidity providers. (TONCO v1: Due to TON's non-atomic architecture, multihop swaps are not yet supported.)
The address of each pool is calculated deterministically by a mechanism using token(jetton) wallet addresses owned by the router as salt. Generally, this address depends on 5 parameters:
- Jetton0/Jetton1 wallet address
- Router Address
- Subcontracts code
- NFT position contract code
- User account code
- Actual pool code
Liquidity pools are not intended for direct use by ordinary users. For convenient use of the protocol functionality, users use peripheral contracts that implement additional calculations and security checks. Pool won't accept the majority of the operations from any user, except for the operation that allows to redeem NFT and get collected fees (POOL_START_BURN).
Requirements for tokens
The liquidity pool expects the token to adhere to the TEP-74/89 standard. Additionally, there are the following limitations:
- The TONCO does not support tokens that can arbitrarily reduce balances at addresses.
- The TONCO does not guarantee full functionality when using tokens whose total supply exceeds (maximum value uint120).
- The TONCO does not guarantee full performance with tokens that have the ability to self-destruct or use upgradeable code.
Pool Description
The primary purpose of the pool is to enable swaps using concentrated liquidity. Below is a high-level description of the main functionality implemented in the pool.
Token(Jetton) Swap
The main task of AMM is to facilitate token swaps. Swapping in TONCO is performed using concentrated liquidity technology. A more detailed description of the swap process can be found in the article about swap calculations.
Due to the architecture of TON, jettons for the swap and gas are first sent by the user. And after swap computations are finished they receive the swap result, initial jetton change(if any), and any excess gas back. The front-end helps to compute how many jettons and gas should be sent.
Liquidity positions
Users can provide tokens as liquidity within a specific price range. A liquidity position, as long as it is active, receives a portion of the fees that are collected on each swap. For a more detailed description of the logic and calculations see the article on liquidity and liquidity positions
Basic actions with liquidity positions include:
mint- increase liquidity in the position by providing jetton in exchange for NFT. Whoever calls the method must provide jettons to the pool. (v1 TONCO currently doesn't allow adding more jettons to an existing position)
When adding liquidity, the tickspacing restriction is taken into account. You cannot add liquidity to a position if the indices of the corresponding ticks are not divisible bytickSpacing.burn- reduce liquidity in the position. As a result of liquidity reduction the user receives both - a part of the pool reserves and the fees collected by the position since its creation or previous burnburn0, collect- the user receives the jettons recorded as a fee in the corresponding liquidity position.
Pool Customization
Each TONCO AMM pool has several parameters that can be changed by the protocol team, DAO, or authorized persons. Persons authorized to change the pool parameters (each has their own limitations) :
- DEX Administrator (Router administrator)
- Pool Administrator
- Pool Controller
Customizable parameters:
lock/unlock- a way to temporarily stop the mint and swap in the pool. burn/burn0 operations are always availableprotocolFee- the share of the collected fee that is available to the administrator for the needs of the protocol.tickSpacing- a limitation on ticks that can be used as the boundaries of the liquidity position. New liquidity can only be added to positions whose upper and lower ticks are divisible bytickSpacing. For example iftickSpacingis equal to 60, then only every 60th tick (... -60, 0, 60 ...) can be used as a new liquidity position boundary.fee- the commission for swaps in the pool can be adjusted manually or from the authorized backend.
Swap calculation
AMM Base
TONCO AMM is based on the same principles as classical CPF-AMM (e.g., UniswapV2). The internal state of AMM as a system must always satisfy a given invariant, which, in the case of CPF-AMM looks like this:
Where – token (jetton) reserves, - constant. Jettons are conventionally called jetton1 (Y) and jetton0 (X):
slice_hash(jetton0_address) > slice_hash(jetton1_address).
Uniswap V3 transformed the system to a new form. While preserving the basic invariant, two new values are introduced:
is the root of the current price of jetton0 relative to jetton1. In the limit, at infinitesimal values, it reduces to .
is liquidity
At each point in time, the state of the AMM as a dynamic system is described by these two values. Interactions with TONCO AMM during swaps, adding or removing liquidity change the state of the AMM, thus affecting these values. Only one of these values changes at any given time, with the change in the price root and liquidity being related by the following formulas:
We denote the balance change of jetton0 at the pool as , and is the balance change of jetton1 at the pool. Thus, a change in the price root "generates" the movement of jettons in and out of the pool. Swap can be described as a process of "movement" of the price to some value.
However, the peculiarity of TONCO AMM is concentrated liquidity - in the course of price movement liquidity can increase or decrease due to crossing of position boundaries of liquidity providers. For this purpose, a tick mechanism is implemented
Swap as price movement from tick to tick
Main cycle
In each tick is recorded the value of , which should be added/subtracted to the liquidity, depending on the direction in which the tick crosses (left to right or right to left). Due to this, the whole swap process is represented as a price movement towards the next active tick (to the right or left depending on the swap direction - (zeroForOne = true) to the left/down, (zeroForOne = false) to the right/up). If the price reaches an active tick, the current liquidity changes and the movement continues to the next tick:
- Find out the current price ()
- Find out the price on the next active tick ()
- Calculate jetton entry and exit for price movement to ()
- If jetton input/output does not exceed the input conditions, then cross the tick and change the current liquidity value. . Return to step 2.
- If there is unspent jetton input/output left, determine to what price the current price can be moved using the remaining stock.
So as it is possible, the price moves from tick to tick, and then its movement stops somewhere between ticks, depending on the set number of tokens on the input or output of the swap.
Limit on the number of tokens (amountRequired)
The limit on the number of tokens(jettons) at swap can be set in two ways, which are conventionally called exactInput and exactOutput:
exactInput - swap should use no more input jettons than specified.
(due to TON architecture v1 TONCO doesn't support exactOutput - the swap should output no more jettons than specified.)
Restriction on price changes
Additionally, the parameter limitSqrtPrice is taken into account when calculating the swap, which imposes a limit on the possible price movement - if the price reaches this value, the swap is stopped.
This parameter allows simplification of some scenarios of AMM usage, including arbitrage or jetton price adjustment.
Fees calculation
Fees
The protocol is supported by the use of liquidity (jettons), which is provided by liquidity providers. One of the incentives for providing liquidity is to receive fees, which is charged to traders at each swap. To simplify the calculation, fees are always taken in the input jetton.
Fees value is set as a fraction of the sum of jettons at the input of the swap and is a constant during the swap.
At each iteration of the main swap cycle, the price movement is calculated by taking into account the need to use an appropriate share of the input jettons to pay fees to liquidity providers.
The accumulator value of the following form is used to distribute the accumulated fees among active liquidity providers:
Each swap, or rather each iteration within the main swap cycle, increases this accumulator by a new summand. Further, liquidity providers get their share of the collected commissions by multiplying their contribution to liquidity by the corresponding delta of the values of this accumulator (more detailed description in the article about positions and liquidity).
Protocol Fee
The protocol also takes a portion of the fees for itself. This share of fees in TONCO AMM is called Protocol Fee. Protocol fee is calculated as a fraction of also within each iteration of the main swap cycle:
Token(Jetton) Delta Math
This library implements jetton delta calculations using the formulas mentioned above:
Accordingly, X is called jetton0Delta and Y is called jetton1Delta.
To obtain deltas, the formulas are converted to the following form:
Price Movement Math
A key component of this library is the swapInternal method, which provides the parameter values that result from moving the price to a given target under specified conditions.
Given an input/output jetton constraint, target price, current price, commission value, and swap direction, the method determines:
- The price value resulting from the price movement
- Required number of input jettons (including fee)
- Number of output jettons
- Number of jettons collected as fee
💰 Liquidity and positions
Definition of liquidity
As stated in the article about swap calculations, the internal state of the TONCO AMM at any moment is determined by two values:
- the root of the current price of token1(jetton1) relative to token0(jetton0).
- liquidity.
A change in the current price in the pool (via swaps) entails the movement of jettons from / to the pool, with the number of jettons depending on a coefficient called liquidity. Formulas linking token deltas, price change, and liquidity value:
Thus, liquidity can be defined as a coefficient that determines the "speed" of price change when tokens are swapped. Change of (or , depending on the direction of the swap) is inversely proportional to liquidity. This means that the greater the liquidity, the more tokens need to be swapped to move the price by a given value.
Liquidity position
TONCO is based on the concept of concentrated liquidity. This means that users can provide their tokens as liquidity for swaps at a certain price range. The following describes what this means and how the value of liquidity is related to tokens.
A liquidity position in TONCO is an entity defined by the following parameters:
positionv3::user_address- Position ownerpositionv3::pool_address- Pool to which the position belongspositionv3::tickLower- the tick corresponding to the lowest price at which the liquidity of this position can be usedpositionv3::tickUpper- is the tick corresponding to the highest price at which the liquidity of this position can be usedpositionv3::liquidity- - liquidity value associated with this position
The liquidity value associated with the position adds to the global liquidity value when the position becomes active (price inside the specified tick range) and is subtracted from the global liquidity value when the position becomes inactive (price outside the specified tick range). These changes take place during the swap on the crossing of position-related ticks.
Thus, the value must ensure the fulfillment of the formulas from the liquidity definition section on the price range defined by the upper and lower tick of the position. Then the correlation between the number of tokens and can be obtained as follows. Let:
- the price root value corresponding to the upper tick of the position.
- the price root value corresponding to the lower tick of the position.
- the current value of the price root in the pool.
Note that during the upward price movement (zeroToOne = false) the pool buys jetton1 (Y) and sells jetton0 (X). On the other hand, during the downward price movement (zeroToOne = true) the pool buys jetton0 and sells token1.
This means that a liquidity position must, on the one hand, provide enough jetton0 for the sale to move the price up to the , and, on the other hand, provide for sale a sufficient amount of jetton1 to move the price to .
Correlation between the liquidity value and the amount of tokens(jettons)
Then we can express the correlation between the number of jettons and , if is inside the price range of the position:
If is not inside the position's price range, the amount of jettons associated with the position must cover price movement in one direction only (depending on the position of the current price).
If the current price is higher than the upper price of the position ( ):
If the current price is below the price range of the position ( ):
Thus, when creating a position with the given , , , the user must provide jetton0 and jetton1, calculated according to the above formulas taking into account the current price in the pool.
On the other hand, when withdrawing liquidity, the user should receivejetton0 and jetton1, also calculated using the same formulas considering the current price and the change in .
Fee distribution between liquidity positions
For swaps, the pool deducts a fee that is allocated to the currently active liquidity positions (positions whose liquidity is used for the swap).
Two accumulators of the following form are used for this purpose:
where - the collected amount of fees in jetton0 or jetton1, - the current global value of the fee.
During the swap, each iteration of the main loop holds the commission in the input token and increments the value of the corresponding accumulator.
Thanks to this mechanism, it is easy to calculate the share of fees due to each liquidity position. With the help of the ticks mechanism, it is possible to know at any moment what parts of accumulators were added at the moment when the price was between two given ticks. The definition of accumulator increments within the tick range is described in more detail in the article about ticks.
The corresponding values of accumulators increment are recorded in the position when it was created, let's call them:
- accumulator increment for jetton0 that occurred between specified ticks
- accumulator increment for jetton1 that occurred between specified ticks
Then the amount of tokens that correspond to the share of the fees for a liquidity position can be calculated at any time:
This is followed by an update of and
📏 Ticks
Ticks
The entire price space is divided into sections using special cut-offs called ticks.

The ticks are distributed logarithmically: the tick with index i corresponds to the price:
Inside the segment defined by two neighboring ticks (often this segment is also called a tick), the AMM behaves like a regular CPF-AMM (like UniV2). When the price crosses a tick, the value of active liquidity may change (if the boundary of someone's position is crossed).
For this reason, when making swaps, you should be able to find the next active tick (a tick that is the boundary of a position).
Storing ticks in TON Contract
To simplify navigation through ticks during swaps and for other purposes, active ticks are organized in a dict() with 24-bit signed ints used as keys.
Because dict() is internally stored as a tree pool always has quick access to the information about which ticks are currently the next and previous active ticks. So when swapping, it is easy to get information about which tick a particular iteration of the swap is up to.
Determination of fee increment within the range of ticks
Ticks play an important role in allocating the commission between liquidity positions. The accumulator values described in the article on liquidity and positions are used for this purpose:
Two additional values are stored in each tick that correspond to the accumulator increments "outside" the ticks: outerFeeGrowth0Jetton, outerFeeGrowth1Jetton.
These values have a relative character and are set at the moment of tick initialization according to the following rule: it is presumed that the entire "increment" of the commission accumulator occurred below this tick. For this reason, the values are initialized as follows:
If the tick to be initialized is less than or equal to the global tick at the moment:
On the other hand, if the tick to be initialized is above the current global tick , then the corresponding values are initialized to zero:
Later on, at each tick crossing these values are updated according to the following rule:
This ensures that knowing the current global tick, it is possible at any time to determine what jetton increment has occurred "on the other side" since the tick was initialized:

outerFeeGrowth - commission accumulator increment "on the other side" from tick N
At the same time, the pool possesses the accumulator values totalFeeGrowthJetton0 and totalFeeGrowthJetton1, which contains the total commission increment for the entire time of the pool's existence.

totalFeeGrowth - total fee / L growth for the entire pool lifetime
Due to these values, it is easy to calculate the value of the commission increment that occurred within a given range of ticks after their initialization.
If :

innerFeeGrowth - increment of accumulator fee / L from the moment of ticks initialisation
If :
If :
Thus, using the above formulas for innerFeeGrowth it is possible to know the accumulator increment within the range specified by any two active ticks at any time. Distribution of commission among liquidity positions is performed by tracking the change of innerFeeGrowth for a liquidity position:
The corresponding bit at the root is 1 if the corresponding second-level word has at least one active bit.
The maximum allowed tick is 887272
The minimum allowed tick is -887272.
Thus, there can be (887272 + 887272 + 1).
.
🏦 Reserves
Reserves definition
A reserves mechanism was introduced to track increases in pool balances. Reserves reflect the last known pool balances - after the last major pool action has been performed (mint/burn/swap/collect/flash). Immediately after interaction with the pool, under normal conditions, reserves should have the following values:
Synchronization of reserves and balances after interaction with the pool occurs based on the expected change in balances - using the number of tokens sent or requested by the pool. For example, sending tokens to a user () causes the next change in the corresponding reserve:
On the other hand, when is received, the reserve is increased accordingly:
Thus, the value of the reserve at any given moment reflects the number of jettons the pool expects to have on its balance.
Reserves synchronization
At the beginning of the main pool interactions, it is checked whether the pool balances match the expected reserve values. In case of discrepancy, the corresponding jetton surplus (jettonDelta) is calculated and distributed among active liquidity positions as additional fees if the current liquidity is not zero:
If current liquidity is zero (), no synchronization occurs until liquidity becomes non-zero.
Thanks to this mechanism it became possible to support jetton rebase by the TONCO protocol - the pool can detect an increase in jetton balances due to external reasons and distribute the excess jettons among active liquidity providers.
Limits
The reserve value for each of the jettons is limited to the maximum value of the data type uint128: . This restriction allows us to minimize the cost of gas when interacting with the pool.
However, this restriction means that TONCO does not guarantee full pooling performance when using jettons with totalSupply greater than the maximum value of uint120. For most pool interactions, if the pool interaction results in the reserve exceeding the maximum allowed value, the coins would be returned to the user
Pending Protocol Fee
To minimize the number of unnecessary transfers, Protocol Fee is not sent to the Pool Admin at each swap. Instead, the accumulated part of the commissions is recorded in the variables poolv3::collectedProtocolFee{0,1}.
Accumulation and sending of Protocol Fee is taken into account in tracking pool reserves.
Contracts
Scenarios
- Scenarios
Scenarios
Deploy/Init Pool
Pool deployment is triggered by the administrator of the AMM (this person is also the administrator of the router contract - router::admin_address) or by specific roles - pool factory (router::pool_factory_address) and pool admin (router::pool_admin_address). It is done by invoking the operation CREATE_POOL. Pool deployment leads to the processing of the POOLV3_INIT operation inside the pool contract. This operation is used both - during the initial pool deployment and when the pool administrator wants to change some crucial parameters. Pool deployment consists of two stages
I. Forming and sending state_init data that holds
- Router address
- Jetton0/Jetton1 wallet addresses (these are attached to the router)
- Account contract code
- Position NFT contract code
Next newly created pool would only accept the init message from the router and admin(which is set to BLACK_HOLE_ADDRESS in state_init). No operations (except for POOLV3_INIT) would be processed while the administrator address equals BLACK_HOLE_ADDRESS.
This ensures that the only thing that could activate the pool is the init operation sent by the pool
II. state_init message holds as a body POOLV3_INIT operation
This message will be accepted and sets all the data that is needed for pool operation, including an optional flag that would activate the pool and make it available for mints and swaps.
Pool factory is expected to be a contract callable for anyone. It checks on-chain data validity for pool creation. The pool created by pool factory can only have a predefined number of fee and tick spacing settings
Mint
Position minting is done by sending two jettons to the router. Generally, the user calls pool::getMintEstimate to estimate the number of jettons that the person needs to send to mint a particular amount of liquidity in the given price range (tick range). Optionally, the user may send more jettons than needed to account for possible slippage.
While sending both jettons to router wallets, the user sends the payload that contains the position parameters. On receiving the jettons (operation JETTON_TRANSFER_NOTIFICATION ) router would compute the pool address and forward the operation to the pool (POOLV3_FUND_ACCOUNT)
The pool uses a special structure - user Account (Account) to create a barrier that would collect the proof that two jettons are funded and order to mint liquidity and then send it back to the pool for actual mint execution
Swap
Generally user calls pool::getSwapEstimate to estimate the amount of the jettons that he/she needs to send to swap and then sends jettons to the router with a payload describing the future swap. The router forwards the swap to the pool by dynamically computing the pool address.
Burn and Collect
Storage Formats
Price Storage Format and conversion
You may encounter price in a lot of places
- 14th (with index 13) element returned from #getpoolstateandconfiguration
- Limit price for swap
- Initial price for the pool during the creation
It is stored as sqrt of the real price (jetton0 in terms of jetton1) that is packed in 160 bits of Fixed Point Q64.96 (96 bits of the fractional part)
Example how to pack price from virtual reserves.
Same could be used if you have a price and want to pack it.
export function encodePriceSqrt(reserve1: bigint, reserve0: bigint): bigint
{
BigNumber.set({DECIMAL_PLACES: 60})
let result = BigNumber(reserve1.toString())
.div(reserve0.toString())
.sqrt()
.multipliedBy(new BigNumber(2).pow(96))
.integerValue(3);
return BigInt(result.toFixed(0));
}
export function encodePriceSqrt(price: number): bigint
{
BigNumber.set({DECIMAL_PLACES: 60})
let result = BigNumber(price.toString())
.sqrt()
.multipliedBy(new BigNumber(2).pow(96))
.integerValue(3);
return BigInt(result.toFixed(0));
}
Example how to invert price
// Don't use this in production. It's quite imprecise.
export function invertPriceSqrtX96(sqrtPriceX96: bigint): bigint {
return 2n ** (96n+96n) / sqrtPriceX96
}
Unpack the price
// Core to unpack to readable format (note: this loses precision)
export function getApproxFloatPrice(priceSqrt: bigint) : number {
let result = BigNumber(priceSqrt.toString())
.div(BigNumber(2).pow(48))
.pow(2)
.div(BigNumber(2).pow(96))
return Number(result.toPrecision(8));
}
Also note that if you request the price from the contract it there is a special case to handle.
If the price moves outside of all active liquidity ranges it could take values
MIN_SQRT_RATIO = 4295128739
MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342
To check for validity of the price you may check if current liquidity equals to zero. Current liquidity is also returned by #getpoolstateandconfiguration
Payload Formats
Swap Payload
To start the swap you need to send the coins to the router wallets with the payload that describes the swap. Ton and jetton transfer differ, but the payload remains mostly the same. This payload you be passed by JettonWallet or ProxyTon to the router contract.
https://docs.tonco.io/technical-reference/contracts/router#jetton_transfer_notification
The swap payload looks like this:
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| 0 | op | Uint(32),op | POOLV3_SWAP (Opcode : 0xa7fb58f8) |
| 0 | target_wallet | Address(267) | jetton wallet attached to the router. used to identify swap direction |
| 0 | sqrtPriceLimitX96 | Uint(160),PriceX96 | Limit marginal price. Swap won't go beyond it. This is a price in pool terms - so you need to invert it if pool has swapped the jettons order. It is safe to pass 0 here if you don't care about the price movement For more details see - #price-storage-format-and-conversion |
| 0 | minOutAmount | Coins(124) | Minimum amount of the output jettons to get back. If not reached, your input would be returned to you |
| 0 | recipient | Address(267) | The recipient of the swap if it fails (or is payload for mutihop is not present) |
| payloads_cell | MaybeCell(1) | Cell with payloads for swap result and change | |
| 1 | target_address | Address(267) | Target will receive the result of the swap. Could be addr_none() (*00b*) then owner_address is used |
| 1 | ok_forward_amount | Coins(124) | Amount of GRAM to use for forward payload that would be sent with the result of the swap |
| 1 | ok_forward_payload | Cell(0),Payload | Payload that would be sent with the jettons of the result of the swap |
| 1 | ret_forward_amount | Coins(124) | Amount of GRAM to use for forward payload that would be sent with the change for the swap (if any) |
| 1 | ret_forward_payload | Cell(0),Payload | Payload that would be sent with the jettons of the change of the swap (if any) |
Notes
- Note how the role of
owner_addresschanges depending on whether multihop is enabled on the Router or not.
This is needed so that if multihop is disabled asynchronously, there is still a chance that the coins go to the owner. - Depending on which token is used as the intermediate, its recipient may differ.
- For a regular token, the recipient is the wallet owner. For example, if you want to make a swap through USDT, the result of the first swap must be sent back to the Router — so the recipient is the Router.
- For wtPTon (the GRAM wrapper), the recipient is the GRAM wallet address associated with the Router. It directly receives the wrapped GRAM and, without unwrapping, sends a notification about it to the Router.
- For reference: our
proxyTonis exactly the same asproxyTonfrom stonfi, only not packed into a library — so you can peek at its behavior in the source code.
- For reference: our
- Gas.
When the time comes to send coins along two paths — the result and the change — the gas left after processing by the pool and the Router is split into two parts:
ok_forward_amountandret_forward_amount.
If there’s anything left, it is split in half and added to these values before the token is sent.
In other words, to receive both the change and the result without knowing in advance how it will turn out, you need to send extra gas along with the swap message:
ok_forward_amount + ret_forward_amount + 2 * transfer_fee(~0.048?).
Pool
pool
Description
This contract implements V3 like functionality of the pool with concentrated liquidity.
Due to the new storage organization and availability of the dict data type we don't need the tickBitmap data structures. However, gas consumption while using the dict is also quite significant
Data Storage
| Index | Type | Size (b/r) | Cell | Name | Description |
|---|---|---|---|---|---|
| 1 | addr | 267 / 0 | 1 | poolv3::router_address | Address of the router contract that created this pool (totally immutable set at creation ) |
| 2 | uint16 | 16 / 0 | 1 | poolv3::lp_fee_base | Liquidity provider fee. base in FEE_DENOMINATOR parts |
| 3 | uint16 | 16 / 0 | 1 | poolv3::protocol_fee | Protocol fee in FEE_DENOMINATOR |
| 4 | uint16 | 16 / 0 | 1 | poolv3::lp_fee_current | Current value of the pool fee, in case of dynamic adjustment |
| 5 | addr | 267 / 0 | 1 | poolv3::jetton0_wallet | Address of the 0 token in the pool. This is an address of the jetton0 wallet attached to router |
| 6 | addr | 267 / 0 | 1 | poolv3::jetton1_wallet | Address of the 1 token in the pool This is an address of the jetton0 wallet attached to router |
| 7 | int24 | 24 / 0 | 1 | poolv3::tick_spacing | Spacing of the ticks, 24 bits of signed int would be used |
| 8 | uint64 | 64 / 0 | 1 | poolv3::seqno | Used by indexer to ensure that none of pool interactions are skipped |
| 9 | uint256 | 256 / 0 | 11 | poolv3::feeGrowthGlobal0X128 | This variable stores current collected fee per the unit of liquidity in jetton0 |
| 10 | uint256 | 256 / 0 | 11 | poolv3::feeGrowthGlobal1X128 | This variable stores current collected fee per the unit of liquidity in jetton0 |
| 11 | uint128 | 128 / 0 | 11 | poolv3::collectedProtocolFee0 | Collected protocol fee of the jetton0 |
| 12 | uint128 | 128 / 0 | 11 | poolv3::collectedProtocolFee1 | Collected protocol fee of the jetton1 |
| 13 | coins | 124 / 0 | 11 | poolv3::reserve0 | These are additional separate protection system - it calculates the reserves of the pool In case main math has a bug it protects the funds of other pools. Reserve of the jetton0 |
| 14 | coins | 124 / 0 | 11 | poolv3::reserve1 | Reserve of the jetton1 |
| 15 | uint1 | 1 / 0 | 12 | poolv3::pool_active | Pool acitve flag 0 is inactive, 1 is active |
| 16 | int24 | 24 / 0 | 12 | poolv3::tick | Current tick, signed, 24 bits would be used. Pool maintains it in correspondence to poolv3::price_sqrt |
| 17 | uint160 | 160 / 0 | 12 | poolv3::price_sqrt | Current square root of the price in Q64.96 format using 160 bits |
| 18 | uint128 | 128 / 0 | 12 | poolv3::liquidity | Current active concentrated liquidity in 128 bits |
| 19 | uint24 | 24 / 0 | 12 | poolv3::occupied_ticks | number of occupied ticks in storage |
| 20 | uint64 | 64 / 0 | 12 | poolv3::nftv3item_counter | Pool is also an NFT collection. So this is the counter of currently minted positions |
| 21 | uint64 | 64 / 0 | 12 | poolv3::nftv3items_active | Pool is also an NFT collection. Number of unburnt items |
| 22 | addr | 267 / 0 | 12 | poolv3::admin_address | Admin address. Can init pool |
| 23 | addr | 267 / 0 | 12 | poolv3::controller_address | Controller address. Can change fee and lock and unlock pool |
| 24 | addr | 267 / 0 | 121 | poolv3::jetton0_minter | Address of the 0 token minter. Beta only. |
| 25 | addr | 267 / 0 | 121 | poolv3::jetton1_minter | Address of the 1 token minter. Beta only. |
| 26 | dict | 0 / 1 | 13 | poolv3::ticks_dictionary | Storage of the ticks |
| 27 | code | 0 / 1 | 14 | poolv3::accountv3_code | Pool knows how to create user accounts to store fund pairs for a particular user |
| 28 | code | 0 / 1 | 14 | poolv3::position_nftv3_code | Pool knows how to create user position. So it stores it's code |
| 29 | cell | 0 / 1 | 14 | poolv3::nftv3_content | packed metadata that would be given to nft that corresponds to nft collection |
| 30 | cell | 0 / 1 | 14 | poolv3::nftv3item_content | packed metadata that would be given to nft that corresponds to the position |
Cells
| Name | Size | Free |
|---|---|---|
| 1 | 937 | 86 |
| 11 | 1016 | 7 |
| 12 | 999 | 24 |
| 13 | 0 | 1023 |
| 14 | 0 | 1023 |
| 121 | 534 | 489 |
Interface
getIsActive
(int) getIsActive ()
Returns is pool is active
Return Values
| # | Type | Description |
|---|---|---|
| 0 | int | int containing the poolv3::pool_active |
getPoolStateAndConfiguration
(slice, slice, slice, slice, slice, slice, slice, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int ) getPoolStateAndConfiguration ()
Returns pool state and configuration
Return Values
| # | Type | Description |
|---|---|---|
| 0 | slice | Router address |
| 1 | slice | Admin address |
| 2 | slice | Controller address |
| 3 | slice | Jetton 0 Wallet address. The wallet is owned by the Router |
| 4 | slice | Jetton 1 Wallet address. The wallet is owned by the Router |
| 5 | slice | Jetton 0 Minter address. |
| 6 | slice | Jetton 1 Minter address. |
| 7 | int | Flag that denotes if the pool is active |
| 8 | int | Pool tick spacing |
| 9 | int | Fee that is used as a base. Stored in x 1/10000 |
| 10 | int | Fee that protocol takes. Stored in x 1/10000 |
| 11 | int | Fee that is currently active. Stored in x 1/10000 |
| 12 | int | Current tick |
| 13 | int | Current price |
| 14 | int | Current liquidity |
| 15 | int | poolv3::feeGrowthGlobal0X128 |
| 16 | int | poolv3::feeGrowthGlobal1X128 |
| 17 | int | Amount of jetton0 fee collected for protocol owners |
| 18 | int | Amount of jetton0 fee collected for protocol owners |
| 19 | int | Number of total minted NFT positions |
| 20 | int | Reserves of the jetton0 |
| 21 | int | Reserves of the jetton1 |
| 22 | int | Number of active NFT positions |
| 23 | int | Number of currently occupied ticks |
| 24 | int | Number of operations with pool since the deploy |
getChildContracts
(cell, cell, cell, cell) getChildContracts ()
Return Values
| # | Type | Description |
|---|---|---|
| 0 | cell | code of the account contract |
| 1 | cell | code of the nft position contract |
| 2 | cell | metadata for NFT Collection |
| 3 | cell | metadata for NFT Item |
getAllTickInfos
(cell) getAllTickInfos ()
returns the cell with all the ticks.
Return Values
| # | Type | Description |
|---|---|---|
| 0 | cell | cell that contains the dict with all the ticks. |
getTickInfosFrom
(tuple) getTickInfosFrom (int key, int amount, int dir, int full)
Returns ticks starting form key (non-inclusive)
- 0 forward
- 1 backward
Parameters
| # | Type | Name | Description |
|---|---|---|---|
| 0 | int | key | key (tick number) from which to get data |
| 1 | int | amount | amount of ticks to get (large values would fail with out of gas) |
| 2 | int | dir | 0 - for forward search, 1 - for backward |
| 3 | int | full | with true (non zero) would fill fee counter values |
getCollectedFees
(int, int) getCollectedFees (int tickLower, int tickUpper, int posLiquidityDelta, int posFeeGrowthInside0X128, int posFeeGrowthInside1X128)
Predicts how much fees a position can collect
Parameters
| # | Type | Name | Description |
|---|---|---|---|
| 0 | int | tickLower | |
| 1 | int | tickUpper | |
| 2 | int | posLiquidityDelta | |
| 3 | int | posFeeGrowthInside0X128 | |
| 4 | int | posFeeGrowthInside1X128 |
Return Values
| # | Type | Description |
|---|---|---|
| 0 | int | amount of jetton0 that is collected |
| 1 | int | amount of jetton1 that is collected |
getUserAccountAddress
(slice) getUserAccountAddress (slice user_address)
computes user account address for a given user
Parameters
| # | Type | Name | Description |
|---|---|---|---|
| 0 | slice | user_address | address for witch account address is requested |
Return Values
| # | Type | Description |
|---|---|---|
| 0 | slice | account address |
getMintEstimate
(int, int, int) getMintEstimate (int tickLower, int tickUpper, int liquidity)
Computes estimates for the mint
Parameters
| # | Type | Name | Description |
|---|---|---|---|
| 0 | int | tickLower | lower tick for the desired position |
| 1 | int | tickUpper | upper tick for the desired position |
| 2 | int | liquidity | liquidity that is planned to be minted |
Return Values
| # | Type | Description |
|---|---|---|
| 0 | int | amount of jetton0 needed to mint the position |
| 1 | int | amount of jetton1 needed to mint the position |
| 2 | int | error code that shows if the basic checks for the mint would succeed |
getSwapEstimate
(int, int) getSwapEstimate (int zeroForOne, int amount, int sqrtPriceLimitX96)
Deprecated. Use getSwapEstimateGas instead
Computes estimates fot the swap
Parameters
| # | Type | Name | Description |
|---|---|---|---|
| 0 | int | zeroForOne | direction of the swap zeroForOne !=0 - swap jetton0 for jetton1, otherwise jetton1 for jetton0 |
| 1 | int | amount | amount of input token for the swap |
| 2 | int | sqrtPriceLimitX96 | limit price (formatted as fixed point 64.96 square root of the price).If reached - swap will stop |
Return Values
| # | Type | Description |
|---|---|---|
| 0 | int | amount of jetton0 that would be put to/get from the pool |
| 1 | int | amount of jetton1 that would be put to/get from the pool |
getSwapEstimateGas
(int, int) getSwapEstimateGas (int zeroForOne, int amount, int sqrtPriceLimitX96, int minOutAmount, int gasLimit)
Computes estimates for the swap
Parameters
| # | Type | Name | Description |
|---|---|---|---|
| 0 | int | zeroForOne | direction of the swap zeroForOne !=0 - swap jetton0 for jetton1, otherwise jetton1 for jetton0 |
| 1 | int | amount | amount of input token for the swap |
| 2 | int | sqrtPriceLimitX96 | limit price (formatted as fixed point 64.96 square root of the price).If reached - swap will stop (swap would succeed remaining input amount would return as change ) |
| 3 | int | minOutAmount | minimum amount. If return value would be less than this, swap will be reverted |
| 4 | int | gasLimit | amount of gas (in gas units). If gasLimit is 0 - default value is used - that equals to contract gas limit from the config |
Return Values
| # | Type | Description |
|---|---|---|
| 0 | int | amount of jetton0 that would be put to/get from the pool |
| 1 | int | amount of jetton1 that would be put to/get from the pool |
get_collection_data
(int, cell, slice) get_collection_data ()
In accordance with TEP-62
Return Values
| # | Type | Description |
|---|---|---|
| 0 | int | poolv3::nftv3item_counter |
| 1 | cell | poolv3::nftv3_content |
| 2 | slice | poolv3::router_address |
get_nft_address_by_index
(slice) get_nft_address_by_index (int index)
In accordance with TEP-62
Parameters
| # | Type | Name | Description |
|---|---|---|---|
| 0 | int | index | index of the requested NFT (Position NFT) |
Return Values
| # | Type | Description |
|---|---|---|
| 0 | slice | address of the Position NFT |
get_nft_content
(cell) get_nft_content (int index, cell nftv3item_content)
In accordance with TEP-62 we will form the output NFT onchain metadata
If NFTItemContent "description" field that is passed to the pool by the pool administrator (poolv3::nftv3item_content) begins with marker "%N%", data from the NFT is added to the beginning of the "description" field. If there is no data attached to the dictionary by the NFT, NFTItemContent description is unchanged
Parameters
| # | Type | Name | Description |
|---|---|---|---|
| 0 | int | index | index of the requested NFT |
| 1 | cell | nftv3item_content | NFT content returned from the get_nft_data() of the NFT |
Return Values
| # | Type | Description |
|---|---|---|
| 0 | cell | cell with metadata dict() |
Messages
POOLV3_INIT
Opcode : 0x441c39ed
The first mandatory operation that fills crucial parameters of the pool
\
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| from_admin | Uint(1),Bool | Flag that shows if this message goes from router admin or pool factory | |
| has_admin | Uint(1),Bool | Flag that shows if this message have a new admin address | |
| admin_addr | Address(267) | New address of the admin. If has_admin is false could be 00b | |
| has_controller | Uint(1),Bool | Flag that shows if this message have a new controller address | |
| controller_addr | Address(267) | Address that is allowed to change the fee. Can always be updated by admin. If has_controller is false could be 00b | |
| set_spacing | Uint(1),Bool | Flag that shows if tick_spacing should be set to the pool or ignored | |
| tick_spacing | Int(24) | Tick spacing to be used in the pool | |
| set_price | Uint(1),Bool | Flag that shows if initial_priceX96 should be set to the pool or ignored | |
| initial_priceX96 | Uint(160),PriceX96 | Initial price for the pool | |
| set_active | Uint(1),Bool | Flag that shows if pool_active should be set to the pool or ignored | |
| pool_active | Uint(1),Bool | Flag is we should start the pool as unlocked | |
| protocol_fee | Uint(16),Fee | Liquidity provider fee. base in FEE_DENOMINATOR parts. If value is more than 10000 value would be default | |
| lp_fee_base | Uint(16),Fee | Protocol fee in FEE_DENOMINATOR. If value is more than 10000 value would be default | |
| lp_fee_current | Uint(16),Fee | Current value of the pool fee, in case of dynamic adjustment. If value is more than 10000 value would be default | |
| nftv3_content | Cell(0),Metadata | Metadata for the NFT Collection | |
| nftv3item_content | Cell(0),Metadata | Metadata for the NFT Item | |
| 0 | Maybe Cell(1) minter_cell | Cell With Minters | |
| 0 | jetton0_minter | Address(267) | Address of the jetton0 minter, used by indexer and frontend |
| 0 | jetton1_minter | Address(267) | Address of the jetton1 minter, used by indexer and frontend |
TL-B Description (TBD)
This is a preliminary tl-b - subject to change
Tlb for POOLV3_INIT
POOLV3_INIT#441c39ed
query_id:uint64
from_admin:uint1
has_admin:uint1
admin_addr:MsgAddress
has_controller:uint1
controller_addr:MsgAddress
set_spacing:uint1
tick_spacing:int24
set_price:uint1
initial_priceX96:uint160
set_active:uint1
pool_active:uint1
protocol_fee:uint16
lp_fee_base:uint16
lp_fee_current:uint16
nftv3_content:Cell
nftv3item_content:Cell
minter_cell:(Maybe ^[
jetton0_minter:MsgAddress
jetton1_minter:MsgAddress
] )
= ContractMessages;
POOLV3_LOCK
Opcode : 0x5e74697
This operation locks the pool. This is allowed to do by the operator and the admin
\
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation |
TL-B Description (TBD)
This is a preliminary tl-b - subject to change
Tlb for POOLV3_LOCK
POOLV3_LOCK#5e74697
query_id:uint64
= ContractMessages;
POOLV3_UNLOCK
Opcode : 0x3205adbd
This operation locks the pool. This is allowed to do by the operator and the admin
\
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation |
TL-B Description (TBD)
This is a preliminary tl-b - subject to change
Tlb for POOLV3_UNLOCK
POOLV3_UNLOCK#3205adbd
query_id:uint64
= ContractMessages;
POOLV3_MINT
Opcode : 0x81702ef8
\
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| amount0Funded | Coins(124) | Amount of jetton 0 received by router for the mint | |
| amount1Funded | Coins(124) | Amount of jetton 1 recived by router for the mint | |
| recipient | Address(267) | Address that would receive the minted NFT, excesses and refunds | |
| liquidity | Uint(128) | Amount of liquidity to mint | |
| tickLower | Int(24) | lower bound of the range in which to mint | |
| tickUpper | Int(24) | upper bound of the range in which to mint |
TL-B Description (TBD)
This is a preliminary tl-b - subject to change
Tlb for POOLV3_MINT
POOLV3_MINT#81702ef8
query_id:uint64
amount0Funded:(VarUInteger 16)
amount1Funded:(VarUInteger 16)
recipient:MsgAddress
liquidity:uint128
tickLower:int24
tickUpper:int24
= ContractMessages;
POOLV3_BURN
Opcode : 0xd73ac09d
Burn whole or part of nft. Is sent by Position NFT itself, would only be accepted from the correct NFT itself
\
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| recipient | Address(267) | NFT owner to receive funds | |
| burned_index | Uint(64) | Index if the NFT to burn. Should match the sender address | |
| liquidity | Uint(128) | NFT liquidity amount prior to burn | |
| tickLower | Int(24) | Lower tick of the NFT. Sanitized by NFTPosition contract | |
| tickUpper | Int(24) | Upper tick of the NFT. Sanitized by NFTPosition contract | |
| liquidity2Burn | Uint(128) | Amount of the liquidity to burn, 0 is a valid amount, in this case only collected fees would be returned | |
| 0 | Cell(0) old_fee_cell | Fee counters to collect from | |
| 0 | feeGrowthInside0LastX128 | Uint(256),x128 | Fee counter inside position range for jetton0, per unit of liquidity, in 128.128 fixed point |
| 0 | feeGrowthInside1LastX128 | Uint(256),x128 | Fee counter inside position range for jetton1, per unit of liquidity, in 128.128 fixed point |
| 1 | Cell(0) new_fee_cell | Fee counters to collect to (Used by indexer) | |
| 1 | feeGrowthInside0CurrentX128 | Uint(256),x128,Indexer | Fee counter inside position range for jetton0, per unit of liquidity, in 128.128 fixed point |
| 1 | feeGrowthInside1CurrentX128 | Uint(256),x128,Indexer | Fee counter inside position range for jetton1, per unit of liquidity, in 128.128 fixed point |
TL-B Description (TBD)
This is a preliminary tl-b - subject to change
Tlb for POOLV3_BURN
POOLV3_BURN#d73ac09d
query_id:uint64
recipient:MsgAddress
burned_index:uint64
liquidity:uint128
tickLower:int24
tickUpper:int24
liquidity2Burn:uint128
old_fee_cell:^[
feeGrowthInside0LastX128:uint256
feeGrowthInside1LastX128:uint256
]
new_fee_cell:^[
feeGrowthInside0CurrentX128:uint256
feeGrowthInside1CurrentX128:uint256
]
= ContractMessages;
POOLV3_SET_FEE
Opcode : 0x6bdcbeb8
This operation sets the fee values for the pool. This is allowed to do by the operator and the admin
\
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| protocol_fee | Uint(16) | Liquidity provider fee. base in FEE_DENOMINATOR parts | |
| lp_fee_base | Uint(16) | Protocol fee in FEE_DENOMINATOR | |
| lp_fee_current | Uint(16) | Current value of the pool fee, in case of dynamic adjustment |
TL-B Description (TBD)
This is a preliminary tl-b - subject to change
Tlb for POOLV3_SET_FEE
POOLV3_SET_FEE#6bdcbeb8
query_id:uint64
protocol_fee:uint16
lp_fee_base:uint16
lp_fee_current:uint16
= ContractMessages;
POOLV3_FUND_ACCOUNT
Opcode : 0x4468de77
Proxy proof of the jettons funding and mint request to the AccountV3. For more information refer to AccountV3
\
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| owner_addr | Address(267) | Address that would receive the minted NFT, excesses and refunds | |
| amount0 | Coins(124) | Amount of jetton0 that is funded for the mint | |
| amount1 | Coins(124) | Amount of jetton1 that is funded for the mint | |
| enough0 | Coins(124) | Minimum amount of jetton0 totally collected on the account that is required to start the mint | |
| enough1 | Coins(124) | Minimum amount of jetton1 totally collected on the account that is required to start the mint | |
| liquidity | Uint(128) | Amount of liquidity to mint | |
| tickLower | Int(24) | lower bound of the range in which to mint | |
| tickUpper | Int(24) | upper bound of the range in which to mint |
TL-B Description (TBD)
This is a preliminary tl-b - subject to change
Tlb for POOLV3_FUND_ACCOUNT
POOLV3_FUND_ACCOUNT#4468de77
query_id:uint64
owner_addr:MsgAddress
amount0:(VarUInteger 16)
amount1:(VarUInteger 16)
enough0:(VarUInteger 16)
enough1:(VarUInteger 16)
liquidity:uint128
tickLower:int24
tickUpper:int24
= ContractMessages;
POOLV3_START_BURN
Opcode : 0x530b5f2c
Burn whole or part of nft. Can be called by anyone, but if not called be the owner - would fail later. This operation would compute the amount of the fees that the position is eligible to get and then forwards a message to the Position NFT contract
\
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| burned_index | Uint(64) | Index if the NFT to burn | |
| liquidity2Burn | Uint(128) | Amount of the liquidity to burn, 0 is a valid amount, in this case only collected fees would be returned | |
| tickLower | Int(24) | Lower tick of the NFT. Should match the real one | |
| tickUpper | Int(24) | Upper tick of the NFT. Should match the real one |
TL-B Description (TBD)
This is a preliminary tl-b - subject to change
Tlb for POOLV3_START_BURN
POOLV3_START_BURN#530b5f2c
query_id:uint64
burned_index:uint64
liquidity2Burn:uint128
tickLower:int24
tickUpper:int24
= ContractMessages;
POOLV3_SWAP
Opcode : 0xa7fb58f8
Computes the swap math, and issues a command to the router to send funds. Only would be accepted from the router This operation we have several input parameters that would affect the result of the swap
| Condition | Swap result | Returned Change | Error Code |
|---|---|---|---|
| Swap finished sqrtPriceLimitX96 not reached. minOutAmount surpassed | total output number of coins | 0 | RESULT_SWAP_OK |
| Swap finished minOutAmount not surpassed | 0 | amount | RESULT_SWAP_OUTPUT_TOO_SMALL |
| Swap reached sqrtPriceLimitX96 after changing part1 coins. minOutAmount surpassed | output number of coins | amount - part1 | RESULT_SWAP_OK |
Access Rights: This operation is allowed for poolv3::router_address\
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| owner_address | Address(267) | Owner of the liquidity in swap | |
| source_wallet | Address(267) | jetton wallet attached to the router. used to identify swap direction | |
| 0 | Cell(0) params_cell | Fee counters To (Used by indexer) | |
| 0 | amount | Coins(124) | Input amount of the jettons to be swapped |
| 0 | sqrtPriceLimitX96 | Uint(160),PriceX96 | Limit marginal price. Swap won't go beyond it. |
| 0 | minOutAmount | Coins(124) | Minimum amount of the output jettons to get back. If not reached, your input would be returned to you |
| 1 | Cell(0) payloads_cell | Cell with payloads for swap result and change | |
| 1 | target_address | Address(267) | Target will receive the result of the swap. Could be addr_none() (*00b*) then owner_address is used |
| 1 | ok_forward_amount | Coins(124) | Amount of GRAM to use for forward payload that would be sent with the result of the swap |
| 1 | ok_forward_payload | Cell(0),Maybe,Payload | Payload that would be sent with the jettons of the result of the swap |
| 1 | ret_forward_amount | Coins(124) | Amount of GRAM to use for forward payload that would be sent with the change for the swap (if any) |
| 1 | ret_forward_payload | Cell(0),Maybe,Payload | Payload that would be sent with the jettons of the change of the swap (if any) |
TL-B Description (TBD)
This is a preliminary tl-b - subject to change
Tlb for POOLV3_SWAP
POOLV3_SWAP#a7fb58f8
query_id:uint64
owner_address:MsgAddress
source_wallet:MsgAddress
params_cell:^[
amount:(VarUInteger 16)
sqrtPriceLimitX96:uint160
minOutAmount:(VarUInteger 16)
]
payloads_cell:^[
target_address:MsgAddress
ok_forward_amount:(VarUInteger 16)
ok_forward_payload:(Maybe ^Cell)
ret_forward_amount:(VarUInteger 16)
ret_forward_payload:(Maybe ^Cell)
]
= ContractMessages;
Router
router
Description
This contract implements the router and does the management of the pools. Due to the distributed nature of the TON Blockchain router, it can't do many checks, so it's mostly the proxy for the calls. The router contract so far is used as an owner of all the wallets holding the funds invested by liquidity providers.
The main idea is that if malformed or corrupted data is sent to the router it would create a malformed address of the pool and the message sent to it would fail. So if the message reaches the pool it means some criteria are satisfied.
Data Storage
| Index | Type | Size (b/r) | Cell | Name | Description |
|---|---|---|---|---|---|
| 1 | addr | 267 / 0 | 1 | router::admin_address | Admin address. |
| 2 | addr | 267 / 0 | 1 | router::pool_admin_address | Admin address. |
| 3 | addr | 267 / 0 | 1 | router::pool_factory_address | PoolFactory address. Only this address and admin can create new pools |
| 4 | uint64 | 64 / 0 | 1 | router::flags | Flags that control some router features |
| 5 | uint64 | 64 / 0 | 1 | router::pool_seqno | Number of pools created. Used by indexer to ensure that none of pools are skipped |
| 6 | code | 0 / 1 | 11 | router::poolv3_code | The cell with the code of the pool, that is needed to create a pool contract |
| 7 | code | 0 / 1 | 11 | router::accountv3_code | The cell with the code of the account, that is needed to create initial data for pool contract |
| 8 | code | 0 / 1 | 11 | router::position_nftv3_code | The cell with the code of the user NFT position, that is needed to create initial data for pool contract |
| 9 | cell | 0 / 1 | 1 | router::timelocked_updates | This cell holds 3 maybe_refs to cells with timelock data for changeable structures |
Cells
| Name | Size | Free |
|---|---|---|
| 1 | 929 | 94 |
| 11 | 0 | 1023 |
Interface
getRouterState
(slice, slice, slice, int, int) getRouterState ()
returns router admin address
Return Values
| # | Type | Description |
|---|---|---|
| 0 | slice | router admin address (router::admin_address) |
| 1 | slice | default value for pool admin (router::pool_admin_address) |
| 2 | slice | pool factory address (router::pool_factory_address) |
| 3 | int | currently active flags ( 0 for working and active router) |
| 4 | int | router internal number of pool creation or reinit messages |
getPoolAddress
(slice) getPoolAddress (slice jetton_wallet0, slice jetton_wallet1)
returns pool address for two given jetton_wallets belonging to the router
Parameters
| # | Type | Name | Description |
|---|---|---|---|
| 0 | slice | jetton_wallet0 | Address of the jetton 0 wallet belonging to router |
| 1 | slice | jetton_wallet1 | Address of the jetton 1 wallet belonging to router |
Return Values
| # | Type | Description |
|---|---|---|
| 0 | slice | pool address |
getChildContracts
(cell, cell, cell) getChildContracts ()
returns code of the child contracts
Return Values
| # | Type | Description |
|---|---|---|
| 0 | cell | code of the pool contract |
| 1 | cell | code of the account contract |
| 2 | cell | code of the nft position contract |
Messages
JETTON_TRANSFER_NOTIFICATION
Opcode : 0x7362d09c
Process router funding, payload determines if it is mint or swap
\
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| jetton_amount | Coins(124) | Amount of coins sent to the router | |
| from_user | Address(267) | User that originated the transfer | |
| forward_payload | Cell(0),Either, Payload | Payload for processing |
TL-B Description (TBD)
This is a preliminary tl-b - subject to change
Tlb for JETTON_TRANSFER_NOTIFICATION
JETTON_TRANSFER_NOTIFICATION#7362d09c
query_id:uint64
jetton_amount:(VarUInteger 16)
from_user:MsgAddress
forward_payload:(Either ^Cell Cell)
= ContractMessages;
ROUTERV3_CREATE_POOL
Opcode : 0x2e3034ef
Operation that deploys and inits new Pool contract for two given jettons identified by their wallets. New pool would reorder the jettons to match the invariant slice_hash(jetton0_address) > slice_hash(jetton1_address).
\
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| jetton_wallet0 | Address(267) | Address of the jetton0 wallet. Used to compute pool address | |
| jetton_wallet1 | Address(267) | Address of the jetton1 wallet. Used to compute pool address | |
| tick_spacing | Int(24) | Tick spacing to be used in the pool | |
| initial_priceX96 | Uint(160),PriceX96 | Initial price for the pool | |
| protocol_fee | Uint(16),Fee | Liquidity provider fee. base in FEE_DENOMINATOR parts. If value is more than 10000 value would be default | |
| lp_fee_base | Uint(16),Fee | Protocol fee in FEE_DENOMINATOR. If value is more than 10000 value would be default | |
| lp_fee_current | Uint(16),Fee | Current value of the pool fee, in case of dynamic adjustment. If value is more than 10000 value would be default | |
| nftv3_content | Cell(0),Metadata | Metadata for the NFT Collection | |
| nftv3item_content | Cell(0),Metadata | Metadata for the NFT Item | |
| 0 | Cell(0) minter_cell | Cell With Minters | |
| 0 | jetton0_minter | Address(267) | Address of the jetton0 minter, used by indexer and frontend |
| 0 | jetton1_minter | Address(267) | Address of the jetton1 minter, used by indexer and frontend |
| 0 | controller_addr | Address(267) | Address that is allowed to change the fee. Can always be updated by admin. If has_controller is false could be 00b |
TL-B Description (TBD)
This is a preliminary tl-b - subject to change
Tlb for ROUTERV3_CREATE_POOL
ROUTERV3_CREATE_POOL#2e3034ef
query_id:uint64
jetton_wallet0:MsgAddress
jetton_wallet1:MsgAddress
tick_spacing:int24
initial_priceX96:uint160
protocol_fee:uint16
lp_fee_base:uint16
lp_fee_current:uint16
nftv3_content:Cell
nftv3item_content:Cell
minter_cell:^[
jetton0_minter:MsgAddress
jetton1_minter:MsgAddress
controller_addr:MsgAddress
]
= ContractMessages;
ROUTERV3_PAY_TO
Opcode : 0xa1daa96d
This is not a message Op this is a payload format for JETTON_TRANSFER_NOTIFICATION
\
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| reciever0 | Address(267) | Address of the first reciever of the funds | |
| reciever1 | Address(267) | Address of the second reciever of the funds | |
| exit_code | Uint(32) | queryid as of the TON documentation | |
| seqno | Uint(64),Indexer | queryid as of the TON documentation | |
| 0 | Maybe Cell(1) coinsinfo_cell | Cell with info about the coins | |
| 0 | amount0 | Coins(124) | Amount of coins to be payed to reciever0 |
| 0 | jetton0_address | Address(267) | Jetton to be sent to reciever0 identified by the wallet that belongs to router |
| 0 | amount1 | Coins(124) | Amount of coins to be payed to reciever1 |
| 0 | jetton1_address | Address(267) | Jetton to be sent to reciever1 identified by the wallet that belongs to router |
| Predicate | exit_code = 200 | ||
| 1 | Maybe Cell(1) indexer_swap_info_cell | Information for indexer to process after the swap | |
| 1 | liquidity | Uint(128),Indexer | Post-swap concentrated liquidity at current tick |
| 1 | price_sqrt | Uint(160),Indexer,PriceX96 | Post-swap square root of the price stored as fixed point 64.96 |
| 1 | tick | Int(24),Indexer | Post-swap current tick |
| 1 | feeGrowthGlobal0X128 | Int(256),Indexer | Current range fee per unit of the liquidity for jetton0 |
| 1 | feeGrowthGlobal1X128 | Int(256),Indexer | Current range fee per unit of the liquidity for jetton1 |
| Predicate | exit_code = 201 | ||
| 2 | Maybe Cell(1) indexer_burn_info_cell | Information for indexer to process after the burn | |
| 2 | nftIndex | Uint(64),Indexer | Nft index that is burned |
| 2 | liquidityBurned | Uint(128),Indexer | Amount of liquidity burned |
| 2 | tickLower | Int(24),Indexer | Lower tick of the range in which liquidity was burned |
| 2 | tickUpper | Int(24),Indexer | Upper tick of the range in which liquidity was burned |
| 2 | tick | Int(24),Indexer | Post-burn current tick |
TL-B Description (TBD)
This is a preliminary tl-b - subject to change
Tlb for ROUTERV3_PAY_TO
ROUTERV3_PAY_TO#a1daa96d
query_id:uint64
reciever0:MsgAddress
reciever1:MsgAddress
exit_code:uint32
seqno:uint64
coinsinfo_cell:(Maybe ^[
amount0:(VarUInteger 16)
jetton0_address:MsgAddress
amount1:(VarUInteger 16)
jetton1_address:MsgAddress
] )
(exit_code = 200)?(
indexer_swap_info_cell:(Maybe ^[
liquidity:uint128
price_sqrt:uint160
tick:int24
feeGrowthGlobal0X128:int256
feeGrowthGlobal1X128:int256
] )
)
(exit_code = 201)?(
indexer_burn_info_cell:(Maybe ^[
nftIndex:uint64
liquidityBurned:uint128
tickLower:int24
tickUpper:int24
tick:int24
] )
)
= ContractMessages;
ROUTERV3_RESET_GAS
Opcode : 0x42a0fb43
This operation allows router owners the gas if too much accumulated on the contract
Access Rights: This operation is allowed for router::admin_address\
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation |
TL-B Description (TBD)
This is a preliminary tl-b - subject to change
Tlb for ROUTERV3_RESET_GAS
ROUTERV3_RESET_GAS#42a0fb43
query_id:uint64
= ContractMessages;
Position NFT
position_nft
Description
This is a modified NFT contract to store user position. To minimize data, actual content is not stored inside the contract so far kept empty by the pool. It is appended on the fly with nft values. Pool then adds all the other fields to form valid metadata.
Data Storage
| Index | Type | Size (b/r) | Cell | Name | Description |
|---|---|---|---|---|---|
| 1 | uint64 | 64 / 0 | 1 | positionv3::index | The position number. Also the nft index |
| 2 | addr | 267 / 0 | 1 | positionv3::pool_address | Address of the pool that created this NFT |
| 3 | addr | 267 / 0 | 1 | positionv3::user_address | Address of the user ton wallet that currently owns the position |
| 4 | cell | 0 / 1 | 1 | positionv3::content | NFT metadata that contains image url, name and description packed in standard format |
| 5 | uint128 | 128 / 0 | 1 | positionv3::liquidity | Position liquidity |
| 6 | int24 | 24 / 0 | 1 | positionv3::tickLower | Position lower tick number |
| 7 | int24 | 24 / 0 | 1 | positionv3::tickUpper | Position upper tick number |
| 8 | uint256 | 256 / 0 | 11 | positionv3::feeGrowthInside0LastX128 | Fees collected before the position was opened or updated for jetton0 (in pool terms) |
| 9 | uint256 | 256 / 0 | 11 | positionv3::feeGrowthInside1LastX128 | Fees collected before the position was opened or updated for jetton1 (in pool terms) |
Cells
| Name | Size | Free |
|---|---|---|
| 1 | 774 | 249 |
| 11 | 512 | 511 |
Interface
getPoolAddress
(slice) getPoolAddress ()
This function returns pool address that created this Position NFT
Return Values
| # | Type | Description |
|---|---|---|
| 0 | slice | address in question |
getUserAddress
(slice) getUserAddress ()
This function returns user address that owned created this Position NFT
Return Values
| # | Type | Description |
|---|---|---|
| 0 | slice | address in question |
getPositionInfo
(int, int, int, int, int) getPositionInfo ()
This function returns data stored in Position NFT and is related to the position
Return Values
| # | Type | Description |
|---|---|---|
| 0 | int | liquidity that this position owns |
| 1 | int | lower tick of the position |
| 2 | int | upper tick of the position |
| 3 | int | fee growth of jetton0 in the given range at moment of the creation or latest collect of the NFT position |
| 4 | int | fee growth of jetton1 in the given range at moment of the creation or latest collect of the NFT position |
get_nft_data
(int, int, slice, slice, cell) get_nft_data ()
This function returns data of this Position NFT that is related to NFT as TEP-62 It also attaches some values to transfer them to pool
Return Values
| # | Type | Description |
|---|---|---|
| 0 | int | Is position active (in our case, if positionv3::liquidity != 0) |
| 1 | int | positionv3::index |
| 2 | slice | positionv3::pool_address |
| 3 | slice | Owner address (positionv3::user_address) |
| 4 | cell | Content of the NFT. The cell with a dict that has position data appended to the cell for the pool to parse |
Messages
POSITIONNFTV3_POSITION_INIT
Opcode : 0xd5ecca2a
Initial message that pools sends to the NFT after state_init
Access Rights: This operation is allowed for positionv3::pool_address\
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| user_address | Address(267) | NFT owner | |
| liquidity | Uint(128) | Amount of the liquidity | |
| tickLower | Int(24) | Lower tick of the NFT | |
| tickUpper | Int(24) | Upper tick of the NFT | |
| 0 | Cell(0) old_fee_cell | Fee counters From | |
| 0 | feeGrowthInside0LastX128 | Uint(256),x128 | |
| 0 | feeGrowthInside1LastX128 | Uint(256),x128 | |
| 0 | nftIndex | Uint(64),Indexer | |
| 0 | jetton0Amount | Coins(124),Indexer | |
| 0 | jetton1Amount | Coins(124),Indexer | |
| 0 | tick | Int(24),Indexer |
TL-B Description (TBD)
This is a preliminary tl-b - subject to change
Tlb for POSITIONNFTV3_POSITION_INIT
POSITIONNFTV3_POSITION_INIT#d5ecca2a
query_id:uint64
user_address:MsgAddress
liquidity:uint128
tickLower:int24
tickUpper:int24
old_fee_cell:^[
feeGrowthInside0LastX128:uint256
feeGrowthInside1LastX128:uint256
nftIndex:uint64
jetton0Amount:(VarUInteger 16)
jetton1Amount:(VarUInteger 16)
tick:int24
]
= ContractMessages;
POSITIONNFTV3_POSITION_BURN
Opcode : 0x46ca335a
Message from the pool that is part of burn process. This message carries new feeGrowthInside?Last values form the pool
Access Rights: This operation is allowed for positionv3::user_address\
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| nft_owner | Address(267) | NFT owner to receive funds | |
| liquidity2Burn | Uint(128) | Amount of the liquidity to burn, 0 is a valid amount, in this case only collected fees would be returned | |
| tickLower | Int(24) | Lower tick of the NFT. NFT would check that it is the same as in position | |
| tickUpper | Int(24) | Upper tick of the NFT. NFT would check that it is the same as in position | |
| 0 | Cell(0) old_fee_cell | Fee counters From | |
| 0 | feeGrowthInside0LastX128 | Uint(256),x128 | |
| 0 | feeGrowthInside1LastX128 | Uint(256),x128 |
TL-B Description (TBD)
This is a preliminary tl-b - subject to change
Tlb for POSITIONNFTV3_POSITION_BURN
POSITIONNFTV3_POSITION_BURN#46ca335a
query_id:uint64
nft_owner:MsgAddress
liquidity2Burn:uint128
tickLower:int24
tickUpper:int24
old_fee_cell:^[
feeGrowthInside0LastX128:uint256
feeGrowthInside1LastX128:uint256
]
= ContractMessages;
POSITIONNFTV3_NFT_TRANSFER
Opcode : 0x5fcc3d14
Transfer LP NFT to another user. Please be warned that some UI elements could be unable to track it. However with SDK it still can be burned
Access Rights: This operation is allowed for positionv3::user_address\
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| new_owner | Address(267) | New NFT owner | |
| response_destination | Address(267) | Address to receive response | |
| custom_payload | Cell(0),Maybe | Custom information for NFT. Ignored by our implementation | |
| forward_amount | Coins(124) | Amount of coins to forward for processing | |
| forward_payload | Cell(0),Either | Payload for processing |
TL-B Description (TBD)
This is a preliminary tl-b - subject to change
Tlb for POSITIONNFTV3_NFT_TRANSFER
POSITIONNFTV3_NFT_TRANSFER#5fcc3d14
query_id:uint64
new_owner:MsgAddress
response_destination:MsgAddress
custom_payload:(Maybe ^Cell)
forward_amount:(VarUInteger 16)
forward_payload:(Either ^Cell Cell)
= ContractMessages;
Account
account
Description
Account contract. This a contract that stores the funds that the user would use for minting. The account also proxies the minting message and collects proof that both jettons were actually funded correctly
Data Storage
| Index | Type | Size (b/r) | Cell | Name | Description |
|---|---|---|---|---|---|
| 1 | addr | 267 / 0 | 1 | account::user_address | Address of the user ton wallet of the user that owns this two jetton account |
| 2 | addr | 267 / 0 | 1 | account::pool_address | Address of the pool that created this two jetton account |
| 3 | coins | 124 / 0 | 11 | account::amount0 | Amount of jetton0 (in pool terms) currently stored in the account |
| 4 | coins | 124 / 0 | 11 | account::amount1 | Amount of jetton1 (in pool terms) currently stored in the account |
| 5 | coins | 124 / 0 | 11 | account::enough0 | Amount of jetton0 (in pool terms) that is enough to make a mint operation. When reached for both tokens, mint is triggered |
| 6 | coins | 124 / 0 | 11 | account::enough1 | Amount of jetton1 (in pool terms) that is enough to make a mint operation. |
Cells
| Name | Size | Free |
|---|---|---|
| 1 | 534 | 489 |
| 11 | 496 | 527 |
Interface
get_account_data
(slice, slice, int, int, int, int) get_account_data ()
This function provides current state of the user account
Return Values
| # | Type | Description |
|---|---|---|
| 0 | slice | account::user_address Address of the owner of the account |
| 1 | slice | account::pool_address Address of the pool that this account is attached to |
| 2 | int | account::amount0 Amount of jetton0 that was deposited for mint |
| 3 | int | account::amount1 Amount of jetton1 that was deposited for mint |
| 4 | int | account::enough0 Amount of jetton0 that is enough to send message to the pool and actually do the mint |
| 5 | int | account::enough1 Amount of jetton1 that is enough to send message to the pool and actually do the mint |
Messages
ACCOUNTV3_ADD_LIQUIDITY
Opcode : 0x3ebe5431
This operation adds liquidity and a minting request to the account. This contract is used as a barrier to collect together data about the proofs of funding two tokens and the request to mint some liquidity. Common usage is as follows - send one jetton with the mint instructions and the second jetton with the mint instructions. And as soon as they will both arrive AccountV3 would trigger the minting request in the pool. This makes minting independent of the order in which jettons arrive. Account refers to jettons in the pool (account::pool_address) order
Access Rights: This operation is allowed for account::pool_address\
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| new_amount0 | Coins(124) | Amount of jetton0 that is funded for the mint | |
| new_amount1 | Coins(124) | Amount of jetton1 that is funded for the mint | |
| new_enough0 | Coins(124) | Minimum amount of jetton0 totally collected on the account that is required to start the mint | |
| new_enough1 | Coins(124) | Minimum amount of jetton1 totally collected on the account that is required to start the mint | |
| liquidity | Uint(128) | Amount of liquidity to mint | |
| tickLower | Int(24) | lower bound of the range in which to mint | |
| tickUpper | Int(24) | upper bound of the range in which to mint |
TL-B Description (TBD)
This is a preliminary tl-b - subject to change
Tlb for ACCOUNTV3_ADD_LIQUIDITY
ACCOUNTV3_ADD_LIQUIDITY#3ebe5431
query_id:uint64
new_amount0:(VarUInteger 16)
new_amount1:(VarUInteger 16)
new_enough0:(VarUInteger 16)
new_enough1:(VarUInteger 16)
liquidity:uint128
tickLower:int24
tickUpper:int24
= ContractMessages;
ACCOUNTV3_RESET_GAS
Opcode : 0x42a0fb43
This operation allows user to get back the gas it too much was sent
Access Rights: This operation is allowed for account::user_address\
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation |
TL-B Description (TBD)
This is a preliminary tl-b - subject to change
Tlb for ACCOUNTV3_RESET_GAS
ACCOUNTV3_RESET_GAS#42a0fb43
query_id:uint64
= ContractMessages;
ACCOUNTV3_REFUND_ME
Opcode : 0xbf3f447
This operation allows user to get back the coins if sending of the second coin in the mint failed. This method allows to trigger mint of 0 liquidity that Would allow to return funds.
Access Rights: This operation is allowed for account::user_address\
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation |
TL-B Description (TBD)
This is a preliminary tl-b - subject to change
Tlb for ACCOUNTV3_REFUND_ME
ACCOUNTV3_REFUND_ME#bf3f447
query_id:uint64
= ContractMessages;
pool_factory
Description
This contract implements the creation of the pools. And works as a proxy to CREATE_POOL message of the router
Data Storage
| Index | Type | Size (b/r) | Cell | Name | Description |
|---|---|---|---|---|---|
| 1 | addr | 267 / 0 | 1 | pool_factory::admin_address | Admin address. Only this address can change pool creation parameters |
| 2 | addr | 267 / 0 | 1 | pool_factory::router_address | Router address. Address of the pool to witch pool creation request will be forwarded |
| 3 | coins | 124 / 0 | 1 | pool_factory::ton_price | Ton price that is taken form the pool creator to discourage pool creation spam |
| 4 | cell | 0 / 1 | 1 | pool_factory::order_code | Code of pool factory order subcontract. It is used internally and have no user callable methods |
| 5 | cell | 0 / 1 | 1 | pool_factory::nftv3_content | packed metadata that would be given to nft that corresponds to nft collection |
| 6 | cell | 0 / 1 | 1 | pool_factory::nftv3item_content | packed metadata that would be given to nft that corresponds to the position |
Cells
| Name | Size | Free |
|---|---|---|
| 1 | 658 | 365 |
Interface
getPoolFactoryData
(slice, slice, int, cell, cell) getPoolFactoryData ()
Returns current settings of the pool factory
Return Values
| # | Type | Description |
|---|---|---|
| 0 | slice | address containing the pool_factory::admin_address |
| 1 | slice | address containing the pool_factory::router_address |
| 2 | int | int containing the pool_factory::ton_price |
| 3 | cell | int containing the pool_factory::nftv3_content |
| 4 | cell | int containing the pool_factory::nftv3item_content |
getOrderAddress
(slice) getOrderAddress (slice jetton0MinterAddr, slice jetton1MinterAddr)
Returns future order address
Return Values
| # | Type | Description |
|---|---|---|
| 0 | slice | address where pool creation order is/will be deployed |
Messages
POOL_FACTORY_CREATE_POOL
Opcode : 0x9e9a8f7f
Message that initiates pool creation
You need to pass jetton 0/1 masters(minters) of the coins for which to create the pool You also need to provide two wallet address for the jettons, these need to be the wallets belonging to the Router. Also price need to be provided in sqrt Q64.96 format Sqrt Q64.96 Format
Please note that price should be presented in pool internal format. This means that if you have two jettons - jetton0 and jetton1 and price 1 jetton0 = X jetton1, you need to check if the jettons are in pool natural order - slice_hash(jetton0_wallet_address) > slice_hash(jetton1_wallet_address). If it's not true you would need to invert your price. 1 jetton1 = (1/X) jetton0
Please note that pool factory checks for strict master/minter and wallet correspondence. This is done by onchain call to minter with PROVIDE_WALLET_ADDRESS message. If the minter is lacking this method pool creation is impossible
There are 3 settings presets
| NO | fee | tick_spacing |
|---|---|---|
| 1 | 0.01% | 10 |
| 2 | 0.3% | 60 |
| 3 | 1% | 200 |
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| jetton0Minter | Address(267) | Minter address of the first jetton | |
| jetton1Minter | Address(267) | Minter address of the second jetton | |
| initial_priceX96 | Uint(160),PriceX96 | Initial price for the pool | |
| settings | Uint(16) | Value that describes pool configuration preset | |
| 0 | Cell(0) wallet_cell | Cell With Wallets. Currently content is ignored | |
| 0 | jetton0Wallet | Address(267) | Address of the jetton0 wallet of the Router |
| 0 | jetton1Wallet | Address(267) | Address of the jetton1 wallet of the Router |
TL-B Description (TBD)
This is a preliminary tl-b - subject to change **Tlb for POOL_FACTORY_CREATE_POOL**POOL_FACTORY_CREATE_POOL#9e9a8f7f
query_id:uint64
jetton0Minter:MsgAddress
jetton1Minter:MsgAddress
initial_priceX96:uint160
settings:uint16
wallet_cell:^[
jetton0Wallet:MsgAddress
jetton1Wallet:MsgAddress
]
= ContractMessages;
Contracts V1.6 (Forthcoming)
Scenarios
- Scenarios
Scenarios
Deploy/Init Pool (WIP)
Pool deployment is triggered by the administrator of the AMM (this person is also the administrator of the router contract - router::admin_address) or by specific roles - pool factory (router::pool_factory_address) and pool admin (router::pool_admin_address). It is done by invoking the operation CREATE_POOL. Pool deployment leads to the processing of the POOL_INIT operation inside the pool contract. This operation is used both - during the initial pool deployment and when the pool administrator wants to change some crucial parameters. Pool deployment consists of two stages
I. Forming and sending state_init data
The state_init code is not the actual pool code, but a basic immutable pool prototype (router::subcodes.pool_prototype_code). The state_init data holds only the fields that define the pool identity:
- Router address
- Jetton0/Jetton1 wallet addresses (these are attached to the router; they are ordered by address hash, so for any pair of jettons there is exactly one possible pool address)
All other storage fields are set to defaults: the pool is marked as not deployed, and the admin and controller addresses are set to BLACK_HOLE_ADDRESS.
Since the pool address is fully determined by the router address and the two jetton wallets, anyone can compute it off-chain, and the same prototype code can be reused for every pool while the actual pool code stays upgradable at deploy time.
II. The state_init message holds as a body the POOL_INIT operation
This message carries everything the prototype needs to become a working pool:
- The actual Pool contract code — the prototype replaces its own code with it via set_code
- Account contract code
- Position NFT contract code
- Pool parameters: tick spacing, initial price, fees, activity flag
- Pool roles: admin, controller, creator (and, when sent by the router admin, also arbiter, ALM and oracle)
Newly created pool starts in a "not deployed" state: the is_deployed flag in state_init is set to false, and the admin address is set to BLACK_HOLE_ADDRESS. While the pool is not deployed, every operation except POOL_INIT is rejected. POOL_INIT itself is only accepted from the router or from the pool admin. Since the admin in state_init is BLACK_HOLE_ADDRESS (an address nobody controls), the only party that can perform the initial deployment is the router. And because the pool address is derived from the state_init that contains the router address, nobody can deploy a pool at the "canonical" address with a different router or a pre-set admin — any tampering with the initial data changes the resulting address. After the initial deployment, POOL_INIT can be repeated to change crucial parameters, but only when it originates from the router admin or the pool admin: an init message proxied by the router on behalf of the pool factory carries a flag that forbids re-initialization of an already deployed pool.
Pool Factory
Pool factory is expected to be a contract callable for anyone. It checks on-chain data validity for pool creation. The pool created by pool factory can only have a predefined number of fee and tick spacing settings
Pool Settings
When creating a pool through the Pool Factory, the settings field selects one of the predefined fee / tick spacing combinations:
| settings | LP fee | Tick spacing | Fee, % |
|---|---|---|---|
| 1 | 5 | 10 | 0.05% |
| 2 | 30 | 60 | 0.3% |
| 3 | 100 | 200 | 1% |
| any other value | 1 | 1 | 0.01% |
LP fee is expressed in units of 1/10000 (0.01%). The protocol fee is fixed by the factory and cannot be chosen. Pools created through the factory are activated immediately. Custom fee and tick spacing values are only available to addresses whitelisted by the factory administrator.
Jetton Requirements
Permissionless pool creation works with any jetton whose minter supports on-chain wallet discovery (TEP-89):
- The minter must handle
provide_wallet_address(op0x2c76b973) and reply withtake_wallet_address(op0xd1735400).
The factory does not trust jetton wallet addresses supplied by the caller. Instead, it deploys a temporary order contract (one per jetton pair) that queries both jetton minters on-chain for the router's wallet addresses. Only after both minters respond does the factory proceed with pool creation. If a minter does not implement TEP-89, the discovery never completes and the pool cannot be created through the permissionless flow.
No other on-chain methods are required from the jetton.
If your jetton doesn't support TEP-89 please contact the team and we will gladly help you with pool creation
Mint
Position minting is done by sending two jettons to the router. Generally, the user calls pool::getMintEstimate to estimate the number of jettons that the person needs to send to mint a particular amount of liquidity in the given price range (tick range). Optionally, the user may send more jettons than needed to account for possible slippage.
While sending both jettons to router wallets, the user sends the payload that contains the position parameters. On receiving the jettons (operation JETTON_TRANSFER_NOTIFICATION ) router would compute the pool address and forward the operation to the pool (POOL_FUND_ACCOUNT)
The pool uses a special structure - user Account (Account) to create a barrier that would collect the proof that two jettons are funded and order to mint liquidity and then send it back to the pool for actual mint execution
Swap
Generally user calls pool::getSwapEstimate to estimate the amount of the jettons that he/she needs to send to swap and then sends jettons to the router with a payload describing the future swap. The router forwards the swap to the pool by dynamically computing the pool address.
Burn and Collect
There are two ways that can trigger the burn.
Getter
Here is an example of the onchain getter, that is obviously async, but can be useful sometimes
Pool Factory
Description
The Pool Factory is the public entry point for creating pools. It holds a whitelist of privileged creators and the default settings presets, and on a create request it supplements the message with router data (such as the Default Pool Admin) and asks the Router to deploy the pool. Anyone may request a pool: a whitelisted creator can pick its own preset and take the Controller/Creator roles, while a non-whitelisted caller gets the default preset. A pool born through the factory can never be granted the privileged ALM Vault or Arbiter roles. The factory admin manages the whitelist and the factory's own parameters.
Interface
getPoolFactoryData
(address, address, int, cell, cell) getPoolFactoryData ()
This function provides current state of the user account
Return Values
| # | Type | Description |
|---|---|---|
| 0 | address | admin_address Address of the admin of this Pool Factory |
| 1 | address | router_address Address of the router that this Pool Factory is attached to |
| 2 | int | ton_price Amount of ton taken as fee during Pool Creation (so far usually 0) |
| 3 | cell | nftContent Content of Pool that would be presented as Nft Collection |
| 4 | cell | nftItemContent Content of Position NFT that would be presented as NFT |
getWhitelist
(dict) getWhitelist ()
This function provides current whitelist of the wallets that are allowed to get more granular control on creation.
Return Values
| # | Type | Description |
|---|---|---|
| 0 | dict | whitelist Dictionary with whitelist of the Pool Factory |
Messages
POOL_FACTORY_CREATE_POOL
Opcode : 0x9e9a8f7f
Message that initiates pool creation Access Rights: Open to anyone. The factory treats senders differently: a whitelisted address may choose its own pool preset (fee, tick spacing, active flag, NFT content) and become the pool's Controller and Creator, while a non-whitelisted sender gets the default preset with the Default Pool Admin as Controller and Creator. Whoever calls it pays for deployment; the factory then asks the Router to create the pool. A pool created through the factory cannot be granted the ALM Vault or Arbiter roles.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| jetton0Minter | Address(267) | Minter address of the first jetton | |
| jetton1Minter | Address(267) | Minter address of the second jetton | |
| initial_priceX96 | Uint(160),PriceX96 | Initial price for the pool | |
| settings | Uint(16) | Value that describes pool configuration preset | |
| 0 | Cell(0) wallet_cell | Cell With Wallets. | |
| 0 | jetton0Wallet | Address(267) | Address of the jetton0 wallet of the Router |
| 0 | jetton1Wallet | Address(267) | Address of the jetton1 wallet of the Router |
| 1 | Cell(0) settings_cell | Cell With Settings for Whitelisted | |
| 1 | fee | Uint(16) | Custom LP fee in FEE_DENOMINATOR parts (FEE_DENOMINATOR=10000, so 1 = 0.01%). Applied only if the sender is whitelisted with allow_set_settings |
| 1 | tickSpacing | Uint(24) | Custom tick spacing. Applied only if the sender is whitelisted with allow_set_settings |
| 1 | active | Uint(1),Boolean | Whether the new pool starts active (unlocked). Applied only if the sender is whitelisted with allow_set_active |
| 1 | nftContent | Cell(1),Maybe | Custom NFT collection content for the pool. Applied only if the sender is whitelisted with allow_set_content |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for POOL_FACTORY_CREATE_POOL**POOL_FACTORY_CREATE_POOL#9e9a8f7f
query_id:uint64
jetton0Minter:MsgAddress
jetton1Minter:MsgAddress
initial_priceX96:uint160
settings:uint16
wallet_cell:^Wallet_cellType
settings_cell:^Settings_cellType // optional ref (present only if a ref is left)
= ContractMessages;
_
jetton0Wallet:MsgAddress
jetton1Wallet:MsgAddress
= Wallet_cellType;
_
fee:uint16
tickSpacing:uint24
active:uint1
nftContent:(Maybe ^Cell)
= Settings_cellType;
Pool
Description
The Pool is the core AMM contract — a concentrated-liquidity (Uniswap-V3-style) pool with price in Q64.96, tick ranges and per-unit-of-liquidity fee growth. It owns the swap math, the tick dictionary and the reserves, and orchestrates mint/burn through the user Account and Position NFT sub-contracts (whose addresses it re-derives to authorize them). Swaps and fund flows arrive proxied from the Router; funds leave via the router's PAY_TO. Locking a pool blocks mint and swap but never burn.
Interface
getIsActive
(int) getIsActive ()
Returns is pool is active
Return Values
| # | Type | Description |
|---|---|---|
| 0 | int | int containing the pool::pool_active |
getPoolStateAndConfiguration
(slice, slice, slice, slice, slice, slice, slice, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, slice, int, slice, slice, slice, int ) getPoolStateAndConfiguration ()
Returns pool state and configuration
Return Values
| # | Type | Description |
|---|---|---|
| 0 | slice | Router address |
| 1 | slice | Admin address |
| 2 | slice | Controller address |
| 3 | slice | Jetton 0 Wallet address. The wallet is owned by the Router |
| 4 | slice | Jetton 1 Wallet address. The wallet is owned by the Router |
| 5 | slice | Jetton 0 Minter address. |
| 6 | slice | Jetton 1 Minter address. |
| 7 | int | Flag that denotes if the pool is active |
| 8 | int | Pool tick spacing |
| 9 | int | Fee that is used as a base. Stored in x 1/10000 |
| 10 | int | Fee that protocol takes. Stored in x 1/10000 |
| 11 | int | Fee that is currently active. Stored in x 1/10000 |
| 12 | int | Current tick |
| 13 | int | Current price |
| 14 | int | Current liquidity |
| 15 | int | pool::feeGrowthGlobal0X128 |
| 16 | int | pool::feeGrowthGlobal1X128 |
| 17 | int | Amount of jetton0 fee collected for protocol owners |
| 18 | int | Amount of jetton1 fee collected for protocol owners |
| 19 | int | Number of total minted NFT positions |
| 20 | int | Reserves of the jetton0 |
| 21 | int | Reserves of the jetton1 |
| 22 | int | Number of active NFT positions |
| 23 | int | Number of currently occupied ticks |
| 24 | int | Number of operations with pool since the deploy |
| 25 | slice | Arbiter address |
| 26 | int | Pool version (e.g. 16000 = v1.6.000) |
| 27 | slice | ALM address |
| 28 | slice | Creator address |
| 29 | slice | Oracle address |
| 30 | int | Pool flags |
getChildContracts
(cell, cell, cell, cell) getChildContracts ()
returns the subcontract codes and NFT metadata used by the pool
Return Values
| # | Type | Description |
|---|---|---|
| 0 | cell | code of the account contract |
| 1 | cell | code of the nft position contract |
| 2 | cell | metadata for NFT Collection |
| 3 | cell | metadata for NFT Item |
getAllTickInfos
(cell) getAllTickInfos ()
returns the cell with all the ticks.
Return Values
| # | Type | Description |
|---|---|---|
| 1 | cell | cell that contains the dict with all the ticks. |
getTickInfosFrom
(tuple) getTickInfosFrom (int key, int amount, int dir, int full)
Returns ticks starting form key (non-inclusive) 0 forward 1 backward
Parameters
| # | Type | Name | Description |
|---|---|---|---|
| 0 | int | key | |
| 1 | int | amount | |
| 2 | int | dir | |
| 3 | int | full |
Return Values
| # | Type | Description |
|---|---|---|
| 0 | tuple | a tuple with keys |
getFeeGrowthInside
(int, int) getFeeGrowthInside (int tickLower, int tickUpper, int posLiquidityDelta, int feeGrowthGlobal0X128, int feeGrowthGlobal1X128)
Helper method to estimate fee growth inside a given position
Parameters
| # | Type | Name | Description |
|---|---|---|---|
| 0 | int | tickLower | |
| 1 | int | tickUpper | |
| 2 | int | posLiquidityDelta | |
| 3 | int | feeGrowthGlobal0X128 | |
| 4 | int | feeGrowthGlobal1X128 |
Return Values
| # | Type | Description |
|---|---|---|
| 0 | int | amount of jetton0 that is collected |
| 1 | int | amount of jetton1 that is collected |
getCollectedFees
(int, int) getCollectedFees (int tickLower, int tickUpper, int posLiquidityDelta, int posFeeGrowthInside0X128, int posFeeGrowthInside1X128)
Predicts how much fees a position can collect
Parameters
| # | Type | Name | Description |
|---|---|---|---|
| 0 | int | tickLower | |
| 1 | int | tickUpper | |
| 2 | int | posLiquidityDelta | |
| 3 | int | posFeeGrowthInside0X128 | |
| 4 | int | posFeeGrowthInside1X128 |
Return Values
| # | Type | Description |
|---|---|---|
| 0 | int | amount of jetton0 that is collected |
| 1 | int | amount of jetton1 that is collected |
getUserAccountAddress
(slice) getUserAccountAddress (slice user_address)
computes user account address for a given user
Parameters
| # | Type | Name | Description |
|---|---|---|---|
| 0 | slice | user_address | Address of the user to compute the account for |
Return Values
| # | Type | Description |
|---|---|---|
| 0 | slice | account address |
getMintEstimate
(int, int, int) getMintEstimate (int tickLower, int tickUpper, int liquidity)
Computes estimates for the mint
Parameters
| # | Type | Name | Description |
|---|---|---|---|
| 0 | int | tickLower | |
| 1 | int | tickUpper | |
| 2 | int | liquidity |
Return Values
| # | Type | Description |
|---|---|---|
| 0 | int | amount of jetton0 needed to mint the position |
| 1 | int | amount of jetton1 needed to mint the position |
| 2 | int | error code that shows if the basic checks for the mint would succeed |
getSwapEstimate
(int, int) getSwapEstimate (int zeroForOne, int amount, int sqrtPriceLimitX96)
Deprecated Computes estimates fot the swap
Parameters
| # | Type | Name | Description |
|---|---|---|---|
| 0 | int | zeroForOne | |
| 1 | int | amount | |
| 2 | int | sqrtPriceLimitX96 |
Return Values
| # | Type | Description |
|---|---|---|
| 0 | int | amount of jetton0 that would be put to/get from the pool |
| 1 | int | amount of jetton1 that would be put to/get from the pool |
getSwapEstimateGas
(int, int) getSwapEstimateGas (int zeroForOne, int amount, int sqrtPriceLimitX96, int minOutAmount, int gasLimit)
Computes estimates for the swap
Parameters
| # | Type | Name | Description |
|---|---|---|---|
| 0 | int | zeroForOne | |
| 1 | int | amount | |
| 2 | int | sqrtPriceLimitX96 | |
| 3 | int | minOutAmount | |
| 4 | int | gasLimit | amount of gas (in gas units). If gasLimit is 0 - default value is used - that equals to contract gas limit from the config |
Return Values
| # | Type | Description |
|---|---|---|
| 0 | int | amount of jetton0 that would be put to/get from the pool |
| 1 | int | amount of jetton1 that would be put to/get from the pool |
get_collection_data
(int, cell, slice) get_collection_data ()
In accordance with TEP-62
Return Values
| # | Type | Description |
|---|---|---|
| 0 | int | |
| 1 | cell | |
| 2 | slice |
get_nft_address_by_index
(slice) get_nft_address_by_index (int index)
In accordance with TEP-62
Return Values
| # | Type | Description |
|---|---|---|
| 0 | slice |
get_nft_content
(cell) get_nft_content (int index, cell nftitem_content)
In accordance with TEP-62 we will form the output NFT onchain metadata
If NFTItemContent "description" field that is passed to the pool by the pool administrator (pool::nftv3item_content) begins with marker "%N%", data from the NFT is added to the beginning of the "description" field. If there is no data attached to the dictionary by the NFT, NFTItemContent description is unchanged
Return Values
| # | Type | Description |
|---|---|---|
| 0 | cell |
get_pending_timelocks
(int, int, cell, int, slice, int, slice, int, int ) get_pending_timelocks ()
Get timelocked changes pending to be applied. Timestamp 0 means - never/timelock not active
Return Values
| # | Type | Description |
|---|---|---|
| 0 | int | Timelock delay |
| 1 | int | Timestamp when new code can be committed |
| 2 | cell | Cell with new code |
| 3 | int | Timestamp when new ALM address can be committed |
| 4 | slice | New ALM address |
| 5 | int | Timestamp when new Arbiter address can be committed |
| 6 | slice | New Arbiter address |
| 7 | int | Timestamp when new flags can be committed |
| 8 | int | New flags |
Messages
POOL_INIT
Opcode : 0x441c39ed
The first mandatory operation that fills crucial parameters of the pool Access Rights: Accepted only from the Router or the Pool Admin. The Router forwards it on behalf of the Router Admin (and, indirectly, the Pool Factory), which is the only way to (re)initialize the pool; re-init on an already-deployed pool is rejected unless it comes from the Pool Admin. A request coming through the Pool Factory cannot assign the ALM Vault or Arbiter roles.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| from_admin | Uint(1),Bool | Flag that shows if this message goes from router admin or pool factory | |
| 0 | Cell(0) roles_cell | Cell With Role | |
| 0 | has_admin | Uint(1),Bool | Flag that shows if this message have a new admin address |
| 0 | admin_addr | Address(267) | New address of the admin. If has_admin is false could be 00b |
| 0 | has_controller | Uint(1),Bool | Flag that shows if this message have a new controller address |
| 0 | controller_addr | Address(267) | Address that is allowed to change the fee. Can always be updated by admin. |
| 0 | has_creator | Uint(1),Bool | Flag that shows if this message have a new creator address |
| 0 | creator_addr | Address(267) | Address that is allowed to change the fee. Can always be updated by admin. If has_controller is false could be 00b |
| 0 | 0 | Cell(0) privilege_roles_cell | Cell With Privilege Roles |
| 0->0 | has_arbiter | Uint(1),Bool | Flag that shows if this message have a new Arbiter address |
| 0->0 | arbiter_addr | Address(267) | Address that is allowed to make privileged swaps |
| 0->0 | has_alm | Uint(1),Bool | Flag that shows if this message have a new ALM address |
| 0->0 | alm_addr | Address(267) | Address that is used to identify ALM contract |
| 0 | 1 | Cell(0) roles_add | Cell With Additional Roles |
| 0->1 | has_oracle | Uint(1),Bool | Flag that shows if this message have a new oracle address |
| 0->1 | oracle_addr | Address(267) | Address that is allowed to change the fee. Can always be updated by admin. Could be 00b |
| set_spacing | Uint(1),Bool | Flag that shows if tick_spacing should be set to the pool or ignored | |
| tick_spacing | Uint(24) | Tick spacing to be used in the pool | |
| set_price | Uint(1),Bool | Flag that shows if initial_priceX96 should be set to the pool or ignored | |
| initial_priceX96 | Uint(160),PriceX96 | Initial price for the pool | |
| set_active | Uint(1),Bool | Flag that shows if pool_active should be set to the pool or ignored | |
| pool_active | Uint(1),Bool | Flag is we should start the pool as unlocked | |
| protocol_fee | Uint(16),Fee | Protocol fee in FEE_DENOMINATOR parts. If value is more than 10000 value would be default | |
| lp_fee_base | Uint(16),Fee | Liquidity provider base fee in FEE_DENOMINATOR. If value is more than 10000 value would be default | |
| lp_fee_current | Uint(16),Fee | Current value of the pool fee, in case of dynamic adjustment. If value is more than 10000 value would be default | |
| set_flags | Uint(1),Bool | Is initial flags valid and need to be apply | |
| initial_flags | Uint(32),PoolFlags | Top 32bits Flag that pool should use | |
| 1 | Cell(0) code_cell | Cell With Contract Codes | |
| 1 | pool_code | Cell(0),Code | Pool code |
| 1 | account_code | Cell(0),Code | Account code |
| 1 | position_code | Cell(0),Code | Position code |
| 2 | Cell(0) content_cell | Cell With Content | |
| 2 | nft_content | Cell(0),Metadata | Metadata for the NFT Collection |
| 2 | nft_item_content | Cell(0),Metadata | Metadata for the NFT Item |
| 3 | Maybe Cell(1) minter_cell | Cell With Minters | |
| 3 | jetton0_minter | Address(267) | Address of the jetton0 minter, used by indexer and frontend |
| 3 | jetton1_minter | Address(267) | Address of the jetton1 minter, used by indexer and frontend |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for POOL_INIT**POOL_INIT#441c39ed
query_id:uint64
from_admin:uint1
roles_cell:^Roles_cellType
set_spacing:uint1
tick_spacing:uint24
set_price:uint1
initial_priceX96:uint160
set_active:uint1
pool_active:uint1
protocol_fee:uint16
lp_fee_base:uint16
lp_fee_current:uint16
set_flags:uint1
initial_flags:uint32
code_cell:^Code_cellType
content_cell:^Content_cellType
minter_cell:(Maybe ^Minter_cellType)
= ContractMessages;
_
has_arbiter:uint1
arbiter_addr:MsgAddress
has_alm:uint1
alm_addr:MsgAddress
= Privilege_roles_cellType;
_
has_oracle:uint1
oracle_addr:MsgAddress
= Roles_addType;
_
has_admin:uint1
admin_addr:MsgAddress
has_controller:uint1
controller_addr:MsgAddress
has_creator:uint1
creator_addr:MsgAddress
privilege_roles_cell:^Privilege_roles_cellType
roles_add:^Roles_addType
= Roles_cellType;
_
pool_code:^Cell
account_code:^Cell
position_code:^Cell
= Code_cellType;
_
nft_content:^Cell
nft_item_content:^Cell
= Content_cellType;
_
jetton0_minter:MsgAddress
jetton1_minter:MsgAddress
= Minter_cellType;
POOL_LOCK
Opcode : 0x5e74697
This operation locks the pool. While locked, only mint and swap are blocked — users can still burn their positions. Access Rights: Allowed for the Pool Admin or the Controller (the Controller uses this as a lightweight lock method).
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for POOL_LOCK**POOL_LOCK#5e74697
query_id:uint64
= ContractMessages;
POOL_UNLOCK
Opcode : 0x3205adbd
This operation unlocks the pool, re-enabling mint and swap. Access Rights: Allowed for the Pool Admin or the Controller (the Controller uses this as a lightweight unlock method).
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for POOL_UNLOCK**POOL_UNLOCK#3205adbd
query_id:uint64
= ContractMessages;
POOL_SET_FEE
Opcode : 0x6bdcbeb8
This operation sets the fee values for the pool. This is allowed to do by the operator and the admin Access Rights: Allowed for the Pool Admin or the Controller. Sets both the pool fee and the protocol fee.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| protocol_fee | Uint(16) | Protocol fee in FEE_DENOMINATOR parts | |
| lp_fee_base | Uint(16) | Liquidity provider base fee in FEE_DENOMINATOR | |
| lp_fee_current | Uint(16) | Current value of the pool fee, in case of dynamic adjustment |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for POOL_SET_FEE**POOL_SET_FEE#6bdcbeb8
query_id:uint64
protocol_fee:uint16
lp_fee_base:uint16
lp_fee_current:uint16
= ContractMessages;
POOL_FUND_ACCOUNT
Opcode : 0x4468de77
Proxy proof of the jettons funding and mint request to the Account. For more information refer to Account Access Rights: Accepted only from the Router (which relays the user's verified jetton deposit). The pool then credits the user's per-pool Account, whose address is derived from the user, so funds can only reach the rightful owner's Account.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| owner_addr | Address(267) | Address that would receive the minted NFT, excesses and refunds | |
| initiator_addr | Address(267) | Address that started funding sending | |
| 0 | Cell(0) funds_cell | Cell with parameters | |
| 0 | fundFirst | Uint(1),Boolean | If the amount the jetton0 in the pool |
| 0 | amount0 | Coins(124) | Amount of jetton that is funded for the mint |
| 0 | amount1 | Coins(124) | Amount of the other jetton that is funded for the mint (unused) |
| 0 | inputWallet | Address(267) | Address that would be user for sending refund in case of the bounce |
| 1 | Maybe Cell(1) order_cell | Cell with order | |
| 1 | enough0 | Coins(124) | When temporary storage (user account) satisfy both enough0/1 amounts - mint would trigger |
| 1 | enough1 | Coins(124) | When temporary storage (user account) satisfy both enough0/1 amounts - mint would trigger |
| 1 | needPos | Uint(64) | Amount of positions that are enough for the order to be executed |
| 1 | passthrough | Uint(4) | Amount of positions that will be untouched |
| 1 | target_action | Uint(32) | Routing of the reforge result (ACTION_TARGET_DEFAULT / ACTION_TARGET_ALM) — see target_action in ACCOUNT_SET_ORDER for details |
| 1 | 0 | Maybe Cell(1) MintOrder0 | Cell with mint order |
| 1->0 | op0 | Uint(32) | Mint mode (MINT_NOT_LESS / MINT_AS_MUCH_AS_POSSIBLE) — see mintOp in ACCOUNT_SET_ORDER for details |
| 1->0 | liquidity0 | Uint(128) | Amount of liquidity to mint |
| 1->0 | tickLower0 | Int(24) | lower bound of the range in which to mint |
| 1->0 | tickUpper0 | Int(24) | upper bound of the range in which to mint |
| 1->0 | receiver0 | Address(267) | Address of receiver of the NFT |
| 1 | 1 | Maybe Cell(1) MintOrder1 | Cell with mint order |
| 1->1 | op1 | Uint(32) | Mint mode (MINT_NOT_LESS / MINT_AS_MUCH_AS_POSSIBLE) — see mintOp in ACCOUNT_SET_ORDER for details |
| 1->1 | liquidity1 | Uint(128) | Amount of liquidity to mint |
| 1->1 | tickLower1 | Int(24) | lower bound of the range in which to mint |
| 1->1 | tickUpper1 | Int(24) | upper bound of the range in which to mint |
| 1->1 | receiver1 | Address(267) | Address of receiver of the NFT |
| 1 | 2 | Maybe Cell(1) MintOrder2 | Cell with mint order |
| 1->2 | op2 | Uint(32) | Mint mode (MINT_NOT_LESS / MINT_AS_MUCH_AS_POSSIBLE) — see mintOp in ACCOUNT_SET_ORDER for details |
| 1->2 | liquidity2 | Uint(128) | Amount of liquidity to mint |
| 1->2 | tickLower2 | Int(24) | lower bound of the range in which to mint |
| 1->2 | tickUpper2 | Int(24) | upper bound of the range in which to mint |
| 1->2 | receiver2 | Address(267) | Address of receiver of the NFT |
| 1 | 3 | Maybe Cell(1) MintOrder3 | Cell with mint order |
| 1->3 | op3 | Uint(32) | Mint mode (MINT_NOT_LESS / MINT_AS_MUCH_AS_POSSIBLE) — see mintOp in ACCOUNT_SET_ORDER for details |
| 1->3 | liquidity3 | Uint(128) | Amount of liquidity to mint |
| 1->3 | tickLower3 | Int(24) | lower bound of the range in which to mint |
| 1->3 | tickUpper3 | Int(24) | upper bound of the range in which to mint |
| 1->3 | receiver3 | Address(267) | Address of receiver of the NFT |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for POOL_FUND_ACCOUNT**POOL_FUND_ACCOUNT#4468de77
query_id:uint64
owner_addr:MsgAddress
initiator_addr:MsgAddress
funds_cell:^Funds_cellType
order_cell:(Maybe ^Order_cellType)
= ContractMessages;
_
fundFirst:uint1
amount0:(VarUInteger 16)
amount1:(VarUInteger 16)
inputWallet:MsgAddress
= Funds_cellType;
_
op0:uint32
liquidity0:uint128
tickLower0:int24
tickUpper0:int24
receiver0:MsgAddress
= MintOrder0Type;
_
op1:uint32
liquidity1:uint128
tickLower1:int24
tickUpper1:int24
receiver1:MsgAddress
= MintOrder1Type;
_
op2:uint32
liquidity2:uint128
tickLower2:int24
tickUpper2:int24
receiver2:MsgAddress
= MintOrder2Type;
_
op3:uint32
liquidity3:uint128
tickLower3:int24
tickUpper3:int24
receiver3:MsgAddress
= MintOrder3Type;
_
enough0:(VarUInteger 16)
enough1:(VarUInteger 16)
needPos:uint64
passthrough:uint4
target_action:uint32
MintOrder0:(Maybe ^MintOrder0Type)
MintOrder1:(Maybe ^MintOrder1Type)
MintOrder2:(Maybe ^MintOrder2Type)
MintOrder3:(Maybe ^MintOrder3Type)
= Order_cellType;
POOL_START_BURN
Opcode : 0x530b5f2c
Burn whole or part of nft. Can be called by anyone, but if not called be the owner - would fail later. This operation would compute the amount of the fees that the position is eligible to get and then forwards a message to the Position NFT contract Access Rights: May be called by anyone — the pool only reads state here and changes nothing. Real authorization happens downstream: the message is forwarded to the Position NFT, which verifies that the original caller is the NFT owner and that the ticks match before the burn proceeds. A spoofed request just wastes the caller's gas.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| burned_index | Uint(64) | Index if the NFT to burn | |
| liquidity2Burn | Uint(128) | Amount of the liquidity to burn, 0 is a valid amount, in this case only collected fees would be returned | |
| tickLower | Int(24) | Lower tick of the NFT. Should match the real one | |
| tickUpper | Int(24) | Upper tick of the NFT. Should match the real one | |
| action | Cell(0),Maybe | Cell that describes what to do with the burn result |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for POOL_START_BURN**POOL_START_BURN#530b5f2c
query_id:uint64
burned_index:uint64
liquidity2Burn:uint128
tickLower:int24
tickUpper:int24
action:(Maybe ^Cell)
= ContractMessages;
POOL_SWAP
Opcode : 0xa7fb58f8
V1.5 Computes the swap math, and issues a command to the router to send funds. Only would be accepted from the router This operation we have several input parameters that would affect the result of the swap
| Condition | Swap result | Returned Change | Error Code |
|---|---|---|---|
| Swap finished sqrtPriceLimitX96 not reached. minOutAmount surpassed | total output number of coins | 0 | RESULT_SWAP_OK |
| Swap finished minOutAmount not surpassed | 0 | amount | RESULT_SWAP_OUTPUT_TOO_SMALL |
| Swap reached sqrtPriceLimitX96 after changing part1 coins. minOutAmount surpassed | output number of coins | amount - part1 | RESULT_SWAP_OK |
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| source_type | Uint(4) | Type of source | |
| from_user | Address(267) | User that initiated the swap. This is used to control access | |
| owner_address | Address(267) | Owner of the liquidity in swap | |
| zeroForOne | Uint(1),Boolean | used to identify swap direction | |
| input_jetton_wallet | Address(267) | Router jetton wallet of the input jetton. Pool ignores it, it is only used to refund the swap on bounce | |
| 0 | Cell(0) params_cell | Cell with parameters | |
| 0 | amount | Coins(124) | Input amount of the jettons to be swapped |
| 0 | sqrtPriceLimitX96 | Uint(160),PriceX96 | Limit marginal price. Swap won't go beyond it. |
| 0 | minOutAmount | Coins(124) | Minimum amount of the output jettons to get back. If not reached, your input would be returned to you |
| 1 | Cell(0) payloads_cell | Cell with payloads for swap result and change | |
| 1 | target_address | Address(267) | Target will receive the result of the swap. Could be addr_none() (*00b*) then owner_address is used |
| 1 | ok_forward_amount | Coins(124) | Amount of GRAM to use for forward payload that would be sent with the result of the swap |
| 1 | ok_forward_payload | Cell(0),Maybe,Payload | Payload that would be sent with the jettons of the result of the swap |
| 1 | ret_forward_amount | Coins(124) | Amount of GRAM to use for forward payload that would be sent with the change for the swap (if any) |
| 1 | ret_forward_payload | Cell(0),Maybe,Payload | Payload that would be sent with the jettons of the change of the swap (if any) |
| 1 | excess_address | Address(267) | Address to receive the excess gas. Could be addr_none() (*00b*) then owner_address is used |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for POOL_SWAP**POOL_SWAP#a7fb58f8
query_id:uint64
source_type:uint4
from_user:MsgAddress
owner_address:MsgAddress
zeroForOne:uint1
input_jetton_wallet:MsgAddress
params_cell:^Params_cellType
payloads_cell:^Payloads_cellType
= ContractMessages;
_
amount:(VarUInteger 16)
sqrtPriceLimitX96:uint160
minOutAmount:(VarUInteger 16)
= Params_cellType;
_
target_address:MsgAddress
ok_forward_amount:(VarUInteger 16)
ok_forward_payload:(Maybe ^Cell)
ret_forward_amount:(VarUInteger 16)
ret_forward_payload:(Maybe ^Cell)
excess_address:MsgAddress
= Payloads_cellType;
Router
Description
The Router is the system's central dispatcher and the custodian of all LP jetton wallets. Users reach the protocol by transferring jettons to the router with a payload (see router payloads); the router parses it and routes the funds to the right Pool for a swap or for funding a mint. It also deploys new pools (from the Pool Factory or the admin), pays funds back out through its own jetton wallets (PAY_TO), and supports a multihop shortcut. The router cannot cryptographically verify that a transfer notification came from a real jetton wallet, so it derives the target pool from the sender — a fake sender simply maps to a pool that does not exist. Critical parameters (admin, proxyTON wallet, flags, router/pool code) change only through a two-phase timelock; the admin is expected to be a multisig.
Interface
getRouterState
(address, address, address, address?, int, int, int) getRouterState ()
returns the router state and configuration
Return Values
| # | Type | Description |
|---|---|---|
| 0 | address | router admin address |
| 1 | address | pool admin address (passed to pools on creation; has privileged pool rights) |
| 2 | address | pool factory address (allowed to request pool creation) |
| 3 | address? | proxy TON wallet address (critial to multihop shortcut) |
| 4 | int | router flags |
| 5 | int | router seqno (monotonic counter for indexing) |
| 6 | int | router version (e.g. 16000 = v1.6.000) |
getPoolAddress
(address) getPoolAddress (jettonWallet0: address, jettonWallet1: address)
returns pool address for two given jetton_wallets belonging to the router
Parameters
| # | Type | Name | Description |
|---|---|---|---|
| 0 | address | jettonWallet0 | Address of the jetton 0 wallet belonging to router |
| 1 | address | jettonWallet1 | Address of the jetton 1 wallet belonging to router |
Return Values
| # | Type | Description |
|---|---|---|
| 0 | address | pool address |
getChildContracts
(cell, cell, cell, cell) getChildContracts ()
returns code of the child contracts deprecated
Return Values
| # | Type | Description |
|---|---|---|
| 0 | cell | code of the pool contract |
| 1 | cell | code of the account contract |
| 2 | cell | code of the nft position contract |
| 3 | cell | code of the pool prototype contract |
get_pending_timelocks
( int, int, address?, int, address?, int, int, int, cell?, int, cell?,) get_pending_timelocks ()
returns active router timelocks to monitor system state. Timestamp 0xFFFFFFFFFFFFFFFF means - never/timelock not active
Return Values
| # | Type | Description |
|---|---|---|
| 0 | int | Timelock delay |
| 1 | int | Timestamp when new Admin Address can be committed |
| 2 | address? | New Admin Address |
| 3 | int | Timestamp when new Proxy Ton Address can be committed |
| 4 | address? | New Proxy Ton Address |
| 5 | int | Timestamp when new flags can be committed |
| 6 | int | New Flags |
| 7 | int | Timestamp when new code can be committed |
| 8 | cell? | Cell with new code |
| 9 | int | Timestamp when new pool code can be committed |
| 10 | cell? | Cell with new pool code |
Messages
JETTON_TRANSFER_NOTIFICATION
Opcode : 0x7362d09c
Process router funding, payload determines if it is mint or swap Access Rights: Sent by a jetton wallet when a user transfers tokens to the router. The router cannot verify that the notification truly comes from a real jetton wallet, so it does not authorize the sender directly: the target pool is derived from the sender, and if the sender is not a genuine wallet the derived pool simply does not exist. Funds that fail validation are returned to the user.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| jetton_amount | Coins(124) | Amount of coins sent to the router | |
| from_user | Address(267) | User that originated the transfer | |
| forward_payload | Cell(0),Either, Payload | Payload for processing |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for JETTON_TRANSFER_NOTIFICATION**JETTON_TRANSFER_NOTIFICATION#7362d09c
query_id:uint64
jetton_amount:(VarUInteger 16)
from_user:MsgAddress
forward_payload:(Either ^Cell Cell)
= ContractMessages;
ROUTER_CREATE_POOL
Opcode : 0x2e3034ef
Operation that deploys and inits new Pool contract for two given jettons identified by their wallets. New pool would reorder the jettons to match the invariant slice_hash(jetton0_address) > slice_hash(jetton1_address).
Access Rights:
Allowed for the Pool Factory and the Router Admin (Superadmin). On a repeated CREATE_POOL for an existing pool the router forwards an initialization message, which lets the admin change tick spacing, NFT metadata, the initial price (while no positions exist), the Pool Admin and Controller, and the lock state; a request coming through the Pool Factory cannot assign the ALM Vault or Arbiter roles.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| jetton_wallet0 | Address(267) | Address of the jetton0 wallet. Used to compute pool address | |
| jetton_wallet1 | Address(267) | Address of the jetton1 wallet. Used to compute pool address | |
| tick_spacing | Uint(24) | Tick spacing to be used in the pool | |
| initial_priceX96 | Uint(160),PriceX96 | Initial price for the pool | |
| pool_active | Uint(1) | Should pool be created as active | |
| protocol_fee | Uint(16),Fee | Protocol fee in FEE_DENOMINATOR parts. If value is more than 10000 value would be default | |
| lp_fee_base | Uint(16),Fee | Liquidity provider base fee in FEE_DENOMINATOR. If value is more than 10000 value would be default | |
| lp_fee_current | Uint(16),Fee | Current value of the pool fee, in case of dynamic adjustment. If value is more than 10000 value would be default | |
| 0 | Cell(0) roles_cell | Cell With Role | |
| 0 | has_controller | Uint(1),Bool | Flag that shows if this message have a new controller address |
| 0 | controller_addr | Address(267) | Address that is allowed to change the fee. Can always be updated by admin. |
| 0 | has_creator | Uint(1),Bool | Flag that shows if this message have a new creator address |
| 0 | creator_addr | Address(267) | Address that is allowed to change the fee. Can always be updated by admin. If has_controller is false could be 00b |
| 0 | 0 | Cell(0) privilege_roles_cell | Cell With Privilege Roles |
| 0->0 | has_arbiter | Uint(1),Bool | Flag that shows if this message have a new Arbiter address |
| 0->0 | arbiter_addr | Address(267) | Address that is allowed to make privileged swaps |
| 0->0 | has_alm | Uint(1),Bool | Flag that shows if this message have a new ALM address |
| 0->0 | alm_addr | Address(267) | Address that is used to identify ALM contract |
| 0 | 1 | Cell(0) roles_add | Cell With Additional Roles |
| 0->1 | has_oracle | Uint(1),Bool | Flag that shows if this message have a new oracle address |
| 0->1 | oracle_addr | Address(267) | Address that is allowed to change the fee. Can always be updated by admin. Could be 00b |
| 1 | Cell(0) metadata_cell | Cell With Metainfo | |
| 1 | nftv3_content | Cell(0),Metadata | Metadata for the NFT Collection |
| 1 | nftv3item_content | Cell(0),Metadata | Metadata for the NFT Item |
| 2 | Cell(0) minter_cell | Cell With Minters | |
| 2 | jetton0_minter | Address(267) | Address of the jetton0 minter, used by indexer and frontend |
| 2 | jetton1_minter | Address(267) | Address of the jetton1 minter, used by indexer and frontend |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for ROUTER_CREATE_POOL**ROUTER_CREATE_POOL#2e3034ef
query_id:uint64
jetton_wallet0:MsgAddress
jetton_wallet1:MsgAddress
tick_spacing:uint24
initial_priceX96:uint160
pool_active:uint1
protocol_fee:uint16
lp_fee_base:uint16
lp_fee_current:uint16
roles_cell:^Roles_cellType
metadata_cell:^Metadata_cellType
minter_cell:^Minter_cellType
= ContractMessages;
_
has_arbiter:uint1
arbiter_addr:MsgAddress
has_alm:uint1
alm_addr:MsgAddress
= Privilege_roles_cellType;
_
has_oracle:uint1
oracle_addr:MsgAddress
= Roles_addType;
_
has_controller:uint1
controller_addr:MsgAddress
has_creator:uint1
creator_addr:MsgAddress
privilege_roles_cell:^Privilege_roles_cellType // optional ref (present only if a ref is left)
roles_add:^Roles_addType // optional ref (present only if a ref is left)
= Roles_cellType;
_
nftv3_content:^Cell
nftv3item_content:^Cell
= Metadata_cellType;
_
jetton0_minter:MsgAddress
jetton1_minter:MsgAddress
= Minter_cellType;
ROUTER_PAY_TO
Opcode : 0xa1daa96d
This message is the message that is sent to Router by the pool, an it would be accepted only from the pool:
Possible error codes
| Operation | Error Core | Value | Meaning |
|---|---|---|---|
| SWAP | RESULT_SWAP_OK | 200 | Swap went fine |
| SWAP | RESULT_SWAP_OUTPUT_TOO_SMALL | 230 | minAmount was not reached, swap reverted |
| SWAP | RESULT_SWAP_INTERNAL_ERROR | 231 | Internal error, generally this should not happen |
| SWAP | RESULT_SWAP_POOL_LOCKED | 232 | Attempt to swap in the locked pool |
| SWAP | RESULT_SWAP_POOL_EMPTY | 233 | Attempt to swap in the empty pool |
| MINT | RESULT_MINT_OK | 202 | Mint went fine |
| MINT | RESULT_NO_LIQUIDITY | 226 | Minting Zero liquidity was asked (not always unintended) |
| MINT | RESULT_TICK_IMPOSSIBLE_LOW | 220 | One of the ticks is too low |
| MINT | RESULT_TICK_IMPOSSIBLE_HIGH | 221 | One of the ticks is too high |
| MINT | RESULT_WRONG_TICK_SPACING | 222 | One of ticks is not divisible by tick_spacing |
| MINT | RESULT_TOO_MANY_TICKS | 223 | Pool is full |
| MINT | RESULT_TOO_MUCH_LIQUIDITY | 224 | Too much liquidity in one tick |
| MINT | RESULT_NOT_ENOUGH_COINS | 225 | Not enough coins to mint |
| BURN | RESULT_BURN_OK | 201 | Burn went fine |
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| reciever0 | Address(267) | Address of the first receiver of the funds | |
| reciever1 | Address(267) | Address of the second receiver of the funds | |
| exit_code0 | Uint(8),ErrorCode | Most significant byte. Reforge: result of the first processed order | |
| exit_code1 | Uint(8),ErrorCode | Reforge: result of the second processed order | |
| exit_code2 | Uint(8),ErrorCode | Reforge: result of the third processed order | |
| exit_code3 | Uint(8),ErrorCode | Least significant byte. Swap/burn/collect result is here; reforge: fourth order | |
| seqno | Uint(64),Indexer | Internal pool sequence number | |
| 0 | Maybe Cell(1) coinsinfo_cell | Cell with info about the coins | |
| 0 | jetton0_address | Address(267) | Jetton to be sent to receiver0 identified by the wallet that belongs to router |
| 0 | jetton1_address | Address(267) | Jetton to be sent to receiver1 identified by the wallet that belongs to router |
| 0 | amount0 | Coins(124) | Amount of coins to be payed to receiver0 |
| 0 | amount1 | Coins(124) | Amount of coins to be payed to receiver1 |
| 0 | 0 | Cell(0) payload_cell | Information about the payload |
| 0->0 | payload_amount0 | Coins(124) | Amount of GRAM to forward to receiver0 together with payload_0 |
| 0->0 | payload_0 | Cell(1),Maybe, Payload | Forward payload delivered to receiver0 with the jetton transfer |
| 0->0 | payload_amount1 | Coins(124) | Amount of GRAM to forward to receiver1 together with payload_1 |
| 0->0 | payload_1 | Cell(1),Maybe, Payload | Forward payload delivered to receiver1 with the jetton transfer |
| 0->0 | excess_address | Address(267) | Address to receive the excess gas |
| 1 | Maybe Cell(1) indexer_swap_info_cell | Indexer-only. Post-swap pool state (null for non-swap) | |
| 1 | liquidity | Uint(128),Indexer | Post-swap concentrated liquidity at current tick |
| 1 | price_sqrt | Uint(160),Indexer,PriceX96 | Post-swap square root of the price stored as fixed point 64.96 |
| 1 | tick | Int(24),Indexer | Post-swap current tick |
| 1 | feeGrowthGlobal0X128 | Int(256),Indexer | Current range fee per unit of the liquidity for jetton0 |
| 1 | feeGrowthGlobal1X128 | Int(256),Indexer | Current range fee per unit of the liquidity for jetton1 |
| 2 | Maybe Cell(1) positions_cell | Cell with positions (null for swap/collect) | |
| 2 | 0 | Maybe Cell(1) burn0 | Cell with burn number 0 |
| 2->0 | index[0] | Uint(64) | Index of the burned position NFT |
| 2->0 | subindex[0] | Uint(4) | Subindex |
| 2->0 | liquidity[0] | Uint(128) | Position liquidity before this burn |
| 2->0 | tickLower[0] | Int(24) | lower bound of the range in which to mint |
| 2->0 | tickUpper[0] | Int(24) | upper bound of the range in which to mint |
| 2->0 | feeGrowthInside0LastX128[0] | Int(256),x128 | Fee counter inside position range for jetton0, per unit of liquidity, in 128.128 fixed point |
| 2->0 | feeGrowthInside1LastX128[0] | Int(256),x128 | Fee counter inside position range for jetton1, per unit of liquidity, in 128.128 fixed point |
| 2,0 | 0 | Maybe Cell(1) new_fee_cell | Fee counters to collect to (Used by indexer) |
| 2->0->0 | feeGrowthInside0CurrentX128[0] | Int(256),x128 | Fee counter inside position range for jetton0, per unit of liquidity, in 128.128 fixed point |
| 2->0->0 | feeGrowthInside1CurrentX128[0] | Int(256),x128 | Fee counter inside position range for jetton1, per unit of liquidity, in 128.128 fixed point |
| 2->0 | liquidity2Burn[0] | Uint(128) | Amount of liquidity that was burned from this position |
| 2 | 1 | Maybe Cell(1) burn1 | Cell with burn number 1 |
| 2->1 | index[1] | Uint(64) | Index of the burned position NFT |
| 2->1 | subindex[1] | Uint(4) | Subindex |
| 2->1 | liquidity[1] | Uint(128) | Position liquidity before this burn |
| 2->1 | tickLower[1] | Int(24) | lower bound of the range in which to mint |
| 2->1 | tickUpper[1] | Int(24) | upper bound of the range in which to mint |
| 2->1 | feeGrowthInside0LastX128[1] | Int(256),x128 | Fee counter inside position range for jetton0, per unit of liquidity, in 128.128 fixed point |
| 2->1 | feeGrowthInside1LastX128[1] | Int(256),x128 | Fee counter inside position range for jetton1, per unit of liquidity, in 128.128 fixed point |
| 2,1 | 0 | Maybe Cell(1) new_fee_cell | Fee counters to collect to (Used by indexer) |
| 2->1->0 | feeGrowthInside0CurrentX128[1] | Int(256),x128 | Fee counter inside position range for jetton0, per unit of liquidity, in 128.128 fixed point |
| 2->1->0 | feeGrowthInside1CurrentX128[1] | Int(256),x128 | Fee counter inside position range for jetton1, per unit of liquidity, in 128.128 fixed point |
| 2->1 | liquidity2Burn[1] | Uint(128) | Amount of liquidity that was burned from this position |
| 2 | 2 | Maybe Cell(1) burn2 | Cell with burn number 2 |
| 2->2 | index[2] | Uint(64) | Index of the burned position NFT |
| 2->2 | subindex[2] | Uint(4) | Subindex |
| 2->2 | liquidity[2] | Uint(128) | Position liquidity before this burn |
| 2->2 | tickLower[2] | Int(24) | lower bound of the range in which to mint |
| 2->2 | tickUpper[2] | Int(24) | upper bound of the range in which to mint |
| 2->2 | feeGrowthInside0LastX128[2] | Int(256),x128 | Fee counter inside position range for jetton0, per unit of liquidity, in 128.128 fixed point |
| 2->2 | feeGrowthInside1LastX128[2] | Int(256),x128 | Fee counter inside position range for jetton1, per unit of liquidity, in 128.128 fixed point |
| 2,2 | 0 | Maybe Cell(1) new_fee_cell | Fee counters to collect to (Used by indexer) |
| 2->2->0 | feeGrowthInside0CurrentX128[2] | Int(256),x128 | Fee counter inside position range for jetton0, per unit of liquidity, in 128.128 fixed point |
| 2->2->0 | feeGrowthInside1CurrentX128[2] | Int(256),x128 | Fee counter inside position range for jetton1, per unit of liquidity, in 128.128 fixed point |
| 2->2 | liquidity2Burn[2] | Uint(128) | Amount of liquidity that was burned from this position |
| 2 | 3 | Maybe Cell(1) burn3 | Cell with burn number 3 |
| 2->3 | index[3] | Uint(64) | Index of the burned position NFT |
| 2->3 | subindex[3] | Uint(4) | Subindex |
| 2->3 | liquidity[3] | Uint(128) | Position liquidity before this burn |
| 2->3 | tickLower[3] | Int(24) | lower bound of the range in which to mint |
| 2->3 | tickUpper[3] | Int(24) | upper bound of the range in which to mint |
| 2->3 | feeGrowthInside0LastX128[3] | Int(256),x128 | Fee counter inside position range for jetton0, per unit of liquidity, in 128.128 fixed point |
| 2->3 | feeGrowthInside1LastX128[3] | Int(256),x128 | Fee counter inside position range for jetton1, per unit of liquidity, in 128.128 fixed point |
| 2,3 | 0 | Maybe Cell(1) new_fee_cell | Fee counters to collect to (Used by indexer) |
| 2->3->0 | feeGrowthInside0CurrentX128[3] | Int(256),x128 | Fee counter inside position range for jetton0, per unit of liquidity, in 128.128 fixed point |
| 2->3->0 | feeGrowthInside1CurrentX128[3] | Int(256),x128 | Fee counter inside position range for jetton1, per unit of liquidity, in 128.128 fixed point |
| 2->3 | liquidity2Burn[3] | Uint(128) | Amount of liquidity that was burned from this position |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for ROUTER_PAY_TO**ROUTER_PAY_TO#a1daa96d
query_id:uint64
reciever0:MsgAddress
reciever1:MsgAddress
exit_code0:uint8
exit_code1:uint8
exit_code2:uint8
exit_code3:uint8
seqno:uint64
coinsinfo_cell:(Maybe ^Coinsinfo_cellType)
indexer_swap_info_cell:(Maybe ^Indexer_swap_info_cellType)
positions_cell:(Maybe ^Positions_cellType)
= ContractMessages;
_
payload_amount0:(VarUInteger 16)
payload_0:(Maybe ^Cell)
payload_amount1:(VarUInteger 16)
payload_1:(Maybe ^Cell)
excess_address:MsgAddress
= Payload_cellType;
_
jetton0_address:MsgAddress
jetton1_address:MsgAddress
amount0:(VarUInteger 16)
amount1:(VarUInteger 16)
payload_cell:^Payload_cellType // optional ref (present only if a ref is left)
= Coinsinfo_cellType;
_
liquidity:uint128
price_sqrt:uint160
tick:int24
feeGrowthGlobal0X128:int256
feeGrowthGlobal1X128:int256
= Indexer_swap_info_cellType;
_
feeGrowthInside0CurrentX128_0:int256
feeGrowthInside1CurrentX128_0:int256
= New_fee_cellType;
_
index_0:uint64
subindex_0:uint4
liquidity_0:uint128
tickLower_0:int24
tickUpper_0:int24
feeGrowthInside0LastX128_0:int256
feeGrowthInside1LastX128_0:int256
new_fee_cell:(Maybe ^New_fee_cellType)
liquidity2Burn_0:uint128
= Burn0Type;
_
feeGrowthInside0CurrentX128_1:int256
feeGrowthInside1CurrentX128_1:int256
= New_fee_cellType1;
_
index_1:uint64
subindex_1:uint4
liquidity_1:uint128
tickLower_1:int24
tickUpper_1:int24
feeGrowthInside0LastX128_1:int256
feeGrowthInside1LastX128_1:int256
new_fee_cell:(Maybe ^New_fee_cellType1)
liquidity2Burn_1:uint128
= Burn1Type;
_
feeGrowthInside0CurrentX128_2:int256
feeGrowthInside1CurrentX128_2:int256
= New_fee_cellType2;
_
index_2:uint64
subindex_2:uint4
liquidity_2:uint128
tickLower_2:int24
tickUpper_2:int24
feeGrowthInside0LastX128_2:int256
feeGrowthInside1LastX128_2:int256
new_fee_cell:(Maybe ^New_fee_cellType2)
liquidity2Burn_2:uint128
= Burn2Type;
_
feeGrowthInside0CurrentX128_3:int256
feeGrowthInside1CurrentX128_3:int256
= New_fee_cellType3;
_
index_3:uint64
subindex_3:uint4
liquidity_3:uint128
tickLower_3:int24
tickUpper_3:int24
feeGrowthInside0LastX128_3:int256
feeGrowthInside1LastX128_3:int256
new_fee_cell:(Maybe ^New_fee_cellType3)
liquidity2Burn_3:uint128
= Burn3Type;
_
burn0:(Maybe ^Burn0Type)
burn1:(Maybe ^Burn1Type)
burn2:(Maybe ^Burn2Type)
burn3:(Maybe ^Burn3Type)
= Positions_cellType;
ROUTERV3_RESET_GAS
Opcode : 0x42a0fb43
This operation allows router owners the gas if too much accumulated on the contract Access Rights: Allowed only for the Router Admin. Withdraws excess TON that accumulated on the router, leaving enough to keep it funded.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for ROUTERV3_RESET_GAS**ROUTERV3_RESET_GAS#42a0fb43
query_id:uint64
= ContractMessages;
Router Payloads
Description
These are not standalone messages but forward-payload formats carried inside a jetton transfer_notification to the Router. The payload's leading op selects the action — fund a user's per-pool Account for minting, fund someone else's Account, or swap against a pool. The router derives the target pool from the sending jetton wallet, so a payload can only act on a real pool, and any leftover or failed funds are returned to the user via PAY_TO.
Messages
POOL_FUND_ACCOUNT
Opcode : 0x4468de77
This is not a message Op this is a payload format for JETTON_TRANSFER_NOTIFICATION Access Rights: Not a standalone message — a forward payload any user attaches to a jetton transfer to fund their own per-pool Account (mint request). No authorization is needed: the user funds the Account that belongs to their own address, so they can only ever top up their own.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | POOLV3_FUND_ACCOUNT (0x4468de77) | |
| jetton_target_w | Address(267) | Address of the jetton0 wallet. Used to compute pool address | |
| enough0 | Coins(124) | When temporary storage (user account) satisfy both enough0/1 amounts - mint would trigger | |
| enough1 | Coins(124) | When temporary storage (user account) satisfy both enough0/1 amounts - mint would trigger | |
| needPos | Uint(64) | Amount of positions that are enough for the order to be executed | |
| passthrough | Uint(4) | Amount of positions that will be untouched | |
| target_action | Uint(32) | Routing of the reforge result (ACTION_TARGET_DEFAULT / ACTION_TARGET_ALM) — see target_action in ACCOUNT_SET_ORDER for details | |
| 0 | Maybe Cell(1) MintOrder0 | Cell with mint order | |
| 0 | op0 | Uint(32) | Mint mode (MINT_NOT_LESS / MINT_AS_MUCH_AS_POSSIBLE) — see mintOp in ACCOUNT_SET_ORDER for details |
| 0 | liquidity0 | Uint(128) | Amount of liquidity to mint |
| 0 | tickLower0 | Int(24) | lower bound of the range in which to mint |
| 0 | tickUpper0 | Int(24) | upper bound of the range in which to mint |
| 0 | receiver | Address(267) | Address of receiver of the NFT |
| 1 | Maybe Cell(1) MintOrder1 | Cell with mint order | |
| 1 | op1 | Uint(32) | Mint mode (MINT_NOT_LESS / MINT_AS_MUCH_AS_POSSIBLE) — see mintOp in ACCOUNT_SET_ORDER for details |
| 1 | liquidity1 | Uint(128) | Amount of liquidity to mint |
| 1 | tickLower1 | Int(24) | lower bound of the range in which to mint |
| 1 | tickUpper1 | Int(24) | upper bound of the range in which to mint |
| 1 | receiver | Address(267) | Address of receiver of the NFT |
| 2 | Maybe Cell(1) MintOrder2 | Cell with mint order | |
| 2 | op2 | Uint(32) | Mint mode (MINT_NOT_LESS / MINT_AS_MUCH_AS_POSSIBLE) — see mintOp in ACCOUNT_SET_ORDER for details |
| 2 | liquidity2 | Uint(128) | Amount of liquidity to mint |
| 2 | tickLower2 | Int(24) | lower bound of the range in which to mint |
| 2 | tickUpper2 | Int(24) | upper bound of the range in which to mint |
| 2 | receiver | Address(267) | Address of receiver of the NFT |
| 3 | Maybe Cell(1) MintOrder3 | Cell with mint order | |
| 3 | op3 | Uint(32) | Mint mode (MINT_NOT_LESS / MINT_AS_MUCH_AS_POSSIBLE) — see mintOp in ACCOUNT_SET_ORDER for details |
| 3 | liquidity3 | Uint(128) | Amount of liquidity to mint |
| 3 | tickLower3 | Int(24) | lower bound of the range in which to mint |
| 3 | tickUpper3 | Int(24) | upper bound of the range in which to mint |
| 3 | receiver | Address(267) | Address of receiver of the NFT |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for POOL_FUND_ACCOUNT**POOL_FUND_ACCOUNT#4468de77
jetton_target_w:MsgAddress
enough0:(VarUInteger 16)
enough1:(VarUInteger 16)
needPos:uint64
passthrough:uint4
target_action:uint32
MintOrder0:(Maybe ^MintOrder0Type)
MintOrder1:(Maybe ^MintOrder1Type)
MintOrder2:(Maybe ^MintOrder2Type)
MintOrder3:(Maybe ^MintOrder3Type)
= ContractMessages;
_
op0:uint32
liquidity0:uint128
tickLower0:int24
tickUpper0:int24
receiver:MsgAddress
= MintOrder0Type;
_
op1:uint32
liquidity1:uint128
tickLower1:int24
tickUpper1:int24
receiver:MsgAddress
= MintOrder1Type;
_
op2:uint32
liquidity2:uint128
tickLower2:int24
tickUpper2:int24
receiver:MsgAddress
= MintOrder2Type;
_
op3:uint32
liquidity3:uint128
tickLower3:int24
tickUpper3:int24
receiver:MsgAddress
= MintOrder3Type;
POOL_FUND_SOMEONES_ACCOUNT
Opcode : 0x134b6d30
This is not a message Op this is a payload format for JETTON_TRANSFER_NOTIFICATION
Access Rights:
Not a standalone message — a forward payload that funds another user's (to_user) Account rather than the sender's. Used by the Arbiter, which acts like a WalletAccount but always credits itself when funding through this path.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| jetton_target_w | Address(267) | Address of the second jetton wallet (first is identified by sender_address). Used to compute pool address | |
| to_user | Address(267) | User to fund |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for POOL_FUND_SOMEONES_ACCOUNT**POOL_FUND_SOMEONES_ACCOUNT#134b6d30
jetton_target_w:MsgAddress
to_user:MsgAddress
= ContractMessages;
POOL_SWAP
Opcode : 0xa7fb58f8
This is not a message Op this is a payload format for JETTON_TRANSFER_NOTIFICATION Access Rights: Not a standalone message — a forward payload any user attaches to a jetton transfer to swap against a pool. No sender authorization is enforced here: the pool is derived from the sending jetton wallet, swapped funds and excess gas go to the owner/excess addresses carried in the payload, and the swap result is returned via PAY_TO.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| target_jetton_wallet | Address(267) | Router's jetton wallet of the OTHER side of the swap. Together with the sending jetton wallet it identifies the pool (and thus the swap direction) | |
| sqrtPriceLimitX96 | Uint(160),PriceX96 | Limit price. Swap won't go beyond it | |
| minOutAmount | Coins(124) | Minimum acceptable output amount (slippage guard). If the swap output is below this, it is reverted (LOWER_THEN_MIN_OUT) and funds are returned | |
| owner_address | Address(267) | Address of the sender | |
| 0 | Maybe Cell(1) multihop_cell | Cell with multihop data | |
| 0 | target_address | Address(267) | Address of the receiver |
| 0 | ok_forward_amount | Coins(124) | Amount of GRAM to use for forward payload that would be sent with the result of the swap |
| 0 | ok_forward_payload | Cell(0),Maybe, Payload | Payload for processing by target with swapped coins |
| 0 | ret_forward_amount | Coins(124) | Amount of GRAM to use for forward payload that would be sent with the change for the swap (if any) |
| 0 | ret_forward_payload | Cell(0),Maybe, Payload | Payload for processing by owner with change/return coins |
| 0 | excess_address | Address(267) | Address to get excess gas |
| 1 | Cell(0) referral_cell | Indexer-only. Partner referral marker, not parsed on-chain (read off-chain by indexer) | |
| 1 | code | Uint(32),Indexer | Partner referral code |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for POOL_SWAP**POOL_SWAP#a7fb58f8
target_jetton_wallet:MsgAddress
sqrtPriceLimitX96:uint160
minOutAmount:(VarUInteger 16)
owner_address:MsgAddress
multihop_cell:(Maybe ^Multihop_cellType)
referral_cell:^Referral_cellType // optional ref (present only if a ref is left)
= ContractMessages;
_
target_address:MsgAddress
ok_forward_amount:(VarUInteger 16)
ok_forward_payload:(Maybe ^Cell)
ret_forward_amount:(VarUInteger 16)
ret_forward_payload:(Maybe ^Cell)
excess_address:MsgAddress
= Multihop_cellType;
_
code:uint32
= Referral_cellType;
Position NFT
Description
The Position NFT represents a single liquidity position as a standalone, TEP-62/TEP-64 compatible NFT contract. Its address is deterministic — derived from {pool, index} — so the pool can always recompute and trust it. The NFT holds the position's liquidity, tick range and fee counters, and works hand in hand with the Pool: the pool seeds it on mint, and burns flow through it so it can verify ownership and tick consistency. The owner can transfer it, deposit part of it into their Account, or burn it — including via a plain wallet burn text comment, a front-end-free escape hatch (see BURN_WITH_TEXT).
Interface
getPoolAddress
(address) getPoolAddress ()
This function returns pool address that created this Position NFT
Return Values
| # | Type | Description |
|---|---|---|
| 0 | address | address in question |
getUserAddress
(address) getUserAddress ()
This function returns user address that owned created this Position NFT
Return Values
| # | Type | Description |
|---|---|---|
| 0 | address | address in question |
getPositionInfo
(int, int, int, int, int) getPositionInfo ()
This function returns data stored in Position NFT and is related to the position
Return Values
| # | Type | Description |
|---|---|---|
| 0 | int | liquidity that this position owns |
| 1 | int | lower tick of the position |
| 2 | int | upper tick of the position |
| 3 | int | fee growth of jetton0 in the given range at moment of the creation or latest collect of the NFT position |
| 4 | int | fee growth of jetton1 in the given range at moment of the creation or latest collect of the NFT position |
get_nft_data
(bool, int, address, address, cell) get_nft_data ()
This function returns data of this Position NFT that is related to NFT as TEP-62 It also attaches some values to transfer them to pool
Return Values
| # | Type | Description |
|---|---|---|
| 0 | bool | Is position active (in our case, if positionv3::liquidity != 0) |
| 1 | int | positionv3::index |
| 2 | address | positionv3::pool_address |
| 3 | address | Owner address (positionv3::user_address) |
| 4 | cell | Content of the NFT. The cell with a dict that has position data appended to the cell for the pool to parse |
Messages
POSITIONNFTV3_POSITION_INIT
Opcode : 0xd5ecca2a
Initial message that pools sends to the NFT after state_init Access Rights: Accepted only from the pool that owns this NFT (its address is fixed at creation). Sent right after deployment to fill in the position.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| user_address | Address(267) | NFT owner | |
| 0 | Cell(0) pos0 | Position | |
| 0 | liquidity | Uint(128) | Amount of the liquidity |
| 0 | tickLower | Int(24) | Lower tick of the NFT |
| 0 | tickUpper | Int(24) | Upper tick of the NFT |
| 0 | feeGrowthInside0LastX128 | Int(256),x128 | Fees collected before the position was opened or updated for jetton0 (in pool terms) |
| 0 | feeGrowthInside1LastX128 | Int(256),x128 | Fees collected before the position was opened or updated for jetton1 (in pool terms) |
| 0 | 0 | Maybe Cell(1) fees | newFees |
| 0->0 | feeGrowthInside0LastX128 | Int(256),x128 | Optional updated fee growth for jetton0 inside the range, per unit of liquidity, 128.128 fixed point (used by the indexer to track fees to collect) |
| 0->0 | feeGrowthInside1LastX128 | Int(256),x128 | Optional updated fee growth for jetton1 inside the range, per unit of liquidity, 128.128 fixed point (used by the indexer to track fees to collect) |
| 1 | Cell(0) Indexer | Fee counters From | |
| 1 | seqno | Uint(64),Indexer | Indexer-only. Pool sequence number at mint time |
| 1 | nftIndex | Uint(64),Indexer | Indexer-only. Index of this position NFT in the collection |
| 1 | jetton0Amount | Coins(124),Indexer | Indexer-only. Amount of jetton0 deposited into this position at mint |
| 1 | jetton1Amount | Coins(124),Indexer | Indexer-only. Amount of jetton1 deposited into this position at mint |
| 1 | tick | Int(24),Indexer | Indexer-only. Pool current tick at mint time |
| 1 | sqrtPriceX96 | Uint(160),PriceX96, Indexer | Current price |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for POSITIONNFTV3_POSITION_INIT**POSITIONNFTV3_POSITION_INIT#d5ecca2a
query_id:uint64
user_address:MsgAddress
pos0:^Pos0Type
Indexer:^IndexerType
= ContractMessages;
_
feeGrowthInside0LastX128:int256
feeGrowthInside1LastX128:int256
= FeesType;
_
liquidity:uint128
tickLower:int24
tickUpper:int24
feeGrowthInside0LastX128:int256
feeGrowthInside1LastX128:int256
fees:(Maybe ^FeesType)
= Pos0Type;
_
seqno:uint64
nftIndex:uint64
jetton0Amount:(VarUInteger 16)
jetton1Amount:(VarUInteger 16)
tick:int24
sqrtPriceX96:uint160
= IndexerType;
POSITIONNFT_POSITION_BURN
Opcode : 0x46ca335a
Message from the pool that is part of burn process. This message carries new feeGrowthInside?Last values form the pool Access Rights: Accepted only from the pool that owns this NFT. It is the pool's reply within the burn flow, carrying the fee counters needed to settle the position.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| nft_owner | Address(267) | NFT owner to receive funds | |
| liquidity2Burn | Uint(128) | Amount of the liquidity to burn, 0 is a valid amount, in this case only collected fees would be returned | |
| tickLower | Int(24) | Lower tick of the NFT. NFT would check that it is the same as in position | |
| tickUpper | Int(24) | Upper tick of the NFT. NFT would check that it is the same as in position | |
| 0 | Cell(0) old_fee_cell | Fee counters From | |
| 0 | feeGrowthInside0LastX128 | Int(256),x128 | Current fee growth for jetton0 inside the position range supplied by the pool, per unit of liquidity, 128.128 fixed point. The NFT uses the delta versus its stored value to compute fees owed |
| 0 | feeGrowthInside1LastX128 | Int(256),x128 | Current fee growth for jetton1 inside the position range supplied by the pool, per unit of liquidity, 128.128 fixed point. The NFT uses the delta versus its stored value to compute fees owed |
| action | Cell(0),Maybe | Cell that describes what to do with burn result |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for POSITIONNFT_POSITION_BURN**POSITIONNFT_POSITION_BURN#46ca335a
query_id:uint64
nft_owner:MsgAddress
liquidity2Burn:uint128
tickLower:int24
tickUpper:int24
old_fee_cell:^Old_fee_cellType
action:(Maybe ^Cell)
= ContractMessages;
_
feeGrowthInside0LastX128:int256
feeGrowthInside1LastX128:int256
= Old_fee_cellType;
BURN_WITH_TEXT
Opcode : 0x0
A plain wallet transfer with the text comment burn, sent by the position owner to burn the whole position manually. Meant for an ordinary, non-technical user: no front-end, no SDK and no knowledge of message layouts required. From any wallet (Tonkeeper, Tonhub, etc.) the owner just sends a small amount of GRAM to the address of their own position NFT and types burn in the comment field. The wallet encodes that comment in the standard TON way — 32 zero bits (op = 0) followed by the UTF-8 bytes of the text, no query_id — and the NFT recognises it.
The comment must be exactly burn: the contract compares the whole comment against the literal burn, so any extra text or argument is rejected (WRONG_COMMAND). There is no burn <amount> form — this path always burns the entire position. Accepted only from the current owner of the NFT (otherwise INVALID_CALLER); the NFT then forwards START_BURN to the pool for the position's full liquidity.
Why it exists: a deliberate self-custodial escape hatch. The owner can always reclaim their funds directly on-chain with nothing but a wallet — no front-end, SDK or indexer needed — even if the indexer is down or the web front-end is unavailable to them (e.g. geo-blocking). (At the TS level the same body is beginCell().storeUint(0, 32).storeBuffer(Buffer.from("burn")).endCell() — see sendBurnText in jest/burn.system.spec.ts — but a real user never needs this; it is just the wallet comment under the hood.)
Dangers — mind the gas. The NFT requires a minimum attached value (BURN_GAS_AMOUNT, ~0.1 GRAM in release builds) and rejects anything below it with INSUFFICIENT_GAS. That minimum only covers the first hop, though: a burn fans out into a multi-contract round-trip (NFT → pool → router → jetton wallets). If too little GRAM is attached, an intermediate step can run out of gas and the funds are NOT recovered automatically. Over-fund to be safe (the tests attach ~0.5 GRAM); unspent gas is returned. Always send the comment to the correct NFT address — a typo or a foreign/forged NFT just wastes the gas spent.
Access Rights:
Allowed only for the current NFT owner. Sending the text comment burn initiates a full-position burn toward the pool.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| text | String(32) | Text with command |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for BURN_WITH_TEXT**BURN_WITH_TEXT#0
text:Text
= ContractMessages;
POSITIONNFTV3_NFT_TRANSFER
Opcode : 0x5fcc3d14
Transfer LP NFT to another user. Please be warned that some UI elements could be unable to track it. However with SDK it still can be burned Access Rights: Allowed only for the current NFT owner. Transfers ownership of the position to a new address.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| new_owner | Address(267) | New NFT owner | |
| response_destination | Address(267) | Address to receive response | |
| custom_payload | Cell(0),Maybe | Custom information for NFT. Ignored by our implementation | |
| forward_amount | Coins(124) | Amount of coins to forward for processing | |
| forward_payload | Cell(0),Either | Payload for processing |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for POSITIONNFTV3_NFT_TRANSFER**POSITIONNFTV3_NFT_TRANSFER#5fcc3d14
query_id:uint64
new_owner:MsgAddress
response_destination:MsgAddress
custom_payload:(Maybe ^Cell)
forward_amount:(VarUInteger 16)
forward_payload:(Either ^Cell Cell)
= ContractMessages;
Account
Description
The Account is a per-user sub-contract of a pool, with a deterministic address derived from {user, pool}. It acts as a barrier that collects the two token deposits of a mint independently of the order in which they arrive: it accumulates amount0/amount1, and once both satisfy the order's thresholds it sends the reforge (mint) request to the Pool. The pool funds it and returns excess/refunds to it; the owning user can set the pending order or reclaim excess gas. Because the address encodes the user, funds can only ever reach the rightful owner's Account.
Interface
get_account_data
(address, address, coins, coins, int, Cell
This function provides current state of the user account
Return Values
| # | Type | Description |
|---|---|---|
| 0 | address | account::user_address Address of the owner of the account |
| 1 | address | account::pool_address Address of the pool that this account is attached to |
| 2 | coins | account::amount0 Amount of jetton0 that was deposited for mint |
| 3 | coins | account::amount1 Amount of jetton1 that was deposited for mint |
| 4 | int | account::posCount Number of positions currently stored in the account |
| 5 | Cell | account::order Pending mint order cell, or null if none |
| 6 | Cell | account::positions Cell with the stored burn positions |
Messages
ACCOUNT_ADD_LIQUIDITY
Opcode : 0x3ebe5431
This operation adds liquidity and a minting request to the account. This contract is used as a barrier to collect together data about the proofs of funding two tokens and the request to mint some liquidity. Common usage is as follows - send one jetton with the mint instructions and the second jetton with the mint instructions. And as soon as they will both arrive AccountV3 would trigger the minting request in the pool. This makes minting independent of the order in which jettons arrive. Account refers to jettons in the pool order Access Rights: Accepted only from the pool this Account belongs to. The pool relays each verified jetton deposit here, and once both sides satisfy the order the Account triggers the mint in the pool.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| new_amount0 | Coins(124) | Amount of jetton0 that is funded for the mint | |
| new_amount1 | Coins(124) | Amount of jetton1 that is funded for the mint | |
| enough0 | Coins(124) | When temporary storage (user account) satisfy both enough0/1 amounts - mint would trigger | |
| enough1 | Coins(124) | When temporary storage (user account) satisfy both enough0/1 amounts - mint would trigger | |
| needPos | Uint(64) | Amount of positions that are enough for the order to be executed | |
| passthrough | Uint(4) | Amount of positions that will be untouched | |
| target_action | Uint(32) | Routing of the reforge result (ACTION_TARGET_DEFAULT / ACTION_TARGET_ALM) — see target_action in ACCOUNT_SET_ORDER for details | |
| 0 | Maybe Cell(1) MintOrder0 | Cell with mint order | |
| 0 | op0 | Uint(32) | Mint mode (MINT_NOT_LESS / MINT_AS_MUCH_AS_POSSIBLE) — see mintOp in ACCOUNT_SET_ORDER for details |
| 0 | liquidity0 | Uint(128) | Amount of liquidity to mint |
| 0 | tickLower0 | Int(24) | lower bound of the range in which to mint |
| 0 | tickUpper0 | Int(24) | upper bound of the range in which to mint |
| 0 | receiver0 | Address(267) | Address of receiver of the NFT |
| 1 | Maybe Cell(1) MintOrder1 | Cell with mint order | |
| 1 | op1 | Uint(32) | Mint mode (MINT_NOT_LESS / MINT_AS_MUCH_AS_POSSIBLE) — see mintOp in ACCOUNT_SET_ORDER for details |
| 1 | liquidity1 | Uint(128) | Amount of liquidity to mint |
| 1 | tickLower1 | Int(24) | lower bound of the range in which to mint |
| 1 | tickUpper1 | Int(24) | upper bound of the range in which to mint |
| 1 | receiver1 | Address(267) | Address of receiver of the NFT |
| 2 | Maybe Cell(1) MintOrder2 | Cell with mint order | |
| 2 | op2 | Uint(32) | Mint mode (MINT_NOT_LESS / MINT_AS_MUCH_AS_POSSIBLE) — see mintOp in ACCOUNT_SET_ORDER for details |
| 2 | liquidity2 | Uint(128) | Amount of liquidity to mint |
| 2 | tickLower2 | Int(24) | lower bound of the range in which to mint |
| 2 | tickUpper2 | Int(24) | upper bound of the range in which to mint |
| 2 | receiver2 | Address(267) | Address of receiver of the NFT |
| 3 | Maybe Cell(1) MintOrder3 | Cell with mint order | |
| 3 | op3 | Uint(32) | Mint mode (MINT_NOT_LESS / MINT_AS_MUCH_AS_POSSIBLE) — see mintOp in ACCOUNT_SET_ORDER for details |
| 3 | liquidity3 | Uint(128) | Amount of liquidity to mint |
| 3 | tickLower3 | Int(24) | lower bound of the range in which to mint |
| 3 | tickUpper3 | Int(24) | upper bound of the range in which to mint |
| 3 | receiver3 | Address(267) | Address of receiver of the NFT |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for ACCOUNT_ADD_LIQUIDITY**ACCOUNT_ADD_LIQUIDITY#3ebe5431
query_id:uint64
new_amount0:(VarUInteger 16)
new_amount1:(VarUInteger 16)
enough0:(VarUInteger 16)
enough1:(VarUInteger 16)
needPos:uint64
passthrough:uint4
target_action:uint32
MintOrder0:(Maybe ^MintOrder0Type)
MintOrder1:(Maybe ^MintOrder1Type)
MintOrder2:(Maybe ^MintOrder2Type)
MintOrder3:(Maybe ^MintOrder3Type)
= ContractMessages;
_
op0:uint32
liquidity0:uint128
tickLower0:int24
tickUpper0:int24
receiver0:MsgAddress
= MintOrder0Type;
_
op1:uint32
liquidity1:uint128
tickLower1:int24
tickUpper1:int24
receiver1:MsgAddress
= MintOrder1Type;
_
op2:uint32
liquidity2:uint128
tickLower2:int24
tickUpper2:int24
receiver2:MsgAddress
= MintOrder2Type;
_
op3:uint32
liquidity3:uint128
tickLower3:int24
tickUpper3:int24
receiver3:MsgAddress
= MintOrder3Type;
ACCOUNT_RESET_GAS
Opcode : 0x42a0fb43
This operation allows user to get back the gas it too much was sent Access Rights: Allowed only for the user who owns this Account. Returns excess TON that accumulated on the contract.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for ACCOUNT_RESET_GAS**ACCOUNT_RESET_GAS#42a0fb43
query_id:uint64
= ContractMessages;
ACCOUNT_SET_ORDER
Opcode : 0x61d86ffe
This operation allows user to set an order (usually for minting or refunding) that will be executed if the preconditions are satisfied Access Rights: Allowed only for the user who owns this Account. Sets the order (mint or refund) that runs once its preconditions are met.
| Path | Mnemonic | Type | Description |
|---|---|---|---|
| op | Uint(32),op | ||
| query_id | Uint(64) | queryid as of the TON documentation | |
| enough0 | Coins(124) | When temporary storage (user account) satisfy both enough0/1 amounts - mint would trigger | |
| enough1 | Coins(124) | When temporary storage (user account) satisfy both enough0/1 amounts - mint would trigger | |
| needPos | Uint(64) | Amount of positions that are enough for the order to be executed | |
| passthrough | Uint(4) | Amount of positions that will be untouched | |
| target_action | Uint(32) | Routing of the reforge result: ACTION_TARGET_DEFAULT (0) sends minted NFT/refunds to the normal recipients; ACTION_TARGET_ALM (1) sends all coins to the pool's ALM role address (used by ALM flows). See action_target in pool_reforge.func | |
| 0 | Maybe Cell(1) mint0 | Cell with mint order 0 | |
| 0 | mintOp[0] | Uint(32) | Mint mode for this order. MINT_NOT_LESS (0): mint exactly `liquidity` units or fail and refund. MINT_AS_MUCH_AS_POSSIBLE (1): mint as much as the funded amounts allow, up to `liquidity`. The 'as much as possible' mode is only honored when the pool has FLAG_MINT_EX_MASK set; otherwise it is silently downgraded to MINT_NOT_LESS (see pool_mint.func). |
| 0 | liquidity[0] | Uint(128) | Amount of liquidity to mint for this order (interpreted per mintOp mode) |
| 0 | tickLower[0] | Int(24) | lower bound of the range in which to mint |
| 0 | tickUpper[0] | Int(24) | upper bound of the range in which to mint |
| 0 | nftReceiver[0] | Address(267) | Address that would receive the minted NFT, excesses and refunds |
| 1 | Maybe Cell(1) mint1 | Cell with mint order 1 | |
| 1 | mintOp[1] | Uint(32) | Mint mode for this order. MINT_NOT_LESS (0): mint exactly `liquidity` units or fail and refund. MINT_AS_MUCH_AS_POSSIBLE (1): mint as much as the funded amounts allow, up to `liquidity`. The 'as much as possible' mode is only honored when the pool has FLAG_MINT_EX_MASK set; otherwise it is silently downgraded to MINT_NOT_LESS (see pool_mint.func). |
| 1 | liquidity[1] | Uint(128) | Amount of liquidity to mint for this order (interpreted per mintOp mode) |
| 1 | tickLower[1] | Int(24) | lower bound of the range in which to mint |
| 1 | tickUpper[1] | Int(24) | upper bound of the range in which to mint |
| 1 | nftReceiver[1] | Address(267) | Address that would receive the minted NFT, excesses and refunds |
| 2 | Maybe Cell(1) mint2 | Cell with mint order 2 | |
| 2 | mintOp[2] | Uint(32) | Mint mode for this order. MINT_NOT_LESS (0): mint exactly `liquidity` units or fail and refund. MINT_AS_MUCH_AS_POSSIBLE (1): mint as much as the funded amounts allow, up to `liquidity`. The 'as much as possible' mode is only honored when the pool has FLAG_MINT_EX_MASK set; otherwise it is silently downgraded to MINT_NOT_LESS (see pool_mint.func). |
| 2 | liquidity[2] | Uint(128) | Amount of liquidity to mint for this order (interpreted per mintOp mode) |
| 2 | tickLower[2] | Int(24) | lower bound of the range in which to mint |
| 2 | tickUpper[2] | Int(24) | upper bound of the range in which to mint |
| 2 | nftReceiver[2] | Address(267) | Address that would receive the minted NFT, excesses and refunds |
| 3 | Maybe Cell(1) mint3 | Cell with mint order 3 | |
| 3 | mintOp[3] | Uint(32) | Mint mode for this order. MINT_NOT_LESS (0): mint exactly `liquidity` units or fail and refund. MINT_AS_MUCH_AS_POSSIBLE (1): mint as much as the funded amounts allow, up to `liquidity`. The 'as much as possible' mode is only honored when the pool has FLAG_MINT_EX_MASK set; otherwise it is silently downgraded to MINT_NOT_LESS (see pool_mint.func). |
| 3 | liquidity[3] | Uint(128) | Amount of liquidity to mint for this order (interpreted per mintOp mode) |
| 3 | tickLower[3] | Int(24) | lower bound of the range in which to mint |
| 3 | tickUpper[3] | Int(24) | upper bound of the range in which to mint |
| 3 | nftReceiver[3] | Address(267) | Address that would receive the minted NFT, excesses and refunds |
TL-B Description
This is a preliminary tl-b - subject to change **Tlb for ACCOUNT_SET_ORDER**ACCOUNT_SET_ORDER#61d86ffe
query_id:uint64
enough0:(VarUInteger 16)
enough1:(VarUInteger 16)
needPos:uint64
passthrough:uint4
target_action:uint32
mint0:(Maybe ^Mint0Type)
mint1:(Maybe ^Mint1Type)
mint2:(Maybe ^Mint2Type)
mint3:(Maybe ^Mint3Type)
= ContractMessages;
_
mintOp_0:uint32
liquidity_0:uint128
tickLower_0:int24
tickUpper_0:int24
nftReceiver_0:MsgAddress
= Mint0Type;
_
mintOp_1:uint32
liquidity_1:uint128
tickLower_1:int24
tickUpper_1:int24
nftReceiver_1:MsgAddress
= Mint1Type;
_
mintOp_2:uint32
liquidity_2:uint128
tickLower_2:int24
tickUpper_2:int24
nftReceiver_2:MsgAddress
= Mint2Type;
_
mintOp_3:uint32
liquidity_3:uint128
tickLower_3:int24
tickUpper_3:int24
nftReceiver_3:MsgAddress
= Mint3Type;