Smart Contract

UniswapV3SwapperConnector

A.ca7ee55e4fc3251a.UniswapV3SwapperConnector

Valid From

135,629,903

Deployed

5d ago
Feb 23, 2026, 12:43:39 AM UTC

Dependents

2 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import FlowToken from 0x1654653399040a61
3import EVM from 0xe467b9dd11fa00df
4import Burner from 0xf233dcee88fe0abe
5import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141
6import FlowEVMBridgeConfig from 0x1e4aa0b87d10b141
7
8import DeFiActions from 0xca7ee55e4fc3251a
9
10/// UniswapV3SwapperConnector
11///
12/// DeFiActions Swapper connector for Uniswap V3 routers on Flow EVM.
13/// Based on the official FlowActions UniswapV3SwapConnectors pattern.
14///
15/// Supports single-hop and multi-hop swaps using exactInput with proper
16/// FlowEVMBridge integration for token bridging.
17///
18access(all) contract UniswapV3SwapperConnector {
19
20    /// Events
21    access(all) event SwapperCreated(
22        routerAddress: String,
23        tokenPath: [String],
24        feePath: [UInt32]
25    )
26    access(all) event SwapExecuted(
27        routerAddress: String,
28        amountIn: UFix64,
29        amountOut: UFix64,
30        tokenPath: [String]
31    )
32    access(all) event QuoteFetched(
33        quoterAddress: String,
34        amountIn: UFix64,
35        amountOut: UFix64
36    )
37
38    /// Storage paths
39    access(all) let AdminStoragePath: StoragePath
40
41    /// Default addresses for FlowSwap V3 on Flow EVM Mainnet
42    access(all) let defaultRouterAddress: EVM.EVMAddress
43    access(all) let defaultQuoterAddress: EVM.EVMAddress
44    access(all) let defaultFactoryAddress: EVM.EVMAddress
45
46    /// WFLOW address on Flow EVM Mainnet
47    access(all) let wflowAddress: EVM.EVMAddress
48
49    /// ABI Helper: Encode a UInt256 as 32 bytes (big-endian)
50    access(all) fun abiUInt256(_ value: UInt256): [UInt8] {
51        var result: [UInt8] = []
52        var remaining = value
53        var bytes: [UInt8] = []
54
55        if remaining == 0 {
56            bytes.append(0)
57        } else {
58            while remaining > 0 {
59                bytes.append(UInt8(remaining % 256))
60                remaining = remaining / 256
61            }
62        }
63
64        // Pad to 32 bytes
65        while bytes.length < 32 {
66            bytes.append(0)
67        }
68
69        // Reverse to get big-endian
70        var i = 31
71        while i >= 0 {
72            result.append(bytes[i])
73            if i == 0 { break }
74            i = i - 1
75        }
76
77        return result
78    }
79
80    /// ABI Helper: Encode an address as 32 bytes
81    access(all) fun abiAddress(_ addr: EVM.EVMAddress): [UInt8] {
82        var result: [UInt8] = []
83        // 12 bytes of zero padding
84        var i = 0
85        while i < 12 {
86            result.append(0)
87            i = i + 1
88        }
89        // 20 bytes of address
90        for byte in addr.bytes {
91            result.append(byte)
92        }
93        return result
94    }
95
96    /// ABI Helper: Encode dynamic bytes with length prefix
97    access(all) fun abiDynamicBytes(_ data: [UInt8]): [UInt8] {
98        var result: [UInt8] = []
99        // Length as uint256
100        result = result.concat(self.abiUInt256(UInt256(data.length)))
101        // Data
102        result = result.concat(data)
103        // Pad to 32-byte boundary
104        let padding = (32 - (data.length % 32)) % 32
105        var i = 0
106        while i < padding {
107            result.append(0)
108            i = i + 1
109        }
110        return result
111    }
112
113    /// ABI Helper: Encode word (32 bytes)
114    access(all) fun abiWord(_ value: UInt256): [UInt8] {
115        return self.abiUInt256(value)
116    }
117
118    /// Encode exactInput tuple: (bytes path, address recipient, uint256 amountIn, uint256 amountOutMin)
119    access(all) fun encodeExactInputTuple(
120        pathBytes: [UInt8],
121        recipient: EVM.EVMAddress,
122        amountIn: UInt256,
123        amountOutMin: UInt256
124    ): [UInt8] {
125        let tupleHeadSize = 32 * 4  // 4 fields: offset, address, uint256, uint256
126
127        var head: [[UInt8]] = []
128        var tail: [[UInt8]] = []
129
130        // 1) bytes path (dynamic) -> offset to tail (after head)
131        head.append(self.abiWord(UInt256(tupleHeadSize)))
132        tail.append(self.abiDynamicBytes(pathBytes))
133
134        // 2) address recipient
135        head.append(self.abiAddress(recipient))
136
137        // 3) uint256 amountIn
138        head.append(self.abiUInt256(amountIn))
139
140        // 4) uint256 amountOutMin
141        head.append(self.abiUInt256(amountOutMin))
142
143        // Concatenate head and tail
144        var result: [UInt8] = []
145        for part in head {
146            result = result.concat(part)
147        }
148        for part in tail {
149            result = result.concat(part)
150        }
151        return result
152    }
153
154    /// UniswapV3Swapper resource implementing DeFiActions.Swapper
155    access(all) resource UniswapV3Swapper: DeFiActions.Swapper {
156        /// Router address for V3 swaps
157        access(all) let routerAddress: EVM.EVMAddress
158        /// Quoter address for price quotes
159        access(all) let quoterAddress: EVM.EVMAddress
160        /// Factory address for pool lookups
161        access(all) let factoryAddress: EVM.EVMAddress
162
163        /// Token path for multi-hop swaps (at least 2 addresses)
164        access(all) let tokenPath: [EVM.EVMAddress]
165        /// Fee path for V3 pools (length = tokenPath.length - 1)
166        access(all) let feePath: [UInt32]
167
168        /// Input vault type (Cadence)
169        access(self) let inVaultType: Type
170        /// Output vault type (Cadence)
171        access(self) let outVaultType: Type
172
173        /// COA capability for EVM interactions
174        access(self) let coaCapability: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>
175
176        init(
177            routerAddress: EVM.EVMAddress,
178            quoterAddress: EVM.EVMAddress,
179            factoryAddress: EVM.EVMAddress,
180            tokenPath: [EVM.EVMAddress],
181            feePath: [UInt32],
182            inVaultType: Type,
183            outVaultType: Type,
184            coaCapability: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>
185        ) {
186            pre {
187                tokenPath.length >= 2: "tokenPath must contain at least two addresses"
188                feePath.length == tokenPath.length - 1: "feePath length must be tokenPath.length - 1"
189                coaCapability.check(): "Invalid COA Capability"
190                FlowEVMBridgeConfig.getTypeAssociated(with: tokenPath[0]) == inVaultType:
191                    "inVaultType must be associated with tokenPath[0] in FlowEVMBridgeConfig"
192                FlowEVMBridgeConfig.getTypeAssociated(with: tokenPath[tokenPath.length - 1]) == outVaultType:
193                    "outVaultType must be associated with tokenPath[last] in FlowEVMBridgeConfig"
194            }
195            self.routerAddress = routerAddress
196            self.quoterAddress = quoterAddress
197            self.factoryAddress = factoryAddress
198            self.tokenPath = tokenPath
199            self.feePath = feePath
200            self.inVaultType = inVaultType
201            self.outVaultType = outVaultType
202            self.coaCapability = coaCapability
203        }
204
205        /// Build V3 path bytes: token0 + fee0 + token1 + fee1 + token2 ...
206        access(self) fun buildPathBytes(reverse: Bool): [UInt8] {
207            var path: [UInt8] = []
208
209            // Build indices based on direction
210            let length = self.tokenPath.length
211
212            var i = 0
213            while i < length {
214                // Get index based on direction
215                let tokenIdx = reverse ? (length - 1 - i) : i
216                let feeIdx = reverse ? (self.feePath.length - 1 - i) : i
217
218                // Add token address (20 bytes)
219                for byte in self.tokenPath[tokenIdx].bytes {
220                    path.append(byte)
221                }
222
223                // Add fee tier (3 bytes, big-endian) if not last token
224                if i < self.feePath.length {
225                    let fee = self.feePath[feeIdx]
226                    path.append(UInt8((fee >> 16) & 0xFF))
227                    path.append(UInt8((fee >> 8) & 0xFF))
228                    path.append(UInt8(fee & 0xFF))
229                }
230                i = i + 1
231            }
232            return path
233        }
234
235        /// Execute swap on Uniswap V3 via exactInput
236        /// Uses FlowEVMBridge for token bridging - the bridge handles FLOW↔WFLOW automatically
237        /// since WFLOW is associated with FlowToken.Vault in FlowEVMBridgeConfig
238        access(all) fun swap(
239            inVault: @{FungibleToken.Vault},
240            quote: DeFiActions.Quote
241        ): @{FungibleToken.Vault} {
242            let originalAmount = inVault.balance
243            let minOut = quote.minAmount
244
245            let coa = self.coaCapability.borrow()
246                ?? panic("Invalid COA Capability")
247
248            // Get input/output token addresses from path
249            let inToken = self.tokenPath[0]
250            let outToken = self.tokenPath[self.tokenPath.length - 1]
251
252            // EVM requires balances to be divisible by 10^10 wei for Cadence compatibility.
253            // Any amount with finer precision than 10^-8 (Cadence's precision) will fail.
254            //
255            // The fix: Round DOWN to nearest amount that's cleanly representable.
256            // 1. Convert Cadence amount to EVM wei
257            // 2. Round down to nearest 10^10 (floor division then multiply)
258            // 3. Convert back to Cadence
259            //
260            // This ensures the amount survives the Cadence↔EVM round-trip without rounding errors.
261
262            // Step 1: Convert to EVM wei
263            let evmWei = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount(
264                originalAmount,
265                erc20Address: inToken
266            )
267
268            // Step 2: Round DOWN to nearest 10^10 wei (Cadence-compatible precision)
269            // This is critical: balance % 10^10 must equal 0 for the bridge to accept it
270            let precision: UInt256 = 10_000_000_000 // 10^10
271            let cleanWei = (evmWei / precision) * precision
272
273            // Validate input amount is large enough for EVM bridging
274            // If cleanWei is 0, the amount is too small to bridge
275            assert(
276                cleanWei > 0,
277                message: "Input amount too small for EVM swap. Amount "
278                    .concat(originalAmount.toString())
279                    .concat(" converts to ")
280                    .concat(evmWei.toString())
281                    .concat(" wei which rounds to 0. Minimum ~0.00000001 tokens required for EVM swaps.")
282            )
283
284            // Step 3: Convert clean wei back to Cadence amount
285            let cleanAmount = FlowEVMBridgeUtils.convertERC20AmountToCadenceAmount(
286                cleanWei,
287                erc20Address: inToken
288            )
289
290            // Use the clean amount (may be slightly less than original due to rounding)
291            let amountIn = cleanAmount > 0.0 ? cleanAmount : originalAmount
292
293            // Move the vault - we'll use the full amount since precision is handled by the bridge
294            let vaultToBridge <- inVault
295
296            // Convert amounts to EVM format (18 decimals)
297            let evmAmountIn = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount(
298                amountIn,
299                erc20Address: inToken
300            )
301            let evmAmountOutMin = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount(
302                minOut,
303                erc20Address: outToken
304            )
305
306            // Calculate bridge fee (2x for deposit + withdraw)
307            let bridgeFeeBalance = EVM.Balance(attoflow: 0)
308            bridgeFeeBalance.setFLOW(flow: 2.0 * FlowEVMBridgeUtils.calculateBridgeFee(bytes: 256))
309            let feeVault <- coa.withdraw(balance: bridgeFeeBalance)
310            let feeVaultRef = &feeVault as auth(FungibleToken.Withdraw) &{FungibleToken.Vault}
311
312            // Bridge input tokens to EVM (now with safe amount)
313            coa.depositTokens(vault: <-vaultToBridge, feeProvider: feeVaultRef)
314
315            // If input is FlowToken (WFLOW), we need to wrap native FLOW to WFLOW ERC20
316            // The bridge gives us native balance, but DEXes need the WFLOW ERC20 token
317            let wflowAddress = UniswapV3SwapperConnector.wflowAddress
318            if inToken.toString().toLower() == wflowAddress.toString().toLower() {
319                // Wrap native FLOW to WFLOW by calling WFLOW.deposit() with value
320                let wrapData = EVM.encodeABIWithSignature("deposit()", [])
321                let wrapValue = EVM.Balance(attoflow: 0)
322                wrapValue.setFLOW(flow: amountIn)  // Send native FLOW as value
323
324                let wrapResult = coa.call(
325                    to: wflowAddress,
326                    data: wrapData,
327                    gasLimit: 100_000,
328                    value: wrapValue
329                )
330                if wrapResult.status != EVM.Status.successful {
331                    panic("Failed to wrap FLOW to WFLOW: ".concat(wrapResult.errorMessage))
332                }
333            }
334
335            // Build V3 path bytes
336            let pathBytes = self.buildPathBytes(reverse: false)
337
338            // Approve router to spend input tokens
339            let approveData = EVM.encodeABIWithSignature(
340                "approve(address,uint256)",
341                [self.routerAddress, evmAmountIn]
342            )
343            let approveResult = coa.call(
344                to: inToken,
345                data: approveData,
346                gasLimit: 120_000,
347                value: EVM.Balance(attoflow: 0)
348            )
349            if approveResult.status != EVM.Status.successful {
350                panic("Failed to approve router: ".concat(approveResult.errorMessage))
351            }
352
353            // Use the minAmount from quote directly (already includes slippage)
354            // The quote's minAmount is expectedOut * (1 - slippageTolerance)
355            let minOutForSwap = evmAmountOutMin
356
357            // exactInput selector: 0xb858183f
358            let selector: [UInt8] = [0xb8, 0x58, 0x18, 0x3f]
359
360            // Encode the tuple
361            let argsBlob = UniswapV3SwapperConnector.encodeExactInputTuple(
362                pathBytes: pathBytes,
363                recipient: coa.address(),
364                amountIn: evmAmountIn,
365                amountOutMin: minOutForSwap
366            )
367
368            // Head for single dynamic arg is always 32
369            let head = UniswapV3SwapperConnector.abiWord(UInt256(32))
370
371            // Final calldata = selector || head || tuple
372            let calldata = selector.concat(head).concat(argsBlob)
373
374            // Execute the swap - try V3 first, fall back to V2 if needed
375            var swapResult = coa.call(
376                to: self.routerAddress,
377                data: calldata,
378                gasLimit: 2_000_000,
379                value: EVM.Balance(attoflow: 0)
380            )
381
382            var evmAmountOut: UInt256 = 0
383
384            if swapResult.status == EVM.Status.successful {
385                // V3 swap succeeded
386                let decoded = EVM.decodeABI(types: [Type<UInt256>()], data: swapResult.data)
387                evmAmountOut = decoded.length > 0 ? decoded[0] as! UInt256 : UInt256(0)
388            } else {
389                // V3 failed - try PunchSwap V2 fallback
390                // PunchSwap V2 Router address (hardcoded to avoid adding new field to deployed contract)
391                let punchswapV2Router = EVM.addressFromString("0xf45AFe28fd5519d5f8C1d4787a4D5f724C0eFa4d")
392
393                // First approve V2 router
394                let approveV2Data = EVM.encodeABIWithSignature(
395                    "approve(address,uint256)",
396                    [punchswapV2Router, evmAmountIn]
397                )
398                let approveV2Result = coa.call(
399                    to: inToken,
400                    data: approveV2Data,
401                    gasLimit: 120_000,
402                    value: EVM.Balance(attoflow: 0)
403                )
404
405                // V2 swap: swapExactTokensForTokens(amountIn, amountOutMin, path[], to, deadline)
406                let deadline = UInt256(UInt64(getCurrentBlock().timestamp) + 300)
407                let v2SwapData = EVM.encodeABIWithSignature(
408                    "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)",
409                    [evmAmountIn, minOutForSwap, [inToken, outToken], coa.address(), deadline]
410                )
411
412                let v2Result = coa.call(
413                    to: punchswapV2Router,
414                    data: v2SwapData,
415                    gasLimit: 500_000,
416                    value: EVM.Balance(attoflow: 0)
417                )
418
419                if v2Result.status != EVM.Status.successful {
420                    panic("Both V3 and V2 swaps failed. V3: ".concat(swapResult.errorMessage).concat(" V2: ").concat(v2Result.errorMessage))
421                }
422
423                // V2 returns amounts array - last element is output amount
424                let v2Decoded = EVM.decodeABI(types: [Type<[UInt256]>()], data: v2Result.data)
425                if v2Decoded.length > 0 {
426                    let amounts = v2Decoded[0] as! [UInt256]
427                    evmAmountOut = amounts[amounts.length - 1]
428                }
429            }
430
431            // Calculate dynamic precision based on output token decimals
432            // For 18 decimal tokens: precision = 10^10 (gap between 18 and 8 decimals)
433            // For 6 decimal tokens: precision = 1 (6 decimals already fits in 8 decimal precision)
434            // Formula: precision = 10^max(0, tokenDecimals - 8)
435            let outDecimals = FlowEVMBridgeUtils.getTokenDecimals(evmContractAddress: outToken)
436            var outPrecision: UInt256 = 1
437            if outDecimals > 8 {
438                // Calculate 10^(decimals - 8)
439                var i: UInt8 = 0
440                while i < (outDecimals - 8) {
441                    outPrecision = outPrecision * 10
442                    i = i + 1
443                }
444            }
445            let cleanAmountOut = (evmAmountOut / outPrecision) * outPrecision
446
447            // Validate that we have a meaningful output amount
448            // If cleanAmountOut is 0, the swap succeeded but returned too little (dust)
449            if cleanAmountOut == 0 {
450                panic("Swap output amount too small after precision rounding. Raw output: "
451                    .concat(evmAmountOut.toString())
452                    .concat(" wei, token decimals: ")
453                    .concat(outDecimals.toString())
454                    .concat(". Increase your purchase amount or check liquidity."))
455            }
456
457            // Withdraw output tokens back to Cadence
458            // FlowEVMBridge handles WFLOW→FLOW conversion automatically since WFLOW is
459            // associated with FlowToken.Vault in FlowEVMBridgeConfig
460            let outVault <- coa.withdrawTokens(
461                type: self.outVaultType,
462                amount: cleanAmountOut,
463                feeProvider: feeVaultRef
464            )
465
466            // Handle leftover fee vault
467            if feeVault.balance > 0.0 {
468                coa.deposit(from: <-feeVault)
469            } else {
470                Burner.burn(<-feeVault)
471            }
472
473            let cadenceAmountOut = FlowEVMBridgeUtils.convertERC20AmountToCadenceAmount(
474                cleanAmountOut,
475                erc20Address: outToken
476            )
477
478            emit SwapExecuted(
479                routerAddress: self.routerAddress.toString(),
480                amountIn: amountIn,
481                amountOut: cadenceAmountOut,
482                tokenPath: self.getTokenPathStrings()
483            )
484
485            return <- outVault
486        }
487
488        /// Get quote using V3 Quoter via dryCall (view call, no gas spent)
489        access(all) fun getQuote(
490            fromTokenType: Type,
491            toTokenType: Type,
492            amount: UFix64
493        ): DeFiActions.Quote {
494            let coa = self.coaCapability.borrow()
495
496            if coa != nil {
497                let inToken = self.tokenPath[0]
498                let outToken = self.tokenPath[self.tokenPath.length - 1]
499
500                let evmAmount = FlowEVMBridgeUtils.convertCadenceAmountToERC20Amount(
501                    amount,
502                    erc20Address: inToken
503                )
504
505                // Build path bytes for quote - wrap in EVMBytes for ABI encoding
506                let pathBytes = EVM.EVMBytes(value: self.buildPathBytes(reverse: false))
507
508                // quoteExactInput(bytes path, uint256 amountIn) returns (uint256 amountOut)
509                let quoteData = EVM.encodeABIWithSignature(
510                    "quoteExactInput(bytes,uint256)",
511                    [pathBytes, evmAmount]
512                )
513
514                // Use dryCall for quotes - it's a view call that doesn't spend gas
515                let quoteResult = coa!.dryCall(
516                    to: self.quoterAddress,
517                    data: quoteData,
518                    gasLimit: 1_000_000,
519                    value: EVM.Balance(attoflow: 0)
520                )
521
522                if quoteResult.status == EVM.Status.successful && quoteResult.data.length > 0 {
523                    let decoded = EVM.decodeABI(types: [Type<UInt256>()], data: quoteResult.data)
524                    if decoded.length > 0 {
525                        let evmAmountOut = decoded[0] as! UInt256
526                        let expectedOut = FlowEVMBridgeUtils.convertERC20AmountToCadenceAmount(
527                            evmAmountOut,
528                            erc20Address: outToken
529                        )
530                        let minAmount = expectedOut * 0.90  // 10% slippage
531
532                        emit QuoteFetched(
533                            quoterAddress: self.quoterAddress.toString(),
534                            amountIn: amount,
535                            amountOut: expectedOut
536                        )
537
538                        return DeFiActions.Quote(
539                            expectedAmount: expectedOut,
540                            minAmount: minAmount,
541                            slippageTolerance: 0.10,
542                            deadline: nil,
543                            data: {
544                                "dex": "UniswapV3" as AnyStruct,
545                                "tokenPath": self.getTokenPathStrings() as AnyStruct
546                            }
547                        )
548                    }
549                }
550            }
551
552            // Fallback estimate (should rarely be used)
553            return DeFiActions.Quote(
554                expectedAmount: amount * 0.99,
555                minAmount: amount * 0.89,
556                slippageTolerance: 0.10,
557                deadline: nil,
558                data: {
559                    "dex": "UniswapV3" as AnyStruct,
560                    "estimated": true as AnyStruct
561                }
562            )
563        }
564
565        /// Get token path as strings
566        access(self) fun getTokenPathStrings(): [String] {
567            var result: [String] = []
568            for token in self.tokenPath {
569                result.append(token.toString())
570            }
571            return result
572        }
573
574        /// Get swapper info
575        access(all) fun getInfo(): DeFiActions.ComponentInfo {
576            return DeFiActions.ComponentInfo(
577                type: "Swapper",
578                identifier: "UniswapV3",
579                version: "3.0.0"
580            )
581        }
582    }
583
584    /// Create V3 swapper with token path and fee path
585    access(all) fun createSwapper(
586        routerAddress: EVM.EVMAddress,
587        quoterAddress: EVM.EVMAddress,
588        factoryAddress: EVM.EVMAddress,
589        tokenPath: [EVM.EVMAddress],
590        feePath: [UInt32],
591        inVaultType: Type,
592        outVaultType: Type,
593        coaCapability: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>
594    ): @UniswapV3Swapper {
595        var pathStrings: [String] = []
596        for token in tokenPath {
597            pathStrings.append(token.toString())
598        }
599
600        emit SwapperCreated(
601            routerAddress: routerAddress.toString(),
602            tokenPath: pathStrings,
603            feePath: feePath
604        )
605
606        return <- create UniswapV3Swapper(
607            routerAddress: routerAddress,
608            quoterAddress: quoterAddress,
609            factoryAddress: factoryAddress,
610            tokenPath: tokenPath,
611            feePath: feePath,
612            inVaultType: inVaultType,
613            outVaultType: outVaultType,
614            coaCapability: coaCapability
615        )
616    }
617
618    /// Create swapper with FlowSwap V3 defaults
619    access(all) fun createSwapperWithDefaults(
620        tokenPath: [EVM.EVMAddress],
621        feePath: [UInt32],
622        inVaultType: Type,
623        outVaultType: Type,
624        coaCapability: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>
625    ): @UniswapV3Swapper {
626        return <- self.createSwapper(
627            routerAddress: self.defaultRouterAddress,
628            quoterAddress: self.defaultQuoterAddress,
629            factoryAddress: self.defaultFactoryAddress,
630            tokenPath: tokenPath,
631            feePath: feePath,
632            inVaultType: inVaultType,
633            outVaultType: outVaultType,
634            coaCapability: coaCapability
635        )
636    }
637
638    /// Admin resource
639    access(all) resource Admin {
640        // Admin functions for future updates
641    }
642
643    init() {
644        self.AdminStoragePath = /storage/UniswapV3SwapperConnectorAdmin
645
646        // FlowSwap V3 Mainnet addresses (Flow EVM Chain ID: 747)
647        self.defaultRouterAddress = EVM.addressFromString("0xeEDC6Ff75e1b10B903D9013c358e446a73d35341")   // SwapRouter02
648        self.defaultFactoryAddress = EVM.addressFromString("0xca6d7Bb03334bBf135902e1d919a5feccb461632")  // V3 Core Factory
649        self.defaultQuoterAddress = EVM.addressFromString("0x370A8DF17742867a44e56223EC20D82092242C85")   // Quoter
650
651        // WFLOW on Flow EVM Mainnet
652        self.wflowAddress = EVM.addressFromString("0xd3bF53DAC106A0290B0483EcBC89d40FcC961f3e")
653    }
654}
655