Smart Contract
IncrementFiSwapperConnector
A.17ae3b1b0b0d50db.IncrementFiSwapperConnector
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