Smart Contract

SwapPair

A.c353b9d685ec427d.SwapPair

Deployed

1w ago
Feb 16, 2026, 08:43:19 AM UTC

Dependents

3 imports
1/**
2
3# SwapPair
4
5# Author: Increment Labs
6
7*/
8
9import DelegatorManager from 0xd6f80565193ad727
10import FungibleToken from 0xf233dcee88fe0abe
11import SwapInterfaces from 0xb78ef7afa52ff906
12import SwapConfig from 0xb78ef7afa52ff906
13import SwapError from 0xb78ef7afa52ff906
14import SwapFactory from 0xb063c16cac85dbd1
15
16
17access(all) contract SwapPair: FungibleToken {
18    /// Total supply of pair lpTokens in existence
19    access(all) var totalSupply: UFix64
20    
21    /// Two vaults for the trading pair.
22    access(self) let token0Vault: @{FungibleToken.Vault}
23    access(self) let token1Vault: @{FungibleToken.Vault}
24    access(all) let token0VaultType: Type
25    access(all) let token1VaultType: Type
26    access(all) let token0Key: String
27    access(all) let token1Key: String
28    
29    /// TWAP: last cumulative price
30    access(all) var blockTimestampLast: UFix64
31    access(all) var price0CumulativeLastScaled: UInt256
32    access(all) var price1CumulativeLastScaled: UInt256
33
34    /// Transaction lock 
35    access(self) var lock: Bool
36    
37    /// √(reserve0 * reserve1) for volatile pool, or √√[(r0^3 * r1 + r0 * r1^3) / 2] for stable pool, as of immediately after the most recent liquidity event
38    access(all) var rootKLast: UFix64
39
40    /// Reserved parameter fields: {ParamName: Value}
41    /// Used fields:
42    ///   |__ 1. "isStableSwap" -> Bool
43    access(self) let _reservedFields: {String: AnyStruct}
44
45    /// Event that is emitted when the contract is created
46    access(all) event TokensInitialized(initialSupply: UFix64)
47    /// Event that is emitted when lp tokens are minted
48    access(all) event TokensMinted(amount: UFix64)
49    /// Event that is emitted when lp tokens are destroyed
50    access(all) event TokensBurned(amount: UFix64)
51    /// Event that is emitted when liquidity is added
52    access(all) event LiquidityAdded(amount0: UFix64, amount1: UFix64, reserve0After: UFix64, reserve1After: UFix64, amount0Type: String, amount1Type: String)
53    /// Event that is emitted when liquidity is removed
54    access(all) event LiquidityRemoved(amount0: UFix64, amount1: UFix64, reserve0After: UFix64, reserve1After: UFix64, amount0Type: String, amount1Type: String)
55    /// Event that is emitted when a swap trade happenes to this trading pair
56    access(all) event Swap(amount0In: UFix64, amount1In: UFix64, amount0Out: UFix64, amount1Out: UFix64, reserve0After: UFix64, reserve1After: UFix64, amount0Type: String, amount1Type: String)
57    /// Event that is emitted when a flashloan is originated from this SwapPair pool
58    access(all) event Flashloan(executor: Address, executorType: Type, originator: Address, requestedTokenType: String, requestedAmount: UFix64, reserve0After: UFix64, reserve1After: UFix64)
59
60    /// Lptoken Vault
61    ///
62    access(all) resource Vault: FungibleToken.Vault {
63        /// Holds the balance of a users tokens
64        access(all) var balance: UFix64
65
66        /// Initialize the balance at resource creation time
67        init(balance: UFix64) {
68            self.balance = balance
69        }
70
71        /// Called when this SwapPair's LpToken is burned via the `Burner.burn()` method
72        access(contract) fun burnCallback() {
73            if self.balance > 0.0 {
74                SwapPair.totalSupply = SwapPair.totalSupply - self.balance
75            }
76            self.balance = 0.0
77        }
78
79        /// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts
80        access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
81            return {self.getType(): true}
82        }
83
84        access(all) view fun isSupportedVaultType(type: Type): Bool {
85            if (type == self.getType()) { return true } else { return false }
86        }
87
88        /// Asks if the amount can be withdrawn from this vault
89        access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
90            return amount <= self.balance
91        }
92
93        /// Added simply to conform to FT-V2 interface.
94        ///
95        /// SwapPair doesn't support MetadataViews api as this is not just a normal FT, and there could be multiple types of lpTokens
96        /// for different SwapPairs, so it is stored in LpTokenCollection instead (not directly associated with a StoragePath).
97        access(all) view fun getViews(): [Type] { return [] }
98        access(all) fun resolveView(_ view: Type): AnyStruct? { return nil }
99
100        /// withdraw
101        ///
102        /// Function that takes an integer amount as an argument
103        /// and withdraws that amount from the Vault.
104        /// It creates a new temporary Vault that is used to hold
105        /// the money that is being transferred. It returns the newly
106        /// created Vault to the context that called so it can be deposited
107        /// elsewhere.
108        ///
109        access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @{FungibleToken.Vault} {
110            self.balance = self.balance - amount
111            return <-create Vault(balance: amount)
112        }
113
114        /// deposit
115        ///
116        /// Function that takes a Vault object as an argument and adds
117        /// its balance to the balance of the owners Vault.
118        /// It is allowed to destroy the sent Vault because the Vault
119        /// was a temporary holder of the tokens. The Vault's balance has
120        /// been consumed and therefore can be destroyed.
121        ///
122        access(all) fun deposit(from: @{FungibleToken.Vault}) {
123            let vault <- from as! @SwapPair.Vault
124            self.balance = self.balance + vault.balance
125            vault.balance = 0.0
126            destroy vault
127        }
128
129        access(all) fun createEmptyVault(): @{FungibleToken.Vault} {
130            return <-create Vault(balance: 0.0)
131        }
132    }
133
134    access(all) struct PairInfo: SwapInterfaces.PairInfo {
135        access(all) let token0Key: String
136        access(all) let token1Key: String
137        access(all) let token0Reserve: UFix64
138        access(all) let token1Reserve: UFix64
139        access(all) let pairAddr: Address
140        access(all) let lpTokenSupply: UFix64
141        access(all) let swapFeeInBps: UInt64
142        access(all) let isStableswap: Bool
143        access(all) let stableCurveP: UFix64
144
145        view init(
146            _ token0Key: String,
147            _ token1Key: String,
148            _ token0Reserve: UFix64,
149            _ token1Reserve: UFix64,
150            _ pairAddr: Address,
151            _ lpTokenSupply: UFix64,
152            _ swapFeeInBps: UInt64,
153            _ isStableswap: Bool,
154            _ stableCurveP: UFix64
155        ) {
156            self.token0Key = token0Key
157            self.token1Key = token1Key
158            self.token0Reserve = token0Reserve
159            self.token1Reserve = token1Reserve
160            self.pairAddr = pairAddr
161            self.lpTokenSupply = lpTokenSupply
162            self.swapFeeInBps = swapFeeInBps
163            self.isStableswap = isStableswap
164            self.stableCurveP = stableCurveP
165        }
166    }
167
168    /// Added simply to conform to FT-V2 interface.
169    ///
170    /// SwapPair doesn't support MetadataViews api as this is not just a normal FT, and there could be multiple types of lpTokens
171    /// for different SwapPairs, so it is stored in LpTokenCollection instead (not directly associated with a StoragePath).
172    access(all) view fun getContractViews(resourceType: Type?): [Type] { return [] }
173    access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? { return nil }
174
175    /// createEmptyVault
176    //
177    /// Function that creates a new Vault with a balance of zero
178    /// and returns it to the calling context. A user must call this function
179    /// and store the returned Vault in their storage in order to allow their
180    /// account to be able to receive deposits of this token type.
181    ///
182    access(all) fun createEmptyVault(vaultType: Type): @SwapPair.Vault {
183        return <-create Vault(balance: 0.0)
184    }
185
186    /// Permanently lock the first small amount lpTokens
187    access(self) fun donateInitialMinimumLpToken(donateLpBalance: UFix64) {
188        self.totalSupply = self.totalSupply + donateLpBalance
189        emit TokensMinted(amount: donateLpBalance)
190    }
191
192    /// Mint lpTokens
193    access(self) fun mintLpToken(amount: UFix64): @SwapPair.Vault {
194        self.totalSupply = self.totalSupply + amount
195        emit TokensMinted(amount: amount)
196        return <- create Vault(balance: amount)
197    } 
198
199    /// Burn lpTokens
200    access(self) fun burnLpToken(from: @SwapPair.Vault) {
201        let amount = from.balance
202        self.totalSupply = self.totalSupply - amount
203        destroy from
204        emit TokensBurned(amount: amount)
205    }
206
207    /// Add liquidity
208    ///
209    access(all) fun addLiquidity(tokenAVault: @{FungibleToken.Vault}, tokenBVault: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
210        pre {
211            tokenAVault.balance > 0.0 && tokenBVault.balance > 0.0:
212                SwapError.ErrorEncode(
213                    msg: "SwapPair: added zero liquidity",
214                    err: SwapError.ErrorCode.ADD_ZERO_LIQUIDITY
215                )
216            (tokenAVault.isInstance(self.token0VaultType) && tokenBVault.isInstance(self.token1VaultType)) || 
217            (tokenBVault.isInstance(self.token0VaultType) && tokenAVault.isInstance(self.token1VaultType)):
218                SwapError.ErrorEncode(
219                    msg: "SwapPair: added incompatible liquidity pair vaults",
220                    err: SwapError.ErrorCode.INVALID_PARAMETERS
221                )
222            self.lock == false: SwapError.ErrorEncode(msg: "SwapPair: Reentrant", err: SwapError.ErrorCode.REENTRANT)
223        }
224        post {
225            self.lock == false: SwapError.ErrorEncode(msg: "SwapPair: Reentrant", err: SwapError.ErrorCode.REENTRANT)
226        }
227        self.lock = true
228
229        let reserve0LastScaled = SwapConfig.UFix64ToScaledUInt256(self.token0Vault.balance)
230        let reserve1LastScaled = SwapConfig.UFix64ToScaledUInt256(self.token1Vault.balance)
231
232        /// Update twap at the first transaction in one block with the last block balance
233        self._update(reserve0Last: self.token0Vault.balance, reserve1Last: self.token1Vault.balance)
234        /// Mint fee
235        let feeOn = self._mintFee(reserve0: self.token0Vault.balance, reserve1: self.token1Vault.balance)
236
237        var liquidity = 0.0
238        var amount0Added = 0.0
239        var amount1Added = 0.0
240        if (self.totalSupply == 0.0) {
241            var donateLpBalance = 0.0
242            if self.isStableSwap() {
243                donateLpBalance = 0.0001    // 1e-4
244            } else {
245                donateLpBalance = 0.000001  // 1e-6
246            }
247            // When adding initial liquidity, the balance should not be below certain minimum amount
248            assert(tokenAVault.balance > donateLpBalance && tokenBVault.balance > donateLpBalance, message:
249                SwapError.ErrorEncode(
250                    msg: "SwapPair: less than initial minimum liquidity",
251                    err: SwapError.ErrorCode.BELOW_MINIMUM_INITIAL_LIQUIDITY
252                )
253            )
254            /// Add initial liquidity
255            if (tokenAVault.isInstance(self.token0VaultType)) {
256                amount0Added = tokenAVault.balance
257                amount1Added = tokenBVault.balance
258                self.token0Vault.deposit(from: <-tokenAVault)
259                self.token1Vault.deposit(from: <-tokenBVault)
260            } else {
261                amount0Added = tokenBVault.balance
262                amount1Added = tokenAVault.balance
263                self.token0Vault.deposit(from: <-tokenBVault)
264                self.token1Vault.deposit(from: <-tokenAVault)
265            }
266            /// Mint initial liquidity token and donate initial minimum liquidity token
267            let initialLpAmount = self._rootK(balance0: self.token0Vault.balance, balance1: self.token1Vault.balance)
268            self.donateInitialMinimumLpToken(donateLpBalance: donateLpBalance)
269
270            liquidity = initialLpAmount - donateLpBalance
271        } else {
272            var lptokenMintAmount0Scaled: UInt256 = 0
273            var lptokenMintAmount1Scaled: UInt256 = 0
274            /// Use UFIx64ToUInt256 in division & multiply to solve precision issues
275            let inAmountAScaled = SwapConfig.UFix64ToScaledUInt256(tokenAVault.balance)
276            let inAmountBScaled = SwapConfig.UFix64ToScaledUInt256(tokenBVault.balance)
277
278            let totalSupplyScaled = SwapConfig.UFix64ToScaledUInt256(self.totalSupply)
279
280            if (tokenAVault.isInstance(self.token0VaultType)) {
281                lptokenMintAmount0Scaled = inAmountAScaled * totalSupplyScaled / reserve0LastScaled
282                lptokenMintAmount1Scaled = inAmountBScaled * totalSupplyScaled / reserve1LastScaled
283                amount0Added = tokenAVault.balance
284                amount1Added = tokenBVault.balance
285                self.token0Vault.deposit(from: <-tokenAVault)
286                self.token1Vault.deposit(from: <-tokenBVault)
287            } else {
288                lptokenMintAmount0Scaled = inAmountBScaled * totalSupplyScaled / reserve0LastScaled
289                lptokenMintAmount1Scaled = inAmountAScaled * totalSupplyScaled / reserve1LastScaled
290                amount0Added = tokenBVault.balance
291                amount1Added = tokenAVault.balance
292                self.token0Vault.deposit(from: <-tokenBVault)
293                self.token1Vault.deposit(from: <-tokenAVault)
294            }
295
296            /// Note: User should add proportional liquidity as any extra is added into pool.
297            let mintLptokenAmountScaled = lptokenMintAmount0Scaled < lptokenMintAmount1Scaled ? lptokenMintAmount0Scaled : lptokenMintAmount1Scaled
298
299            /// Mint liquidity token pro rata
300            liquidity = SwapConfig.ScaledUInt256ToUFix64(mintLptokenAmountScaled)
301        }
302        /// Mint lpTokens
303        let lpTokenVault <-self.mintLpToken(amount: liquidity)
304        emit LiquidityAdded(
305            amount0: amount0Added, amount1: amount1Added,
306            reserve0After: self.token0Vault.balance, reserve1After: self.token1Vault.balance,
307            amount0Type: self.token0Key, amount1Type: self.token1Key
308        )
309
310        if feeOn {
311            self.rootKLast = self._rootK(balance0: self.token0Vault.balance, balance1: self.token1Vault.balance)
312        }
313
314        self.lock = false
315        return <-lpTokenVault
316    }
317
318    /// Remove Liquidity
319    ///
320    /// @Return: @[FungibleToken.Vault; 2]
321    ///
322    access(all) fun removeLiquidity(lpTokenVault: @{FungibleToken.Vault}) : @[{FungibleToken.Vault}] {
323        pre {
324            lpTokenVault.balance > 0.0:
325                SwapError.ErrorEncode(
326                    msg: "SwapPair: removed zero liquidity",
327                    err: SwapError.ErrorCode.INVALID_PARAMETERS
328                )
329            lpTokenVault.isInstance(Type<@SwapPair.Vault>()):
330                SwapError.ErrorEncode(
331                    msg: "SwapPair: input vault type mismatch with lpTokenVault type",
332                    err: SwapError.ErrorCode.MISMATCH_LPTOKEN_VAULT
333                )
334            self.lock == false: SwapError.ErrorEncode(msg: "SwapPair: Reentrant", err: SwapError.ErrorCode.REENTRANT)
335        }
336        post {
337            self.lock == false: SwapError.ErrorEncode(msg: "SwapPair: Reentrant", err: SwapError.ErrorCode.REENTRANT)
338        }
339        self.lock = true
340
341        let reserve0LastScaled = SwapConfig.UFix64ToScaledUInt256(self.token0Vault.balance)
342        let reserve1LastScaled = SwapConfig.UFix64ToScaledUInt256(self.token1Vault.balance)
343
344        /// Update twap
345        self._update(reserve0Last: self.token0Vault.balance, reserve1Last: self.token1Vault.balance)
346        /// Mint fee
347        let feeOn = self._mintFee(reserve0: self.token0Vault.balance, reserve1: self.token1Vault.balance)
348
349        /// Use UFIx64ToUInt256 in division & multiply to solve precision issues
350        let removeAmountScaled = SwapConfig.UFix64ToScaledUInt256(lpTokenVault.balance)
351        let totalSupplyScaled = SwapConfig.UFix64ToScaledUInt256(self.totalSupply)
352
353        let token0AmountScaled = removeAmountScaled * reserve0LastScaled / totalSupplyScaled
354        let token1AmountScaled = removeAmountScaled * reserve1LastScaled / totalSupplyScaled
355        let token0Amount = SwapConfig.ScaledUInt256ToUFix64(token0AmountScaled)
356        let token1Amount = SwapConfig.ScaledUInt256ToUFix64(token1AmountScaled)
357
358        let withdrawnToken0 <- self.token0Vault.withdraw(amount: token0Amount)
359        let withdrawnToken1 <- self.token1Vault.withdraw(amount: token1Amount)
360
361        /// Burn lpTokens
362        self.burnLpToken(from: <- (lpTokenVault as! @SwapPair.Vault))
363        emit LiquidityRemoved(
364            amount0: withdrawnToken0.balance, amount1: withdrawnToken1.balance,
365            reserve0After: self.token0Vault.balance, reserve1After: self.token1Vault.balance,
366            amount0Type: self.token0Key, amount1Type: self.token1Key
367        )
368
369        if feeOn {
370            self.rootKLast = self._rootK(balance0: self.token0Vault.balance, balance1: self.token1Vault.balance)
371        }
372
373        self.lock = false
374        return <- [<-withdrawnToken0, <-withdrawnToken1]
375    }
376
377    /// Swap
378    ///
379    access(all) fun swap(vaultIn: @{FungibleToken.Vault}, exactAmountOut: UFix64?): @{FungibleToken.Vault} {
380        pre {
381            vaultIn.balance > 0.0: SwapError.ErrorEncode(msg: "SwapPair: zero in balance", err: SwapError.ErrorCode.INVALID_PARAMETERS)
382            vaultIn.isInstance(self.token0VaultType) || vaultIn.isInstance(self.token1VaultType):
383                SwapError.ErrorEncode(
384                    msg: "SwapPair: incompatible in token vault",
385                    err: SwapError.ErrorCode.INVALID_PARAMETERS
386                )
387            self.lock == false: SwapError.ErrorEncode(msg: "SwapPair: Reentrant", err: SwapError.ErrorCode.REENTRANT)
388        }
389        post {
390            self.lock == false: SwapError.ErrorEncode(msg: "SwapPair: Reentrant", err: SwapError.ErrorCode.REENTRANT)
391        }
392        self.lock = true
393
394        self._update(reserve0Last: self.token0Vault.balance, reserve1Last: self.token1Vault.balance)
395
396        let amountIn = vaultIn.balance
397        var amountOut = 0.0
398        /// Calculate the swap result
399        if (vaultIn.isInstance(self.token0VaultType)) {
400            if self.isStableSwap() {
401                amountOut = SwapConfig.getAmountOutStable(amountIn: amountIn, reserveIn: self.token0Vault.balance, reserveOut: self.token1Vault.balance, p: self.getStableCurveP(), swapFeeRateBps: self.getSwapFeeBps())
402            } else {
403                amountOut = SwapConfig.getAmountOutVolatile(amountIn: amountIn, reserveIn: self.token0Vault.balance, reserveOut: self.token1Vault.balance, swapFeeRateBps: self.getSwapFeeBps())
404            }
405        } else {
406            if self.isStableSwap() {
407                amountOut = SwapConfig.getAmountOutStable(amountIn: amountIn, reserveIn: self.token1Vault.balance, reserveOut: self.token0Vault.balance, p: 1.0/self.getStableCurveP(), swapFeeRateBps: self.getSwapFeeBps())
408            } else {
409                amountOut = SwapConfig.getAmountOutVolatile(amountIn: amountIn, reserveIn: self.token1Vault.balance, reserveOut: self.token0Vault.balance, swapFeeRateBps: self.getSwapFeeBps())
410            }
411        }
412        /// Check and swap exact output amount if specified in argument
413        if exactAmountOut != nil {
414            assert(amountOut >= exactAmountOut!, message:
415                SwapError.ErrorEncode(
416                    msg: "SwapPair: INSUFFICIENT_OUTPUT_AMOUNT",
417                    err: SwapError.ErrorCode.INSUFFICIENT_OUTPUT_AMOUNT
418                )
419            )
420            amountOut = exactAmountOut!
421        }
422
423        if (vaultIn.isInstance(self.token0VaultType)) {
424            self.token0Vault.deposit(from: <-vaultIn)
425            emit Swap(
426                amount0In: amountIn, amount1In: 0.0,
427                amount0Out: 0.0, amount1Out: amountOut,
428                reserve0After: self.token0Vault.balance, reserve1After: self.token1Vault.balance - amountOut,
429                amount0Type: self.token0Key, amount1Type: self.token1Key
430            )
431            self.lock = false
432            return <- self.token1Vault.withdraw(amount: amountOut)
433        } else {
434            self.token1Vault.deposit(from: <-vaultIn)
435            emit Swap(
436                amount0In: 0.0, amount1In: amountIn,
437                amount0Out: amountOut, amount1Out: 0.0,
438                reserve0After: self.token0Vault.balance - amountOut, reserve1After: self.token1Vault.balance,
439                amount0Type: self.token0Key, amount1Type: self.token1Key
440            )
441            self.lock = false
442            return <- self.token0Vault.withdraw(amount: amountOut)
443        }
444    }
445
446    /// An executor contract can request to use the whole liquidity of current SwapPair and perform custom operations (like arbitrage, liquidation, et al.), as long as:
447    ///   1. executor implements FlashLoanExecutor resource interface and sets up corresponding resource to receive & process requested tokens, and
448    ///   2. executor repays back requested amount + fees (dominated by 'flashloanRateBps x amount'), and
449    /// all in one atomic function call.
450    /// @params: User-definited extra data passed to executor for further auth/check/decode
451    ///
452    access(all) fun flashloan(executor: &{SwapInterfaces.FlashLoanExecutor}, requestedTokenVaultType: Type, requestedAmount: UFix64, params: {String: AnyStruct}) {
453        pre {
454            requestedTokenVaultType == self.token0VaultType || requestedTokenVaultType == self.token1VaultType:
455                SwapError.ErrorEncode(
456                    msg: "SwapPair: flashloan invalid requested token type",
457                    err: SwapError.ErrorCode.INVALID_PARAMETERS
458                )
459            (requestedTokenVaultType == self.token0VaultType && requestedAmount > 0.0 && requestedAmount < self.token0Vault.balance) ||
460            (requestedTokenVaultType == self.token1VaultType && requestedAmount > 0.0 && requestedAmount < self.token1Vault.balance) :
461                SwapError.ErrorEncode(
462                    msg: "SwapPair: flashloan invalid requested amount",
463                    err: SwapError.ErrorCode.INVALID_PARAMETERS
464                )
465            self.lock == false: SwapError.ErrorEncode(msg: "SwapPair: Reentrant", err: SwapError.ErrorCode.REENTRANT)
466        }
467        post {
468            self.lock == false: SwapError.ErrorEncode(msg: "SwapPair: Reentrant", err: SwapError.ErrorCode.REENTRANT)
469        }
470        self.lock = true
471
472        self._update(reserve0Last: self.token0Vault.balance, reserve1Last: self.token1Vault.balance)
473
474        var tokenOut: @{FungibleToken.Vault}? <- nil
475        if (requestedTokenVaultType == self.token0VaultType) {
476            tokenOut <-! self.token0Vault.withdraw(amount: requestedAmount)
477        } else {
478            tokenOut <-! self.token1Vault.withdraw(amount: requestedAmount)
479        }
480        // Send loans and invoke custom executor
481        let tokenIn <- executor.executeAndRepay(loanedToken: <- tokenOut!, params: params)
482        assert(tokenIn.isInstance(requestedTokenVaultType), message:
483            SwapError.ErrorEncode(
484                msg: "SwapPair: flashloan repaid incompatible token",
485                err: SwapError.ErrorCode.INVALID_PARAMETERS
486            )
487        )
488        assert(tokenIn.balance >= requestedAmount * (1.0 + UFix64(SwapFactory.getFlashloanRateBps()) / 10000.0), message:
489            SwapError.ErrorEncode(
490                msg: "SwapPair: flashloan insufficient repayment",
491                err: SwapError.ErrorCode.INVALID_PARAMETERS
492            )
493        )
494        if (requestedTokenVaultType == self.token0VaultType) {
495            self.token0Vault.deposit(from: <- tokenIn)
496        } else {
497            self.token1Vault.deposit(from: <- tokenIn)
498        }
499
500        emit Flashloan(
501            executor: executor.owner!.address,
502            executorType: executor.getType(),
503            originator: self.account.address,
504            requestedTokenType: requestedTokenVaultType == self.token0VaultType ? self.token0Key : self.token1Key,
505            requestedAmount: requestedAmount,
506            reserve0After: self.token0Vault.balance,
507            reserve1After: self.token1Vault.balance
508        )
509
510        self.lock = false
511    }
512
513    /// Update cumulative price on the first call per block
514    access(self) fun _update(reserve0Last: UFix64, reserve1Last: UFix64) {
515        let blockTimestamp = getCurrentBlock().timestamp
516        let timeElapsed = blockTimestamp - self.blockTimestampLast
517        if (timeElapsed > 0.0 && reserve0Last != 0.0 && reserve1Last != 0.0) {
518            let timeElapsedScaled = SwapConfig.UFix64ToScaledUInt256(timeElapsed)
519            let stableswap_p = self.getStableCurveP()
520            let price0 = self.isStableSwap() ?
521                SwapConfig.quoteStable(amountA: 1.0, reserveA: reserve0Last, reserveB: reserve1Last, p: stableswap_p) :
522                SwapConfig.quote(amountA: 1.0, reserveA: reserve0Last, reserveB: reserve1Last)
523            var price1 = 0.0
524            if (price0 == 0.0) {
525                price1 = self.isStableSwap() ?
526                    SwapConfig.quoteStable(amountA: 1.0, reserveA: reserve1Last, reserveB: reserve0Last, p: 1.0 / stableswap_p) :
527                    SwapConfig.quote(amountA: 1.0, reserveA: reserve1Last, reserveB: reserve0Last)
528            } else {
529                price1 = 1.0 / price0
530            }
531
532            self.price0CumulativeLastScaled = SwapConfig.overflowAddUInt256(
533                self.price0CumulativeLastScaled,
534                SwapConfig.UFix64ToScaledUInt256(price0) * timeElapsedScaled / SwapConfig.scaleFactor
535            )
536            self.price1CumulativeLastScaled = SwapConfig.overflowAddUInt256(
537                self.price1CumulativeLastScaled,
538                SwapConfig.UFix64ToScaledUInt256(price1) * timeElapsedScaled / SwapConfig.scaleFactor
539            )   
540        }
541        self.blockTimestampLast = blockTimestamp
542    }
543    
544    /// If feeTo is set, mint 1/6th (the default cut) of the growth in sqrt(k) which is only generated by trading behavior.
545    /// Instead of collecting protocol fees at the time of each trade, accumulated fees are collected only
546    /// when liquidity is deposited or withdrawn.
547    ///
548    access(self) fun _mintFee(reserve0: UFix64, reserve1: UFix64): Bool {
549        let rootKLast = self.rootKLast
550        if SwapFactory.feeTo == nil {
551            if rootKLast > 0.0 {
552                self.rootKLast = 0.0
553            }
554            return false
555        }
556        if rootKLast > 0.0 {
557            let rootK = self._rootK(balance0: reserve0, balance1: reserve1)
558            if rootK > rootKLast {
559                let numerator = self.totalSupply * (rootK - rootKLast)
560                let denominator = (1.0 / SwapFactory.getProtocolFeeCut() - 1.0) * rootK + rootKLast
561                let liquidity = numerator / denominator
562                if liquidity > 0.0 {
563                    let lpTokenVault <-self.mintLpToken(amount: liquidity)
564                    let lpTokenCollectionCap = getAccount(SwapFactory.feeTo!).capabilities.get<&{SwapInterfaces.LpTokenCollectionPublic}>(SwapConfig.LpTokenCollectionPublicPath)
565                    assert(lpTokenCollectionCap.check(), message:
566                        SwapError.ErrorEncode(
567                            msg: "SwapPair: Cannot borrow reference to LpTokenCollection resource in feeTo account",
568                            err: SwapError.ErrorCode.LOST_PUBLIC_CAPABILITY
569                        )
570                    )
571                    lpTokenCollectionCap.borrow()!.deposit(pairAddr: self.account.address, lpTokenVault: <-lpTokenVault)
572                }
573            }
574        }
575        return true
576    }
577
578    access(all) view fun _rootK(balance0: UFix64, balance1: UFix64): UFix64 {
579        let e18: UInt256 = SwapConfig.scaleFactor
580        let balance0Scaled = SwapConfig.UFix64ToScaledUInt256(balance0)
581        let balance1Scaled = SwapConfig.UFix64ToScaledUInt256(balance1)
582        if self.isStableSwap() {
583            let _p_scaled: UInt256 = SwapConfig.UFix64ToScaledUInt256(self.getStableCurveP())
584            let _k_scaled: UInt256 = SwapConfig.k_stable_p(balance0Scaled, balance1Scaled, _p_scaled)
585            return SwapConfig.ScaledUInt256ToUFix64(SwapConfig.sqrt(SwapConfig.sqrt(_k_scaled / 2)))
586        } else {
587            return SwapConfig.ScaledUInt256ToUFix64(SwapConfig.sqrt(balance0Scaled * balance1Scaled / e18))
588        }
589    }
590
591    access(all) view fun isStableSwap(): Bool {
592        return (self._reservedFields["isStableSwap"] as! Bool?) ?? false
593    }
594
595    access(all) view fun getSwapFeeBps(): UInt64 {
596        return SwapFactory.getSwapFeeRateBps(stableMode: self.isStableSwap())
597    }
598
599    /// P for stFlow <> Flow pair
600    access(all) view fun getStableCurveP(): UFix64 {
601        if (self.token0Key == "A.1654653399040a61.FlowToken" && self.token1Key == "A.d6f80565193ad727.stFlowToken") {
602            let price_flow2Stflow = DelegatorManager.borrowCurrentQuoteEpochSnapshot().scaledQuoteFlowStFlow
603            return SwapConfig.ScaledUInt256ToUFix64(price_flow2Stflow)
604        } else if (self.token1Key == "A.1654653399040a61.FlowToken" && self.token0Key == "A.d6f80565193ad727.stFlowToken") {
605            let price_stflow2Flow = DelegatorManager.borrowCurrentQuoteEpochSnapshot().scaledQuoteStFlowFlow
606            return SwapConfig.ScaledUInt256ToUFix64(price_stflow2Flow)
607        } else {
608            return 1.0
609        }
610    }
611
612    /// Public interfaces
613    ///
614    access(all) resource PairPublic: SwapInterfaces.PairPublic {
615        access(all) fun swap(vaultIn: @{FungibleToken.Vault}, exactAmountOut: UFix64?): @{FungibleToken.Vault} {
616            return <- SwapPair.swap(vaultIn: <-vaultIn, exactAmountOut: exactAmountOut)
617        }
618
619        access(all) fun flashloan(executor: &{SwapInterfaces.FlashLoanExecutor}, requestedTokenVaultType: Type, requestedAmount: UFix64, params: {String: AnyStruct}) {
620            SwapPair.flashloan(executor: executor, requestedTokenVaultType: requestedTokenVaultType, requestedAmount: requestedAmount, params: params)
621        }
622
623        access(all) fun removeLiquidity(lpTokenVault: @{FungibleToken.Vault}) : @[{FungibleToken.Vault}] {
624            return <- SwapPair.removeLiquidity(lpTokenVault: <- lpTokenVault)
625        }
626
627        access(all) fun addLiquidity(tokenAVault: @{FungibleToken.Vault}, tokenBVault: @{FungibleToken.Vault}): @{FungibleToken.Vault} {
628            return <- SwapPair.addLiquidity(tokenAVault: <- tokenAVault, tokenBVault: <- tokenBVault)
629        }
630
631        access(all) view fun getAmountIn(amountOut: UFix64, tokenOutKey: String): UFix64 {
632            if SwapPair.isStableSwap() {
633                if tokenOutKey == SwapPair.token1Key {
634                    return SwapConfig.getAmountInStable(amountOut: amountOut, reserveIn: SwapPair.token0Vault.balance, reserveOut: SwapPair.token1Vault.balance, p: SwapPair.getStableCurveP(), swapFeeRateBps: SwapPair.getSwapFeeBps())
635                } else {
636                    return SwapConfig.getAmountInStable(amountOut: amountOut, reserveIn: SwapPair.token1Vault.balance, reserveOut: SwapPair.token0Vault.balance, p: 1.0/SwapPair.getStableCurveP(), swapFeeRateBps: SwapPair.getSwapFeeBps())
637                }
638            } else {
639                if tokenOutKey == SwapPair.token1Key {
640                    return SwapConfig.getAmountInVolatile(amountOut: amountOut, reserveIn: SwapPair.token0Vault.balance, reserveOut: SwapPair.token1Vault.balance, swapFeeRateBps: SwapPair.getSwapFeeBps())
641                } else {
642                    return SwapConfig.getAmountInVolatile(amountOut: amountOut, reserveIn: SwapPair.token1Vault.balance, reserveOut: SwapPair.token0Vault.balance, swapFeeRateBps: SwapPair.getSwapFeeBps())
643                }
644            }
645        }
646        access(all) view fun getAmountOut(amountIn: UFix64, tokenInKey: String): UFix64 {
647            if SwapPair.isStableSwap() {
648                if tokenInKey == SwapPair.token0Key {
649                    return SwapConfig.getAmountOutStable(amountIn: amountIn, reserveIn: SwapPair.token0Vault.balance, reserveOut: SwapPair.token1Vault.balance, p: SwapPair.getStableCurveP(), swapFeeRateBps: SwapPair.getSwapFeeBps())
650                } else {
651                    return SwapConfig.getAmountOutStable(amountIn: amountIn, reserveIn: SwapPair.token1Vault.balance, reserveOut: SwapPair.token0Vault.balance, p: 1.0/SwapPair.getStableCurveP(), swapFeeRateBps: SwapPair.getSwapFeeBps())
652                }
653            } else {
654                if tokenInKey == SwapPair.token0Key {
655                    return SwapConfig.getAmountOutVolatile(amountIn: amountIn, reserveIn: SwapPair.token0Vault.balance, reserveOut: SwapPair.token1Vault.balance, swapFeeRateBps: SwapPair.getSwapFeeBps())
656                } else {
657                    return SwapConfig.getAmountOutVolatile(amountIn: amountIn, reserveIn: SwapPair.token1Vault.balance, reserveOut: SwapPair.token0Vault.balance, swapFeeRateBps: SwapPair.getSwapFeeBps())
658                }
659            }
660        }
661        access(all) view fun getPrice0CumulativeLastScaled(): UInt256 {
662            return SwapPair.price0CumulativeLastScaled
663        }
664        access(all) view fun getPrice1CumulativeLastScaled(): UInt256 {
665            return SwapPair.price1CumulativeLastScaled
666        }
667        access(all) view fun getBlockTimestampLast(): UFix64 {
668            return SwapPair.blockTimestampLast
669        }
670
671        access(all) view fun getPairInfo(): [AnyStruct] {
672            return [
673                SwapPair.token0Key,            // 0
674                SwapPair.token1Key,
675                SwapPair.token0Vault.balance,
676                SwapPair.token1Vault.balance,
677                SwapPair.account.address,
678                SwapPair.totalSupply,          // 5
679                SwapPair.getSwapFeeBps(),
680                SwapPair.isStableSwap(),
681                SwapPair.getStableCurveP()
682            ]
683        }
684
685        access(all) view fun getPairInfoStruct(): PairInfo {
686            return PairInfo(
687                SwapPair.token0Key,
688                SwapPair.token1Key,
689                SwapPair.token0Vault.balance,
690                SwapPair.token1Vault.balance,
691                SwapPair.account.address,
692                SwapPair.totalSupply,
693                SwapPair.getSwapFeeBps(),
694                SwapPair.isStableSwap(),
695                SwapPair.getStableCurveP()
696            )
697        }
698
699        access(all) view fun getLpTokenVaultType(): Type {
700            return Type<@SwapPair.Vault>()
701        }
702
703        access(all) view fun isStableSwap(): Bool {
704            return SwapPair.isStableSwap()
705        }
706
707        access(all) view fun getStableCurveP(): UFix64 {
708            return SwapPair.getStableCurveP()
709        }
710    }
711
712    init(token0Vault: @{FungibleToken.Vault}, token1Vault: @{FungibleToken.Vault}, stableMode: Bool) {
713        self.totalSupply = 0.0
714
715        self.token0VaultType = token0Vault.getType()
716        self.token1VaultType = token1Vault.getType()
717        self.token0Vault <- token0Vault
718        self.token1Vault <- token1Vault
719
720        self.lock = false
721
722        self.blockTimestampLast = getCurrentBlock().timestamp
723        self.price0CumulativeLastScaled = 0
724        self.price1CumulativeLastScaled = 0
725
726        self.rootKLast = 0.0
727        self._reservedFields = {}
728        self._reservedFields["isStableSwap"] = stableMode
729
730        self.token0Key = SwapConfig.SliceTokenTypeIdentifierFromVaultType(vaultTypeIdentifier: self.token0VaultType.identifier)
731        self.token1Key = SwapConfig.SliceTokenTypeIdentifierFromVaultType(vaultTypeIdentifier: self.token1VaultType.identifier)
732
733        /// Setup PairPublic interface capability
734        destroy <-self.account.storage.load<@AnyResource>(from: /storage/pair_public)
735        self.account.storage.save(<-create PairPublic(), to: /storage/pair_public)
736        let pairPublicCap = self.account.capabilities.storage.issue<&{SwapInterfaces.PairPublic}>(/storage/pair_public)
737        self.account.capabilities.publish(pairPublicCap, at: SwapConfig.PairPublicPath)
738
739        emit TokensInitialized(initialSupply: self.totalSupply)
740    }
741}
742