Smart Contract

IncrementFiSwapperConnector

A.17ae3b1b0b0d50db.IncrementFiSwapperConnector

Valid From

132,673,007

Deployed

6d ago
Feb 21, 2026, 05:42:05 PM UTC

Dependents

0 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import DeFiActions from 0x17ae3b1b0b0d50db
3import SwapRouter from 0xa6850776a94e6551
4import SwapFactory from 0xb063c16cac85dbd1
5
6/// IncrementFiSwapperConnector: FlowActions connector for Increment.fi DEX
7///
8/// Implements Swapper interface for atomic swaps on Increment.fi
9///
10/// SLIPPAGE PROTECTION: This connector returns RAW market data. Slippage protection
11/// is enforced at the caller layer (DCAVaultActions.executePurchaseWithSwapper) which:
12/// 1. Applies user-configured slippage tolerance to calculate minAmount
13/// 2. Passes minAmount to swap() which enforces it via SwapRouter.swapExactTokensForTokens
14/// 3. Validates actual slippage after execution against user's tolerance
15///
16/// This separation allows users to configure slippage per DCA plan, rather than
17/// hardcoding a one-size-fits-all value in the connector.
18///
19/// Official Documentation: https://docs.increment.fi
20///
21access(all) contract IncrementFiSwapperConnector {
22
23    /// Events
24    access(all) event SwapExecuted(
25        fromToken: String,
26        toToken: String,
27        amountIn: UFix64,
28        amountOut: UFix64,
29        pairAddress: Address
30    )
31
32    /// IncrementFiSwapper resource implementing Swapper interface
33    access(all) resource IncrementFiSwapper: DeFiActions.Swapper {
34        access(all) let pairAddress: Address
35        access(all) let routerAddress: Address
36        access(all) let tokenKeyPath: [String] // Token identifiers for swap route
37
38        init(pairAddress: Address, routerAddress: Address, tokenKeyPath: [String]) {
39            self.pairAddress = pairAddress
40            self.routerAddress = routerAddress
41            self.tokenKeyPath = tokenKeyPath
42        }
43
44        /// Execute swap on Increment.fi with slippage protection
45        ///
46        /// Uses SwapRouter.swapExactTokensForTokens to execute the swap.
47        /// SLIPPAGE PROTECTION: Enforced via quote.minAmount parameter, which is
48        /// calculated by the caller (DCAVaultActions) based on user's slippageTolerance.
49        /// The swap will revert if output < minAmount.
50        ///
51        access(all) fun swap(
52            inVault: @{FungibleToken.Vault},
53            quote: DeFiActions.Quote
54        ): @{FungibleToken.Vault} {
55            pre {
56                inVault.balance > 0.0: "Input vault must have balance"
57                quote.minAmount > 0.0: "Minimum amount must be positive"
58                quote.deadline != nil && quote.deadline! >= getCurrentBlock().timestamp: "Quote expired"
59            }
60
61            let amountIn = inVault.balance
62            let fromTokenType = inVault.getType().identifier
63
64            // Execute swap through Increment.fi SwapRouter
65            let outVault <- SwapRouter.swapExactTokensForTokens(
66                exactVaultIn: <-inVault,
67                amountOutMin: quote.minAmount,
68                tokenKeyPath: self.tokenKeyPath,
69                deadline: quote.deadline!
70            )
71
72            let amountOut = outVault.balance
73            let toTokenType = outVault.getType().identifier
74
75            // Emit swap event
76            emit SwapExecuted(
77                fromToken: fromTokenType,
78                toToken: toTokenType,
79                amountIn: amountIn,
80                amountOut: amountOut,
81                pairAddress: self.pairAddress
82            )
83
84            return <-outVault
85        }
86
87        /// Get quote for swap - Returns RAW market data
88        ///
89        /// Queries Increment.fi SwapRouter for expected output amounts
90        /// based on current pool reserves.
91        ///
92        /// SLIPPAGE PROTECTION DESIGN:
93        /// This connector returns RAW market data (minAmount = expectedAmount).
94        /// The CALLER is responsible for applying slippage protection by:
95        /// 1. Reading their desired slippageTolerance from DCA plan configuration
96        /// 2. Calculating: minAmount = expectedAmount * (1.0 - slippageTolerance)
97        /// 3. Creating an adjusted Quote with the calculated minAmount
98        /// 4. Passing that Quote to swap(), which enforces the minimum
99        ///
100        /// This design allows users to configure different slippage tolerances per
101        /// DCA plan (e.g., 0.5% for stable pairs, 2% for volatile pairs).
102        ///
103        access(all) fun getQuote(
104            fromTokenType: Type,
105            toTokenType: Type,
106            amount: UFix64
107        ): DeFiActions.Quote {
108            // Query SwapRouter for expected amounts
109            let amounts = SwapRouter.getAmountsOut(
110                amountIn: amount,
111                tokenKeyPath: self.tokenKeyPath
112            )
113
114            // Last element is the expected output amount
115            let expectedAmount = amounts[amounts.length - 1]
116
117            // Return raw quote WITHOUT slippage applied
118            // Caller should calculate minAmount based on their slippage tolerance
119            // Example: minAmount = expectedAmount * (1.0 - slippageTolerance)
120            return DeFiActions.Quote(
121                expectedAmount: expectedAmount,
122                minAmount: expectedAmount, // No slippage - caller applies their own
123                slippageTolerance: 0.0, // Indicates no slippage applied by connector
124                deadline: getCurrentBlock().timestamp + 300.0, // 5 minutes default
125                data: {
126                    "pairAddress": self.pairAddress.toString(),
127                    "routerAddress": self.routerAddress.toString(),
128                    "tokenPath": self.tokenKeyPath.length.toString()
129                }
130            )
131        }
132
133        /// Get swapper info
134        access(all) fun getInfo(): DeFiActions.ComponentInfo {
135            return DeFiActions.ComponentInfo(
136                type: "Swapper",
137                identifier: "IncrementFi",
138                version: "1.0.0"
139            )
140        }
141    }
142
143    /// Create IncrementFi swapper
144    ///
145    /// @param pairAddress: Address of the trading pair
146    /// @param routerAddress: Address of SwapRouter contract
147    ///                       Mainnet: 0xa6850776a94e6551
148    ///                       Testnet: 0x2f8af5ed05bbde0d
149    /// @param tokenKeyPath: Array of token identifiers for swap route
150    ///                      Format: ["A.{address}.{ContractName}", ...]
151    ///                      Example: ["A.1654653399040a61.FlowToken", "A.f1ab99c82dee3526.USDCFlow"]
152    ///
153    access(all) fun createSwapper(
154        pairAddress: Address,
155        routerAddress: Address,
156        tokenKeyPath: [String]
157    ): @IncrementFiSwapper {
158        pre {
159            tokenKeyPath.length >= 2: "Token path must have at least 2 tokens"
160        }
161
162        return <- create IncrementFiSwapper(
163            pairAddress: pairAddress,
164            routerAddress: routerAddress,
165            tokenKeyPath: tokenKeyPath
166        )
167    }
168
169    /// Helper to verify pair exists
170    ///
171    /// @param token0Key: First token identifier
172    /// @param token1Key: Second token identifier
173    /// @return Pair address if exists, nil otherwise
174    ///
175    access(all) fun getPairAddress(token0Key: String, token1Key: String): Address? {
176        return SwapFactory.getPairAddress(token0Key: token0Key, token1Key: token1Key)
177    }
178}
179