Smart Contract
UniswapV3SwapperConnectorV2
A.17ae3b1b0b0d50db.UniswapV3SwapperConnectorV2
1import FungibleToken from 0xf233dcee88fe0abe
2import FlowToken from 0x1654653399040a61
3import EVM from 0xe467b9dd11fa00df
4import Burner from 0xf233dcee88fe0abe
5
6import DeFiActions from 0x17ae3b1b0b0d50db
7
8/// UniswapV3SwapperConnectorV2
9///
10/// DeFiActions Swapper connector implementation for Uniswap V3 style DEXes on Flow EVM.
11/// This V2 version includes full COA-based EVM swap execution.
12///
13/// Supports FlowSwap V3 and PunchSwap V3 deployed on Flow EVM (Chain ID: 747).
14///
15/// Uniswap V3 uses concentrated liquidity with multiple fee tiers:
16/// - 0.01% (100) - Stablecoin pairs
17/// - 0.05% (500) - Stable pairs
18/// - 0.30% (3000) - Standard pairs (default)
19/// - 1.00% (10000) - Exotic pairs
20///
21access(all) contract UniswapV3SwapperConnectorV2 {
22
23 /// Events
24 access(all) event SwapperCreated(
25 routerAddress: String,
26 factoryAddress: String,
27 feeTier: UInt32,
28 tokenIn: String,
29 tokenOut: String
30 )
31 access(all) event SwapExecuted(
32 routerAddress: String,
33 amountIn: UFix64,
34 amountOut: UFix64,
35 feeTier: UInt32,
36 tokenIn: String,
37 tokenOut: String
38 )
39 access(all) event SwapFailed(
40 routerAddress: String,
41 amountIn: UFix64,
42 reason: String
43 )
44 access(all) event QuoteFetched(
45 routerAddress: String,
46 amountIn: UFix64,
47 amountOut: UFix64,
48 feeTier: UInt32
49 )
50
51 /// Storage paths
52 access(all) let AdminStoragePath: StoragePath
53
54 /// Default addresses for FlowSwap V3 on Flow EVM Mainnet
55 access(all) let defaultRouterAddress: EVM.EVMAddress // SwapRouter02
56 access(all) let defaultFactoryAddress: EVM.EVMAddress // V3 Core Factory
57 access(all) let defaultQuoterAddress: EVM.EVMAddress // Quoter V2
58
59 /// WFLOW address on Flow EVM
60 access(all) let wflowAddress: EVM.EVMAddress
61
62 /// Default fee tier (0.3% = 3000)
63 access(all) let DEFAULT_FEE_TIER: UInt32
64
65 /// UniswapV3Swapper resource implementing DeFiActions.Swapper interface
66 access(all) resource UniswapV3Swapper: DeFiActions.Swapper {
67 /// EVM Router address for V3 swaps
68 access(all) let routerAddress: EVM.EVMAddress
69 /// Factory address for pool lookups
70 access(all) let factoryAddress: EVM.EVMAddress
71 /// Quoter address for price quotes
72 access(all) let quoterAddress: EVM.EVMAddress
73 /// Input token EVM address
74 access(all) let tokenIn: EVM.EVMAddress
75 /// Output token EVM address
76 access(all) let tokenOut: EVM.EVMAddress
77 /// Fee tier for the pool (100, 500, 3000, or 10000)
78 access(all) let feeTier: UInt32
79 /// Cadence token type identifiers
80 access(all) let cadenceTokenIn: String
81 access(all) let cadenceTokenOut: String
82 /// COA capability for EVM interactions
83 access(self) let coaCapability: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>?
84 /// Input vault type
85 access(self) let inVaultType: Type
86 /// Output vault type
87 access(self) let outVaultType: Type
88
89 init(
90 routerAddress: EVM.EVMAddress,
91 factoryAddress: EVM.EVMAddress,
92 quoterAddress: EVM.EVMAddress,
93 tokenIn: EVM.EVMAddress,
94 tokenOut: EVM.EVMAddress,
95 feeTier: UInt32,
96 cadenceTokenIn: String,
97 cadenceTokenOut: String,
98 coaCapability: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>?,
99 inVaultType: Type,
100 outVaultType: Type
101 ) {
102 pre {
103 feeTier == 100 || feeTier == 500 || feeTier == 3000 || feeTier == 10000:
104 "Invalid fee tier: must be 100, 500, 3000, or 10000"
105 }
106 self.routerAddress = routerAddress
107 self.factoryAddress = factoryAddress
108 self.quoterAddress = quoterAddress
109 self.tokenIn = tokenIn
110 self.tokenOut = tokenOut
111 self.feeTier = feeTier
112 self.cadenceTokenIn = cadenceTokenIn
113 self.cadenceTokenOut = cadenceTokenOut
114 self.coaCapability = coaCapability
115 self.inVaultType = inVaultType
116 self.outVaultType = outVaultType
117 }
118
119 /// Execute swap on Uniswap V3 via EVM
120 access(all) fun swap(
121 inVault: @{FungibleToken.Vault},
122 quote: DeFiActions.Quote
123 ): @{FungibleToken.Vault} {
124 let amountIn = inVault.balance
125
126 // If no COA capability, cannot execute
127 if self.coaCapability == nil {
128 emit SwapFailed(
129 routerAddress: self.routerAddress.toString(),
130 amountIn: amountIn,
131 reason: "No COA capability - swap cannot be executed"
132 )
133 return <- inVault
134 }
135
136 if let coa = self.coaCapability!.borrow() {
137 return <- self.executeEVMSwap(
138 coa: coa,
139 inVault: <- inVault,
140 amountOutMin: quote.minAmount
141 )
142 }
143
144 emit SwapFailed(
145 routerAddress: self.routerAddress.toString(),
146 amountIn: amountIn,
147 reason: "COA capability is invalid"
148 )
149 return <- inVault
150 }
151
152 /// Execute V3 swap via exactInputSingle
153 access(self) fun executeEVMSwap(
154 coa: auth(EVM.Owner) &EVM.CadenceOwnedAccount,
155 inVault: @{FungibleToken.Vault},
156 amountOutMin: UFix64
157 ): @{FungibleToken.Vault} {
158 let amountIn = inVault.balance
159
160 // Convert amounts to EVM format
161 let evmAmountIn = self.toEVMAmount(amountIn, decimals: 18)
162 let evmAmountOutMin = self.toEVMAmount(amountOutMin, decimals: 18)
163
164 // Determine actual swap token addresses
165 // If input is FLOW, we wrap to WFLOW, so always use WFLOW as swap input
166 let actualTokenIn = UniswapV3SwapperConnectorV2.wflowAddress
167 let actualTokenOut = self.tokenOut
168
169 // Step 1: Deposit tokens to COA and wrap to WFLOW
170 if inVault.getType() == Type<@FlowToken.Vault>() {
171 // Deposit FLOW directly
172 coa.deposit(from: <- (inVault as! @FlowToken.Vault))
173
174 // Wrap FLOW to WFLOW
175 let wrapResult = coa.call(
176 to: UniswapV3SwapperConnectorV2.wflowAddress,
177 data: EVM.encodeABIWithSignature("deposit()", []),
178 gasLimit: 100_000,
179 value: EVM.Balance(attoflow: UInt(evmAmountIn))
180 )
181
182 if wrapResult.status != EVM.Status.successful {
183 panic("Failed to wrap FLOW to WFLOW: ".concat(wrapResult.errorMessage))
184 }
185 } else {
186 destroy inVault
187 panic("Non-FLOW token bridging not yet implemented")
188 }
189
190 // Step 2: Approve routers to spend WFLOW (use max approval for reliability)
191 let maxApproval = UInt256(115792089237316195423570985008687907853269984665640564039457584007913129639935)
192
193 // Approve V3 router
194 let approveV3Data = EVM.encodeABIWithSignature(
195 "approve(address,uint256)",
196 [self.routerAddress, maxApproval]
197 )
198 let approveV3Result = coa.call(
199 to: actualTokenIn, // Use WFLOW address
200 data: approveV3Data,
201 gasLimit: 100_000,
202 value: EVM.Balance(attoflow: 0)
203 )
204
205 // Also approve PunchSwap V2 router upfront
206 let punchswapV2Router = EVM.addressFromString("0xf45AFe28fd5519d5f8C1d4787a4D5f724C0eFa4d")
207 let approveV2Data = EVM.encodeABIWithSignature(
208 "approve(address,uint256)",
209 [punchswapV2Router, maxApproval]
210 )
211 coa.call(
212 to: actualTokenIn, // Use WFLOW address
213 data: approveV2Data,
214 gasLimit: 100_000,
215 value: EVM.Balance(attoflow: 0)
216 )
217
218 // Step 3: Try V3 swap first, fall back to V2 if it fails
219 let deadline = UInt256(UInt64(getCurrentBlock().timestamp) + 300)
220 var evmAmountOut: UInt256 = 0
221 var swapSucceeded = false
222
223 // Try V3 exactInputSingle with multiple fee tiers
224 let feeTiers: [UInt256] = [UInt256(self.feeTier), UInt256(500), UInt256(3000), UInt256(10000)]
225 for fee in feeTiers {
226 let swapData = self.encodeExactInputSingle(
227 tokenIn: actualTokenIn, // Use WFLOW
228 tokenOut: actualTokenOut,
229 fee: fee,
230 recipient: coa.address(),
231 amountIn: evmAmountIn,
232 amountOutMinimum: evmAmountOutMin,
233 sqrtPriceLimitX96: UInt256(0)
234 )
235
236 let swapResult = coa.call(
237 to: self.routerAddress,
238 data: swapData,
239 gasLimit: 500_000,
240 value: EVM.Balance(attoflow: 0)
241 )
242
243 if swapResult.status == EVM.Status.successful && swapResult.data.length > 0 {
244 let decoded = EVM.decodeABI(types: [Type<UInt256>()], data: swapResult.data)
245 if decoded.length > 0 {
246 evmAmountOut = decoded[0] as! UInt256
247 if evmAmountOut > 0 {
248 swapSucceeded = true
249 break
250 }
251 }
252 }
253 }
254
255 // If V3 failed, fall back to PunchSwap V2
256 if !swapSucceeded {
257 // V2 swap using WFLOW -> tokenOut path
258 let v2SwapData = EVM.encodeABIWithSignature(
259 "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)",
260 [evmAmountIn, evmAmountOutMin, [actualTokenIn, actualTokenOut], coa.address(), deadline]
261 )
262
263 let v2SwapResult = coa.call(
264 to: punchswapV2Router,
265 data: v2SwapData,
266 gasLimit: 500_000,
267 value: EVM.Balance(attoflow: 0)
268 )
269
270 if v2SwapResult.status == EVM.Status.successful && v2SwapResult.data.length > 0 {
271 let decoded = EVM.decodeABI(types: [Type<[UInt256]>()], data: v2SwapResult.data)
272 if decoded.length > 0 {
273 let amounts = decoded[0] as! [UInt256]
274 if amounts.length > 1 {
275 evmAmountOut = amounts[amounts.length - 1]
276 swapSucceeded = true
277 }
278 }
279 }
280
281 if !swapSucceeded {
282 // Emit detailed error with addresses for debugging
283 panic("All swap attempts failed. WFLOW: "
284 .concat(actualTokenIn.toString())
285 .concat(" TokenOut: ")
286 .concat(actualTokenOut.toString())
287 .concat(" V2 error: ")
288 .concat(v2SwapResult.errorMessage))
289 }
290
291 emit SwapFailed(
292 routerAddress: self.routerAddress.toString(),
293 amountIn: amountIn,
294 reason: "V3 unavailable, used V2 fallback"
295 )
296 }
297
298 // Step 4: Handle output token
299 // Check if output is WFLOW (need to unwrap to FLOW)
300 let isOutputWFLOW = actualTokenOut.toString().toLower() == UniswapV3SwapperConnectorV2.wflowAddress.toString().toLower()
301
302 if isOutputWFLOW {
303 // Unwrap WFLOW to FLOW
304 let unwrapData = EVM.encodeABIWithSignature(
305 "withdraw(uint256)",
306 [evmAmountOut]
307 )
308 let unwrapResult = coa.call(
309 to: UniswapV3SwapperConnectorV2.wflowAddress,
310 data: unwrapData,
311 gasLimit: 100_000,
312 value: EVM.Balance(attoflow: 0)
313 )
314 if unwrapResult.status != EVM.Status.successful {
315 panic("Failed to unwrap WFLOW: ".concat(unwrapResult.errorMessage))
316 }
317
318 // Withdraw native FLOW from COA
319 let withdrawBalance = EVM.Balance(attoflow: UInt(evmAmountOut))
320 let outputVault <- coa.withdraw(balance: withdrawBalance) as! @FlowToken.Vault
321
322 let cadenceAmountOut = self.fromEVMAmount(evmAmountOut, decimals: 18)
323
324 emit SwapExecuted(
325 routerAddress: self.routerAddress.toString(),
326 amountIn: amountIn,
327 amountOut: cadenceAmountOut,
328 feeTier: self.feeTier,
329 tokenIn: self.cadenceTokenIn,
330 tokenOut: self.cadenceTokenOut
331 )
332
333 return <- outputVault
334 }
335
336 // For non-FLOW output tokens (ERC20s like USDF)
337 // The tokens are now in the COA's EVM balance
338 // For now, we emit an event showing the swap succeeded but output is in EVM
339 // User needs to manually bridge or we need to implement bridging
340
341 let cadenceAmountOut = self.fromEVMAmount(evmAmountOut, decimals: 18)
342
343 emit SwapExecuted(
344 routerAddress: self.routerAddress.toString(),
345 amountIn: amountIn,
346 amountOut: cadenceAmountOut,
347 feeTier: self.feeTier,
348 tokenIn: self.cadenceTokenIn,
349 tokenOut: self.cadenceTokenOut
350 )
351
352 // IMPORTANT: The ERC20 output tokens are in the COA's EVM balance
353 // We cannot return them as a Cadence vault without bridging
354 // For now, panic with clear message
355 panic("EVM swap succeeded! Output: "
356 .concat(cadenceAmountOut.toString())
357 .concat(" tokens at EVM address ")
358 .concat(actualTokenOut.toString())
359 .concat(". Bridging ERC20->Cadence not yet implemented. Tokens are in your COA."))
360 }
361
362 /// Convert Cadence UFix64 to EVM UInt256
363 access(self) fun toEVMAmount(_ amount: UFix64, decimals: UInt8): UInt256 {
364 let factor = self.pow10(UInt8(decimals) - 8)
365 return UInt256(amount * 100_000_000.0) * factor
366 }
367
368 /// Convert EVM UInt256 to Cadence UFix64
369 access(self) fun fromEVMAmount(_ amount: UInt256, decimals: UInt8): UFix64 {
370 let factor = self.pow10(UInt8(decimals) - 8)
371 let cadenceAmount = amount / factor
372 return UFix64(cadenceAmount) / 100_000_000.0
373 }
374
375 /// Helper for 10^n
376 access(self) fun pow10(_ n: UInt8): UInt256 {
377 var result: UInt256 = 1
378 var i: UInt8 = 0
379 while i < n {
380 result = result * 10
381 i = i + 1
382 }
383 return result
384 }
385
386 /// Manually encode exactInputSingle calldata for V3 swap
387 /// Since Cadence can't encode struct tuples, we build the bytes manually
388 access(self) fun encodeExactInputSingle(
389 tokenIn: EVM.EVMAddress,
390 tokenOut: EVM.EVMAddress,
391 fee: UInt256,
392 recipient: EVM.EVMAddress,
393 amountIn: UInt256,
394 amountOutMinimum: UInt256,
395 sqrtPriceLimitX96: UInt256
396 ): [UInt8] {
397 // Function selector for exactInputSingle((address,address,uint24,address,uint256,uint256,uint160))
398 // keccak256("exactInputSingle((address,address,uint24,address,uint256,uint256,uint160))") = 0x414bf389
399 var data: [UInt8] = [0x41, 0x4b, 0xf3, 0x89]
400
401 // Encode each parameter as 32 bytes
402 // 1. tokenIn (address - 20 bytes, left-padded to 32)
403 data = data.concat(self.encodeAddress(tokenIn))
404 // 2. tokenOut (address)
405 data = data.concat(self.encodeAddress(tokenOut))
406 // 3. fee (uint24 - encoded as uint256)
407 data = data.concat(self.encodeUInt256(fee))
408 // 4. recipient (address)
409 data = data.concat(self.encodeAddress(recipient))
410 // 5. amountIn (uint256)
411 data = data.concat(self.encodeUInt256(amountIn))
412 // 6. amountOutMinimum (uint256)
413 data = data.concat(self.encodeUInt256(amountOutMinimum))
414 // 7. sqrtPriceLimitX96 (uint160 - encoded as uint256)
415 data = data.concat(self.encodeUInt256(sqrtPriceLimitX96))
416
417 return data
418 }
419
420 /// Encode an EVM address as 32 bytes (12 zero bytes + 20 address bytes)
421 access(self) fun encodeAddress(_ addr: EVM.EVMAddress): [UInt8] {
422 var result: [UInt8] = []
423 // 12 bytes of zero padding
424 var i = 0
425 while i < 12 {
426 result.append(0)
427 i = i + 1
428 }
429 // 20 bytes of address
430 for byte in addr.bytes {
431 result.append(byte)
432 }
433 return result
434 }
435
436 /// Encode a UInt256 as 32 bytes (big-endian)
437 access(self) fun encodeUInt256(_ value: UInt256): [UInt8] {
438 var result: [UInt8] = []
439 var remaining = value
440 var bytes: [UInt8] = []
441
442 // Convert to bytes (little-endian first)
443 if remaining == 0 {
444 bytes.append(0)
445 } else {
446 while remaining > 0 {
447 bytes.append(UInt8(remaining % 256))
448 remaining = remaining / 256
449 }
450 }
451
452 // Pad to 32 bytes
453 while bytes.length < 32 {
454 bytes.append(0)
455 }
456
457 // Reverse to get big-endian
458 var i = 31
459 while i >= 0 {
460 result.append(bytes[i])
461 if i == 0 {
462 break
463 }
464 i = i - 1
465 }
466
467 return result
468 }
469
470 /// Manually encode quoteExactInputSingle calldata for V3 quoter
471 /// Quoter V2 signature: quoteExactInputSingle((address,address,uint256,uint24,uint160))
472 access(self) fun encodeQuoteExactInputSingle(
473 tokenIn: EVM.EVMAddress,
474 tokenOut: EVM.EVMAddress,
475 fee: UInt256,
476 amountIn: UInt256,
477 sqrtPriceLimitX96: UInt256
478 ): [UInt8] {
479 // Function selector for quoteExactInputSingle((address,address,uint256,uint24,uint160))
480 // This is for Quoter V2 which has params struct
481 // keccak256("quoteExactInputSingle((address,address,uint256,uint24,uint160))")[0:4] = 0xc6a5026a
482 var data: [UInt8] = [0xc6, 0xa5, 0x02, 0x6a]
483
484 // Encode tuple parameters (offset pointer to tuple data)
485 // For a single dynamic struct, offset is 32 (0x20)
486 data = data.concat(self.encodeUInt256(UInt256(32)))
487
488 // Now encode the tuple fields:
489 // 1. tokenIn (address)
490 data = data.concat(self.encodeAddress(tokenIn))
491 // 2. tokenOut (address)
492 data = data.concat(self.encodeAddress(tokenOut))
493 // 3. amountIn (uint256)
494 data = data.concat(self.encodeUInt256(amountIn))
495 // 4. fee (uint24 - encoded as uint256)
496 data = data.concat(self.encodeUInt256(fee))
497 // 5. sqrtPriceLimitX96 (uint160 - encoded as uint256)
498 data = data.concat(self.encodeUInt256(sqrtPriceLimitX96))
499
500 return data
501 }
502
503 /// Get quote using V3 quoter with manual encoding
504 access(all) fun getQuote(
505 fromTokenType: Type,
506 toTokenType: Type,
507 amount: UFix64
508 ): DeFiActions.Quote {
509 if self.coaCapability != nil {
510 if let coa = self.coaCapability!.borrow() {
511 let evmAmount = self.toEVMAmount(amount, decimals: 18)
512
513 // Use V3 quoter with manual encoding for quoteExactInputSingle
514 let quoteData = self.encodeQuoteExactInputSingle(
515 tokenIn: self.tokenIn,
516 tokenOut: self.tokenOut,
517 fee: UInt256(self.feeTier),
518 amountIn: evmAmount,
519 sqrtPriceLimitX96: UInt256(0)
520 )
521
522 let quoteResult = coa.call(
523 to: self.quoterAddress,
524 data: quoteData,
525 gasLimit: 500_000,
526 value: EVM.Balance(attoflow: 0)
527 )
528
529 if quoteResult.status == EVM.Status.successful && quoteResult.data.length > 0 {
530 // V3 quoter returns (amountOut, sqrtPriceX96After, initializedTicksCrossed, gasEstimate)
531 // We just need the first uint256
532 let decoded = EVM.decodeABI(types: [Type<UInt256>()], data: quoteResult.data)
533 if decoded.length > 0 {
534 let amountOut = decoded[0] as! UInt256
535 let expectedOut = self.fromEVMAmount(amountOut, decimals: 18)
536 let minAmount = expectedOut * 0.90 // 10% slippage for safety
537
538 emit QuoteFetched(
539 routerAddress: self.quoterAddress.toString(),
540 amountIn: amount,
541 amountOut: expectedOut,
542 feeTier: self.feeTier
543 )
544
545 return DeFiActions.Quote(
546 expectedAmount: expectedOut,
547 minAmount: minAmount,
548 slippageTolerance: 0.10,
549 deadline: nil,
550 data: {
551 "dex": "UniswapV3" as AnyStruct,
552 "feeTier": self.feeTier as AnyStruct,
553 "quoter": self.quoterAddress.toString() as AnyStruct
554 }
555 )
556 }
557 }
558 }
559 }
560
561 // Fallback to estimated quote based on fee tier
562 let feePercent = UFix64(self.feeTier) / 1_000_000.0
563 let estimatedOutput = amount * (1.0 - feePercent)
564
565 return DeFiActions.Quote(
566 expectedAmount: estimatedOutput,
567 minAmount: estimatedOutput * 0.95,
568 slippageTolerance: 0.05,
569 deadline: nil,
570 data: {
571 "dex": "UniswapV3" as AnyStruct,
572 "feeTier": self.feeTier as AnyStruct,
573 "estimated": true as AnyStruct
574 }
575 )
576 }
577
578 /// Get execution effort (not tracked for V3 v2)
579 access(all) fun getLastExecutionEffort(): UInt64 {
580 return 0
581 }
582
583 /// Get swapper info
584 access(all) fun getInfo(): DeFiActions.ComponentInfo {
585 return DeFiActions.ComponentInfo(
586 type: "Swapper",
587 identifier: "UniswapV3",
588 version: "2.0.0"
589 )
590 }
591 }
592
593 /// Create V3 swapper with explicit fee tier
594 access(all) fun createSwapper(
595 routerAddress: EVM.EVMAddress,
596 factoryAddress: EVM.EVMAddress,
597 quoterAddress: EVM.EVMAddress,
598 tokenIn: EVM.EVMAddress,
599 tokenOut: EVM.EVMAddress,
600 feeTier: UInt32,
601 cadenceTokenIn: String,
602 cadenceTokenOut: String,
603 coaCapability: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>?,
604 inVaultType: Type,
605 outVaultType: Type
606 ): @UniswapV3Swapper {
607 emit SwapperCreated(
608 routerAddress: routerAddress.toString(),
609 factoryAddress: factoryAddress.toString(),
610 feeTier: feeTier,
611 tokenIn: cadenceTokenIn,
612 tokenOut: cadenceTokenOut
613 )
614
615 return <- create UniswapV3Swapper(
616 routerAddress: routerAddress,
617 factoryAddress: factoryAddress,
618 quoterAddress: quoterAddress,
619 tokenIn: tokenIn,
620 tokenOut: tokenOut,
621 feeTier: feeTier,
622 cadenceTokenIn: cadenceTokenIn,
623 cadenceTokenOut: cadenceTokenOut,
624 coaCapability: coaCapability,
625 inVaultType: inVaultType,
626 outVaultType: outVaultType
627 )
628 }
629
630 /// Create V3 swapper with default fee tier (0.3%)
631 access(all) fun createSwapperWithDefaultFee(
632 routerAddress: EVM.EVMAddress,
633 factoryAddress: EVM.EVMAddress,
634 quoterAddress: EVM.EVMAddress,
635 tokenIn: EVM.EVMAddress,
636 tokenOut: EVM.EVMAddress,
637 cadenceTokenIn: String,
638 cadenceTokenOut: String,
639 coaCapability: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>?,
640 inVaultType: Type,
641 outVaultType: Type
642 ): @UniswapV3Swapper {
643 return <- self.createSwapper(
644 routerAddress: routerAddress,
645 factoryAddress: factoryAddress,
646 quoterAddress: quoterAddress,
647 tokenIn: tokenIn,
648 tokenOut: tokenOut,
649 feeTier: self.DEFAULT_FEE_TIER,
650 cadenceTokenIn: cadenceTokenIn,
651 cadenceTokenOut: cadenceTokenOut,
652 coaCapability: coaCapability,
653 inVaultType: inVaultType,
654 outVaultType: outVaultType
655 )
656 }
657
658 /// Create swapper with FlowSwap V3 defaults
659 access(all) fun createSwapperWithDefaults(
660 tokenIn: EVM.EVMAddress,
661 tokenOut: EVM.EVMAddress,
662 cadenceTokenIn: String,
663 cadenceTokenOut: String,
664 coaCapability: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>?,
665 inVaultType: Type,
666 outVaultType: Type
667 ): @UniswapV3Swapper {
668 return <- self.createSwapperWithDefaultFee(
669 routerAddress: self.defaultRouterAddress,
670 factoryAddress: self.defaultFactoryAddress,
671 quoterAddress: self.defaultQuoterAddress,
672 tokenIn: tokenIn,
673 tokenOut: tokenOut,
674 cadenceTokenIn: cadenceTokenIn,
675 cadenceTokenOut: cadenceTokenOut,
676 coaCapability: coaCapability,
677 inVaultType: inVaultType,
678 outVaultType: outVaultType
679 )
680 }
681
682 /// Admin resource
683 access(all) resource Admin {
684 // Admin functions for future updates
685 }
686
687 init() {
688 self.AdminStoragePath = /storage/UniswapV3SwapperConnectorV2Admin
689 self.DEFAULT_FEE_TIER = 3000 // 0.3%
690
691 // FlowSwap V3 Mainnet addresses (Flow EVM Chain ID: 747)
692 self.defaultRouterAddress = EVM.addressFromString("0xeEDC6Ff75e1b10B903D9013c358e446a73d35341") // SwapRouter02
693 self.defaultFactoryAddress = EVM.addressFromString("0xca6d7Bb03334bBf135902e1d919a5feccb461632") // V3 Core Factory
694 self.defaultQuoterAddress = EVM.addressFromString("0x370A8DF17742867a44e56223EC20D82092242C85") // Quoter
695
696 // WFLOW on Flow EVM Mainnet
697 self.wflowAddress = EVM.addressFromString("0xd3bF53DAC106A0290B0483EcBC89d40FcC961f3e")
698 }
699}
700
701