Smart Contract

UniswapV3SwapperConnectorV2

A.17ae3b1b0b0d50db.UniswapV3SwapperConnectorV2

Valid From

135,478,169

Deployed

6d ago
Feb 22, 2026, 02:18:41 AM UTC

Dependents

0 imports
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