Smart Contract

IncrementFiPoolLiquidityConnectors

A.efa9bd7d1b17f1ed.IncrementFiPoolLiquidityConnectors

Valid From

123,713,804

Deployed

1w ago
Feb 18, 2026, 09:03:36 AM UTC

Dependents

23 imports
1import FungibleToken from 0xf233dcee88fe0abe
2
3import SwapConnectors from 0x0bce04a00aedf132
4import DeFiActions from 0x92195d814edf9cb0
5
6import SwapRouter from 0xa6850776a94e6551
7import SwapConfig from 0xb78ef7afa52ff906
8import SwapFactory from 0xb063c16cac85dbd1
9import StableSwapFactory from 0xb063c16cac85dbd1
10import SwapInterfaces from 0xb78ef7afa52ff906
11
12/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
13/// THIS CONTRACT IS IN BETA AND IS NOT FINALIZED - INTERFACES MAY CHANGE AND/OR PENDING CHANGES MAY REQUIRE REDEPLOYMENT
14/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
15///
16/// IncrementFiPoolLiquidityConnectors
17/// Connector for adding liquidity to IncrementFi pools using one token.
18///
19access(all) contract IncrementFiPoolLiquidityConnectors {
20
21    /// An implementation of DeFiActions.Swapper connector that swaps token0 to token1 and adds liquidity
22    /// to the pool using both tokens. It will then return the LP token. It is commonly called a
23    /// "zap" operation in other protocols.
24    ///
25    access(all) struct Zapper : DeFiActions.Swapper {
26
27        /// An optional identifier allowing protocols to identify stacked connector operations by defining a protocol-
28        /// specific Identifier to associated connectors on construction
29        access(contract) var uniqueID: DeFiActions.UniqueIdentifier?
30        /// The pools token0 type
31        access(self) let token0Type: Type
32        /// The pools token1 type
33        access(self) let token1Type: Type
34        /// The pools LP token type
35        access(self) let lpType: Type
36        /// Stable pool mode flag
37        access(self) let stableMode: Bool
38        /// The address to access pair capabilities
39        access(all) let pairAddress: Address
40
41        init(
42            token0Type: Type,
43            token1Type: Type,
44            stableMode: Bool,
45            uniqueID: DeFiActions.UniqueIdentifier?
46        ) {
47            self.token0Type = token0Type
48            self.token1Type = token1Type
49            self.stableMode = stableMode
50            self.uniqueID = uniqueID
51
52            let token0Key = SwapConfig.SliceTokenTypeIdentifierFromVaultType(vaultTypeIdentifier: token0Type.identifier)
53            let token1Key = SwapConfig.SliceTokenTypeIdentifierFromVaultType(vaultTypeIdentifier: token1Type.identifier)
54
55            self.pairAddress = (stableMode)?
56                StableSwapFactory.getPairAddress(token0Key: token0Key, token1Key: token1Key)
57                    ?? panic("nonexistent stable pair \(token0Key) -> \(token1Key)")
58                :
59                SwapFactory.getPairAddress(token0Key: token0Key, token1Key: token1Key)
60                    ?? panic("nonexistent pair \(token0Key) -> \(token1Key)")
61
62            let pairPublicRef = getAccount(self.pairAddress)
63                .capabilities.borrow<&{SwapInterfaces.PairPublic}>(SwapConfig.PairPublicPath)!
64            self.lpType = pairPublicRef.getLpTokenVaultType()
65        }
66
67        /// Returns a list of ComponentInfo for each component in the stack
68        ///
69        /// @return a list of ComponentInfo for each inner DeFiActions component
70        ///
71        access(all) fun getComponentInfo(): DeFiActions.ComponentInfo {
72            return DeFiActions.ComponentInfo(
73                type: self.getType(),
74                id: self.id() ?? nil,
75                innerComponents: []
76            )
77        }
78        /// Returns a copy of the struct's UniqueIdentifier, used in extending a stack to identify another connector in
79        /// a DeFiActions stack. See DeFiActions.align() for more information.
80        ///
81        /// @return a copy of the struct's UniqueIdentifier
82        ///
83        access(contract) view fun copyID(): DeFiActions.UniqueIdentifier? {
84            return self.uniqueID
85        }
86
87        /// Sets the UniqueIdentifier of this component to the provided UniqueIdentifier, used in extending a stack to
88        /// identify another connector in a DeFiActions stack. See DeFiActions.align() for more information.
89        ///
90        /// @param id: the UniqueIdentifier to set for this component
91        ///
92        access(contract) fun setID(_ id: DeFiActions.UniqueIdentifier?) {
93            self.uniqueID = id
94        }
95
96        /// The type of Vault this Swapper accepts when performing a swap
97        access(all) view fun inType(): Type {
98            return self.token0Type
99        }
100
101        /// The type of Vault this Swapper provides when performing a swap
102        /// In a zap operation, the outType is the LP token type
103        access(all) view fun outType(): Type {
104            return self.lpType
105        }
106
107        /// The estimated amount required to provide a Vault with the desired output balance
108        ///
109        /// Note: The returned quote is the best estimate for the input amount and the corresponding
110        ///       output amount. The output amount may be slightly different from the desired output amount
111        ///       due to the precision of the UFix64 type.
112        ///       This function returns 0.0 for unachievable amounts.
113        ///
114        /// @param forDesired: the amount of the output token to receive
115        /// @param reverse: if reverse is false, will estimate the amount of token0 to provide for a desired LP amount
116        ///                 if reverse is true, will estimate the amount of LP tokens to provide for a desired token0 amount
117        ///
118        /// @return a DeFiActions.Quote struct containing the estimated amount required to provide a Vault with the desired output balance
119        ///
120        access(all) fun quoteIn(forDesired: UFix64, reverse: Bool): {DeFiActions.Quote} {
121            // Handle zero amount case gracefully
122            if (forDesired == 0.0) {
123                return SwapConnectors.BasicQuote(
124                    inType: reverse ? self.outType() : self.inType(),
125                    outType: reverse ? self.inType() : self.outType(),
126                    inAmount: 0.0,
127                    outAmount: 0.0
128                )
129            }
130
131            let pairPublicRef = self.getPairPublicRef()
132            let tokenReserves = self.getTokenReserves(pairPublicRef: pairPublicRef)
133            let token0Reserve = tokenReserves[0]
134            let token1Reserve = tokenReserves[1]
135            assert(token0Reserve > 0.0 && token1Reserve > 0.0, message: "Pool must have positive reserves")
136
137            let pairInfo = pairPublicRef.getPairInfo()
138            let lpTokenSupply = pairInfo[5] as! UFix64
139            assert(lpTokenSupply > 0.0, message: "Pool must have positive LP token supply")
140
141            // The number of epochs to run the binary search for
142            // It takes ~64 iterations to exhaust UFix64 precision
143            let estimationEpochs = 64
144
145            // Use binary search to find the optimal input amount
146            // Start with reasonable bounds based on current reserves
147            var minInput = SwapConfig.ufix64NonZeroMin
148            var maxInput = 0.0
149            if (!reverse) {
150                let maxLpMintAmount = self.getMaxLpMintAmount(pairPublicRef: pairPublicRef)
151                // Unachievable
152                if forDesired > maxLpMintAmount {
153                    return SwapConnectors.BasicQuote(
154                        inType: self.inType(),
155                        outType: self.outType(),
156                        inAmount: 0.0,
157                        outAmount: forDesired
158                    )
159                }
160
161                // Top bound to calculate how much token0 we'd need to provide to get the desired LP amount
162                maxInput = UFix64.max
163            } else {
164                let maxToken0Returned = self.getMaxToken0Returned(pairPublicRef: pairPublicRef)
165                // Unachievable
166                if forDesired > maxToken0Returned {
167                    return SwapConnectors.BasicQuote(
168                        inType: self.outType(),
169                        outType: self.inType(),
170                        inAmount: 0.0,
171                        outAmount: forDesired
172                    )
173                }
174
175                // Top bound to calculate how much LP tokens we'd need to provide to get the desired token0 amount
176                maxInput = lpTokenSupply
177            }
178
179            // Binary search to find the input amount that produces the desired output
180            var bestResult = 0.0
181            var bestInput = 0.0
182            var bestDiff = 0.0
183            var epoch = 0
184            while (epoch < estimationEpochs) {
185                let midInput = minInput * 0.5 + maxInput * 0.5
186
187                // Calculate how much tokens we'd get from this input
188                let result = self.quoteOut(forProvided: midInput, reverse: reverse).outAmount
189
190                // Track the best result we've seen
191                // Note: We look for numbers that are less than the desired amount.
192                let currentDiff = result <= forDesired ? forDesired - result : UFix64.max
193                if (bestResult == 0.0 || currentDiff < bestDiff) {
194                    bestDiff = currentDiff
195                    bestResult = result
196                    bestInput = midInput
197                }
198
199                if (result > forDesired) {
200                    maxInput = midInput
201                } else if (result < forDesired) {
202                    minInput = midInput
203                } else {
204                    break
205                }
206
207                // Precision check, we can't be more precise than this for midInput
208                if (maxInput - minInput <= SwapConfig.ufix64NonZeroMin) {
209                    break
210                }
211
212                epoch = epoch + 1
213            }
214
215            // Final validation
216            assert(bestInput > 0.0, message: "Failed to calculate valid input amount")
217            assert(bestResult > 0.0, message: "Failed to calculate valid result")
218
219            return SwapConnectors.BasicQuote(
220                inType: reverse ? self.outType() : self.inType(),
221                outType: reverse ? self.inType() : self.outType(),
222                inAmount: bestInput,
223                outAmount: bestResult
224            )
225        }
226
227        /// The estimated amount delivered out for a provided input balance
228        ///
229        /// @param forProvided: the amount of the input token to provide
230        /// @param reverse: if reverse is false, will estimate the amount of LP tokens received for a provided input balance
231        ///                 if reverse is true, will estimate the amount of token0 received for a provided LP token balance
232        ///
233        /// @return a DeFiActions.Quote struct containing the estimated amount delivered out for a provided input balance
234        ///
235        access(all) fun quoteOut(forProvided: UFix64, reverse: Bool): {DeFiActions.Quote} {
236            // Handle zero amount case gracefully
237            if (forProvided == 0.0) {
238                return SwapConnectors.BasicQuote(
239                    inType: reverse ? self.outType() : self.inType(),
240                    outType: reverse ? self.inType() : self.outType(),
241                    inAmount: 0.0,
242                    outAmount: 0.0
243                )
244            }
245
246            let pairPublicRef = self.getPairPublicRef()
247            let token0Key = SwapConfig.SliceTokenTypeIdentifierFromVaultType(vaultTypeIdentifier: self.token0Type.identifier)
248            let token1Key = SwapConfig.SliceTokenTypeIdentifierFromVaultType(vaultTypeIdentifier: self.token1Type.identifier)
249            if (!reverse) {
250                // Calculate how much to zap from token0 to token1
251                let zappedAmount = self.calculateZappedAmount(forProvided: forProvided, pairPublicRef: pairPublicRef)
252
253                // Calculate how much we get after swapping zappedAmount of token0 to token1
254                let swappedAmount = pairPublicRef.getAmountOut(amountIn: zappedAmount, tokenInKey: token0Key)
255
256                // Calculate lp tokens we're receiving
257                let lpAmount = self.calculateLpAmount(
258                    token0Amount: forProvided - zappedAmount,
259                    token1Amount: swappedAmount,
260                    token0Offset: zappedAmount,
261                    token1Offset: swappedAmount,
262                    pairPublicRef: pairPublicRef
263                )
264
265                return SwapConnectors.BasicQuote(
266                    inType: self.inType(),
267                    outType: self.outType(),
268                    inAmount: forProvided,
269                    outAmount: lpAmount
270                )
271            } else {
272                // Reverse operation: calculate how much token0Vault you get when providing LP tokens
273
274                let lpSupply = pairPublicRef.getPairInfo()[5] as! UFix64
275                // Unachievable
276                if forProvided > lpSupply {
277                    return SwapConnectors.BasicQuote(
278                        inType: self.inType(),
279                        outType: self.outType(),
280                        inAmount: forProvided,
281                        outAmount: 0.0
282                    )
283                }
284
285                // Calculate how much token0 and token1 you get from removing liquidity
286                let tokenAmounts = self.calculateTokenAmountsFromLp(lpAmount: forProvided, pairPublicRef: pairPublicRef)
287                let token0Amount = tokenAmounts[0]
288                let token1Amount = tokenAmounts[1]
289
290                // Calculate how much token0 you get when swapping token1 back to token0
291                let swappedToken0Amount = self.calculateSwapAmount(
292                    amountIn: token1Amount,
293                    token0Offset: -Fix64(token0Amount),
294                    token1Offset: -Fix64(token1Amount),
295                    pairPublicRef: pairPublicRef,
296                    reverse: true
297                )
298
299                // Total token0 amount = direct token0 + swapped token0
300                let totalToken0Amount = token0Amount + swappedToken0Amount
301
302                return SwapConnectors.BasicQuote(
303                    inType: self.outType(), // LP token type
304                    outType: self.inType(), // token0 type
305                    inAmount: forProvided,
306                    outAmount: totalToken0Amount
307                )
308            }
309        }
310
311        /// Converts inToken to LP token
312        access(all) fun swap(quote: {DeFiActions.Quote}?, inVault: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
313            let pairPublicRef = self.getPairPublicRef()
314            let zappedAmount = self.calculateZappedAmount(forProvided: inVault.balance, pairPublicRef: pairPublicRef)
315
316            // Swap
317            let swapVaultIn <- inVault.withdraw(amount: zappedAmount)
318            let token1Vault <- pairPublicRef.swap(vaultIn: <-swapVaultIn, exactAmountOut: nil)
319
320            // Add liquidity
321            let lpTokenVault <- pairPublicRef.addLiquidity(
322                tokenAVault: <- inVault,
323                tokenBVault: <- token1Vault
324            )
325
326            // Return the LP token vault
327            return <-lpTokenVault
328        }
329
330        /// Converts back LP token to inToken
331        access(all) fun swapBack(quote: {DeFiActions.Quote}?, residual: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
332            let pairPublicRef = self.getPairPublicRef()
333
334            // Remove liquidity
335            let tokens <- pairPublicRef.removeLiquidity(lpTokenVault: <-residual)
336            let token0Vault <- tokens[0].withdraw(amount: tokens[0].balance)
337            let token1Vault <- tokens[1].withdraw(amount: tokens[1].balance)
338            destroy tokens
339
340            // Swap token1 to token0
341            let swappedVault <- pairPublicRef.swap(vaultIn: <-token1Vault, exactAmountOut: nil)
342            token0Vault.deposit(from: <-swappedVault)
343
344            return <-token0Vault
345        }
346
347        /// Returns a reference to the pair public interface
348        access(self) view fun getPairPublicRef(): &{SwapInterfaces.PairPublic} {
349            return getAccount(self.pairAddress)
350                .capabilities.borrow<&{SwapInterfaces.PairPublic}>(SwapConfig.PairPublicPath)!
351        }
352
353        /// Calculates the zapped amount for a given provided amount
354        /// This amount is swapped from token A to token B in order to add liquidity to the pool
355        ///
356        /// Based on https://github.com/IncrementFi/Swap/blob/main/src/scripts/query/query_zapped_amount.cdc
357        ///
358        /// @param forProvided: the total amount of the input token0
359        /// @param pairPublicRef: a reference to the pair public interface
360        ///
361        /// @return the amount of token0 to convert to token1 before adding liquidity
362        ///
363        access(self) view fun calculateZappedAmount(
364            forProvided: UFix64,
365            pairPublicRef: &{SwapInterfaces.PairPublic},
366        ): UFix64 {
367            let pairInfo = pairPublicRef.getPairInfo()
368            let tokenReserves = self.getTokenReserves(pairPublicRef: pairPublicRef)
369            var token0Reserve = tokenReserves[0]
370            var token1Reserve = tokenReserves[1]
371            assert(token0Reserve != 0.0, message: "Cannot add liquidity zapped in a new pool.")
372            var zappedAmount = 0.0
373            if !self.stableMode {
374                // Cal optimized zapped amount through dex
375                let r0Scaled = SwapConfig.UFix64ToScaledUInt256(token0Reserve)
376                let swapFeeRateBps = pairInfo[6] as! UInt64
377                let fee = 1.0 - UFix64(swapFeeRateBps)/10000.0
378                let kplus1SquareScaled = SwapConfig.UFix64ToScaledUInt256((1.0+fee)*(1.0+fee))
379                let kScaled = SwapConfig.UFix64ToScaledUInt256(fee)
380                let kplus1Scaled = SwapConfig.UFix64ToScaledUInt256(fee+1.0)
381                let token0InScaled = SwapConfig.UFix64ToScaledUInt256(forProvided)
382                let qScaled = SwapConfig.sqrt(
383                    r0Scaled * r0Scaled / SwapConfig.scaleFactor * kplus1SquareScaled / SwapConfig.scaleFactor
384                    + 4 * kScaled * r0Scaled / SwapConfig.scaleFactor * token0InScaled / SwapConfig.scaleFactor)
385                zappedAmount = SwapConfig.ScaledUInt256ToUFix64(
386                    (qScaled - r0Scaled*kplus1Scaled/SwapConfig.scaleFactor)*SwapConfig.scaleFactor/(kScaled*2)
387                )
388            } else {
389                var desiredZappedAmount = 0.0
390                let reserve0Scaled = SwapConfig.UFix64ToScaledUInt256(token0Reserve)
391                let reserve1Scaled = SwapConfig.UFix64ToScaledUInt256(token1Reserve)
392                let forProvidedScaled = SwapConfig.UFix64ToScaledUInt256(forProvided)
393                if (token0Reserve > token1Reserve) {
394                    desiredZappedAmount = SwapConfig.ScaledUInt256ToUFix64(
395                        forProvidedScaled * reserve1Scaled / reserve0Scaled
396                    )
397                } else {
398                    desiredZappedAmount = SwapConfig.ScaledUInt256ToUFix64(
399                        forProvidedScaled * reserve0Scaled / reserve1Scaled
400                    )
401                }
402                let token0Key = self.token0Type.identifier
403                var desiredAmountOut = pairPublicRef.getAmountOut(amountIn: desiredZappedAmount, tokenInKey: token0Key)
404                var propAmountOut = 0.0
405                var minAmount = SwapConfig.ufix64NonZeroMin
406                var maxAmount = forProvided - SwapConfig.ufix64NonZeroMin
407                var midAmount = 0.0
408                if desiredAmountOut <= token1Reserve {
409                    propAmountOut = (forProvided - desiredZappedAmount) / (token0Reserve + desiredZappedAmount) * (token1Reserve - desiredAmountOut)
410                    var bias = 0.0
411                    if (desiredAmountOut > propAmountOut) {
412                        bias = desiredAmountOut - propAmountOut
413                    } else {
414                        bias = propAmountOut - desiredAmountOut
415                    }
416                    if (bias <= 0.0001) {
417                        return desiredZappedAmount
418                    } else {
419                        if (desiredAmountOut > propAmountOut) {
420                            maxAmount = desiredZappedAmount
421                        } else {
422                            minAmount = desiredZappedAmount
423                        }
424                    }
425                } else {
426                    maxAmount = desiredZappedAmount
427                }
428                var epoch = 0
429                while (epoch < 36) {
430                    midAmount = minAmount * 0.5 + maxAmount * 0.5;
431                    if maxAmount - midAmount < SwapConfig.ufix64NonZeroMin {
432                        break
433                    }
434                    let amountOut = pairPublicRef.getAmountOut(amountIn: midAmount, tokenInKey: token0Key)
435                    let reserveAft0 = token0Reserve + midAmount
436                    if amountOut <= token1Reserve {
437                        let reserveAft1 = token1Reserve - amountOut
438                        let ratioUser = (forProvided - midAmount) / amountOut
439                        let ratioPool = reserveAft0 / reserveAft1
440                        var ratioBias = 0.0
441                        if (ratioUser >= ratioPool) {
442                            if (ratioUser - ratioPool) <= SwapConfig.ufix64NonZeroMin {
443                                break
444                            }
445                            minAmount = midAmount
446                        } else {
447                            if (ratioPool - ratioUser) <= SwapConfig.ufix64NonZeroMin {
448                                break
449                            }
450                            maxAmount = midAmount
451                        }
452                    } else {
453                        maxAmount = midAmount
454                    }
455
456                    epoch = epoch + 1
457                }
458                zappedAmount = midAmount
459            }
460            return zappedAmount
461        }
462
463        /// Calculates the amount of LP tokens received for a given token0Amount and token1Amount
464        ///
465        /// Based on "addLiquidity" function in https://github.com/IncrementFi/Swap/blob/main/src/contracts/SwapPair.cdc
466        ///
467        /// @param token0Amount: the amount of token0 to add to the pool
468        /// @param token1Amount: the amount of token1 to add to the pool
469        /// @param token0Offset: the offset of token0 reserves, used to simulate the impact of a swap on the reserves (added)
470        /// @param token1Offset: the offset of token1 reserves, used to simulate the impact of a swap on the reserves (subtracted)
471        /// @param pairPublicRef: a reference to the pair public interface
472        ///
473        /// @return the amount of LP tokens received
474        ///
475        access(self) view fun calculateLpAmount(
476            token0Amount: UFix64,
477            token1Amount: UFix64,
478            token0Offset: UFix64,
479            token1Offset: UFix64,
480            pairPublicRef: &{SwapInterfaces.PairPublic},
481        ): UFix64 {
482            let pairInfo = pairPublicRef.getPairInfo()
483            let tokenReserves = self.getTokenReserves(pairPublicRef: pairPublicRef)
484            var token0Reserve = tokenReserves[0]
485            var token1Reserve = tokenReserves[1]
486
487            // Note: simulate zap swap impact on reserves
488            // Zapping always swaps token0 -> token1
489            token0Reserve = token0Reserve + token0Offset
490            token1Reserve = token1Reserve - token1Offset
491
492            let reserve0LastScaled = SwapConfig.UFix64ToScaledUInt256(token0Reserve)
493            let reserve1LastScaled = SwapConfig.UFix64ToScaledUInt256(token1Reserve)
494
495            let lpTokenSupply = pairInfo[5] as! UFix64
496
497            var liquidity = 0.0
498            var amount0Added = 0.0
499            var amount1Added = 0.0
500            if (token0Reserve == 0.0 && token1Reserve == 0.0) {
501                var donateLpBalance = 0.0
502                if self.stableMode {
503                    donateLpBalance = 0.0001    // 1e-4
504                } else {
505                    donateLpBalance = 0.000001  // 1e-6
506                }
507                // When adding initial liquidity, the balance should not be below certain minimum amount
508                assert(token0Amount > donateLpBalance && token1Amount > donateLpBalance, message:
509                    "Token0 and token1 amounts must be greater than minimum donation amount"
510                )
511                /// Calculate rootK
512                let e18: UInt256 = SwapConfig.scaleFactor
513                let balance0Scaled = SwapConfig.UFix64ToScaledUInt256(token0Amount)
514                let balance1Scaled = SwapConfig.UFix64ToScaledUInt256(token1Amount)
515                var initialLpAmount = 0.0
516                if self.stableMode {
517                    let _p_scaled: UInt256 = SwapConfig.UFix64ToScaledUInt256(1.0)
518                    let _k_scaled: UInt256 = SwapConfig.k_stable_p(balance0Scaled, balance1Scaled, _p_scaled)
519                    initialLpAmount = SwapConfig.ScaledUInt256ToUFix64(SwapConfig.sqrt(SwapConfig.sqrt(_k_scaled / 2)))
520                } else {
521                    initialLpAmount = SwapConfig.ScaledUInt256ToUFix64(SwapConfig.sqrt(balance0Scaled * balance1Scaled / e18))
522                }
523                liquidity = initialLpAmount - donateLpBalance
524            } else {
525                var lptokenMintAmount0Scaled: UInt256 = 0
526                var lptokenMintAmount1Scaled: UInt256 = 0
527
528                /// Use UFIx64ToUInt256 in division & multiply to solve precision issues
529                let inAmountAScaled = SwapConfig.UFix64ToScaledUInt256(token0Amount)
530                let inAmountBScaled = SwapConfig.UFix64ToScaledUInt256(token1Amount)
531
532                let totalSupplyScaled = SwapConfig.UFix64ToScaledUInt256(lpTokenSupply)
533
534                lptokenMintAmount0Scaled = inAmountAScaled * totalSupplyScaled / reserve0LastScaled
535                lptokenMintAmount1Scaled = inAmountBScaled * totalSupplyScaled / reserve1LastScaled
536
537                /// Note: User should add proportional liquidity as any extra is added into pool.
538                let mintLptokenAmountScaled = lptokenMintAmount0Scaled < lptokenMintAmount1Scaled ? lptokenMintAmount0Scaled : lptokenMintAmount1Scaled
539                liquidity = SwapConfig.ScaledUInt256ToUFix64(mintLptokenAmountScaled)
540            }
541            return liquidity
542        }
543
544        /// Calculates the amount of token0 and token1 you get when removing liquidity with a given LP amount
545        ///
546        /// Based on "removeLiquidity" function in https://github.com/IncrementFi/Swap/blob/main/src/contracts/SwapPair.cdc
547        ///
548        /// @param lpAmount: the amount of LP tokens to remove
549        /// @param pairPublicRef: a reference to the pair public interface
550        ///
551        /// @return an array where [0] = token0Amount, [1] = token1Amount
552        ///
553        access(self) view fun calculateTokenAmountsFromLp(
554            lpAmount: UFix64,
555            pairPublicRef: &{SwapInterfaces.PairPublic}
556        ): [UFix64; 2] {
557            let pairInfo = pairPublicRef.getPairInfo()
558            let tokenReserves = self.getTokenReserves(pairPublicRef: pairPublicRef)
559            let token0Reserve = SwapConfig.UFix64ToScaledUInt256(tokenReserves[0])
560            let token1Reserve = SwapConfig.UFix64ToScaledUInt256(tokenReserves[1])
561
562            let lpTokenSupply = pairInfo[5] as! UFix64
563
564            // Calculate proportional amounts based on LP share
565            let lpAmountScaled = SwapConfig.UFix64ToScaledUInt256(lpAmount)
566            let lpTokenSupplyScaled = SwapConfig.UFix64ToScaledUInt256(lpTokenSupply)
567            let token0Amount = SwapConfig.ScaledUInt256ToUFix64(token0Reserve * lpAmountScaled / lpTokenSupplyScaled)
568            let token1Amount = SwapConfig.ScaledUInt256ToUFix64(token1Reserve * lpAmountScaled / lpTokenSupplyScaled)
569
570            return [token0Amount, token1Amount]
571        }
572
573        /// Returns the reserves of the token0 and token1 in the pair
574        ///
575        /// @param pairPublicRef: a reference to the pair public interface
576        ///
577        /// @return an array where [0] = token0Reserve, [1] = token1Reserve
578        ///
579        access(self) view fun getTokenReserves(
580            pairPublicRef: &{SwapInterfaces.PairPublic}
581        ): [UFix64; 2] {
582            let pairInfo = pairPublicRef.getPairInfo()
583            var token0Reserve = 0.0
584            var token1Reserve = 0.0
585            let token0Key = SwapConfig.SliceTokenTypeIdentifierFromVaultType(vaultTypeIdentifier: self.token0Type.identifier)
586            if token0Key == (pairInfo[0] as! String) {
587                token0Reserve = (pairInfo[2] as! UFix64)
588                token1Reserve = (pairInfo[3] as! UFix64)
589            } else {
590                token0Reserve = (pairInfo[3] as! UFix64)
591                token1Reserve = (pairInfo[2] as! UFix64)
592            }
593            return [token0Reserve, token1Reserve]
594        }
595
596        /// Calculates the amount of token0 received when swapping token1 to token0 with custom reserve values
597        /// If reverse is true, the amountIn is token1Amount
598        /// If reverse is false, the amountIn is token0Amount
599        ///
600        /// @param amountIn: the amount of the input token
601        /// @param token0Offset: the offset of token0 reserves, used to simulate the impact of a swap on the reserves
602        /// @param token1Offset: the offset of token1 reserves, used to simulate the impact of a swap on the reserves
603        /// @param pairPublicRef: a reference to the pair public interface
604        /// @param reverse: if reverse is true, the amountIn is token1Amount
605        ///                 if reverse is false, the amountIn is token0Amount
606        ///
607        /// @return the amount out of the swap operation
608        ///
609        access(self) view fun calculateSwapAmount(
610            amountIn: UFix64,
611            token0Offset: Fix64,
612            token1Offset: Fix64,
613            pairPublicRef: &{SwapInterfaces.PairPublic},
614            reverse: Bool
615        ): UFix64 {
616
617            let pairInfo = pairPublicRef.getPairInfo()
618            let tokenReserves = self.getTokenReserves(pairPublicRef: pairPublicRef)
619            var token0Reserve = tokenReserves[0]
620            var token1Reserve = tokenReserves[1]
621
622            // Note: simulate zap swap impact on reserves
623            // Handle negative offsets carefully to prevent underflow
624            let token0ReserveWithOffset = Fix64(token0Reserve) + token0Offset
625            let token1ReserveWithOffset = Fix64(token1Reserve) + token1Offset
626
627            // Insufficient liquidity
628            if token0ReserveWithOffset <= 0.0 || token1ReserveWithOffset <= 0.0 {
629                return 0.0
630            }
631
632            // Ensure reserves don't go below minimum values
633            token0Reserve = UFix64(token0ReserveWithOffset)
634            token1Reserve = UFix64(token1ReserveWithOffset)
635
636            var swappedToken0Amount = 0.0
637            if (self.stableMode) {
638                swappedToken0Amount = SwapConfig.getAmountOutStable(
639                    amountIn: amountIn,
640                    reserveIn: reverse ? token1Reserve : token0Reserve,
641                    reserveOut: reverse ? token0Reserve : token1Reserve,
642                    p: pairInfo[8] as! UFix64,
643                    swapFeeRateBps: pairInfo[6] as! UInt64
644                )
645            } else {
646                swappedToken0Amount = SwapConfig.getAmountOutVolatile(
647                    amountIn: amountIn,
648                    reserveIn: reverse ? token1Reserve : token0Reserve,
649                    reserveOut: reverse ? token0Reserve : token1Reserve,
650                    swapFeeRateBps: pairInfo[6] as! UInt64
651                )
652            }
653            return swappedToken0Amount
654        }
655
656        // Returns the maximum amount of LP tokens that can be minted
657        // It's bound by the reserves of token1 that can be swapped to token0
658        access(self) fun getMaxLpMintAmount(
659            pairPublicRef: &{SwapInterfaces.PairPublic},
660        ): UFix64 {
661            let quote = self.quoteOut(forProvided: UFix64.max, reverse: false)
662            return quote.outAmount
663        }
664
665        // Returns the maximum amount of token0 that can be returned by providing all LP tokens
666        access(self) fun getMaxToken0Returned(
667            pairPublicRef: &{SwapInterfaces.PairPublic},
668        ): UFix64 {
669            let pairInfo = pairPublicRef.getPairInfo()
670            let lpTokenSupply = pairInfo[5] as! UFix64
671            let quote = self.quoteOut(forProvided: lpTokenSupply - SwapConfig.ufix64NonZeroMin, reverse: true)
672            return quote.outAmount
673        }
674    }
675
676}
677