Smart Contract
UniswapV3SwapperConnector
A.ca7ee55e4fc3251a.UniswapV3SwapperConnector
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