Smart Contract

SwapKeepAliveHandlerV2

A.b13b21a06b75536d.SwapKeepAliveHandlerV2

Valid From

137,865,832

Deployed

1w ago
Feb 15, 2026, 06:15:51 PM UTC

Dependents

16 imports
1import FlowTransactionScheduler from 0xe467b9dd11fa00df
2import FlowToken from 0x1654653399040a61
3import FungibleToken from 0xf233dcee88fe0abe
4import EVM from 0xe467b9dd11fa00df
5import FlowEVMBridgeUtils from 0x1e4aa0b87d10b141
6
7access(all) contract SwapKeepAliveHandlerV2 {
8
9    access(all) entitlement Admin
10
11    /// Encodes UniswapV2-style swapExactTokensForTokens for a 2-token path using EVM ABI helpers.
12    access(all) fun encodeSwapExactTokensForTokens(
13        amountIn: UFix64,
14        amountOutMin: UFix64,
15        tokenInHex: String,
16        tokenOutHex: String,
17        toHex: String,
18        deadline: UFix64,
19        decimals: UInt8
20    ): [UInt8] {
21        let signature = "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)"
22        let amountInScaled: UInt256 = FlowEVMBridgeUtils.ufix64ToUInt256(value: amountIn, decimals: decimals)
23        let minOutScaled: UInt256 = FlowEVMBridgeUtils.ufix64ToUInt256(value: amountOutMin, decimals: decimals)
24        let path: [EVM.EVMAddress] = [
25            EVM.addressFromString(tokenInHex),
26            EVM.addressFromString(tokenOutHex)
27        ]
28        let to = EVM.addressFromString(toHex)
29        let dl: UInt256 = UInt256(UInt64(deadline))
30        return EVM.encodeABIWithSignature(signature, [
31            amountInScaled,
32            minOutScaled,
33            path,
34            to,
35            dl
36        ])
37    }
38
39    /// Encodes factory.getPool(tokenA, tokenB, fee) call to get pool address
40    access(all) fun encodeGetPool(
41        tokenAHex: String,
42        tokenBHex: String,
43        fee: UInt32
44    ): [UInt8] {
45        let signature = "getPool(address,address,uint24)"
46        let tokenA = EVM.addressFromString(tokenAHex)
47        let tokenB = EVM.addressFromString(tokenBHex)
48        return EVM.encodeABIWithSignature(signature, [tokenA, tokenB, UInt256(fee)])
49    }
50
51    /// Encodes generic PunchSwapV3HelperGeneric swapExactIn call
52    access(all) fun encodeV3SwapExactInGeneric(
53        poolHex: String,
54        tokenInHex: String,
55        tokenOutHex: String,
56        amountIn: UFix64,
57        amountOutMin: UFix64,
58        toHex: String,
59        fee: UInt32,
60        decimals: UInt8
61    ): [UInt8] {
62        let signature = "swapExactIn(address,address,address,uint256,uint256,address,uint24)"
63        let pool = EVM.addressFromString(poolHex)
64        let tokenIn = EVM.addressFromString(tokenInHex)
65        let tokenOut = EVM.addressFromString(tokenOutHex)
66        let to = EVM.addressFromString(toHex)
67        let amountInScaled: UInt256 = FlowEVMBridgeUtils.ufix64ToUInt256(value: amountIn, decimals: decimals)
68        let minOutScaled: UInt256 = FlowEVMBridgeUtils.ufix64ToUInt256(value: amountOutMin, decimals: decimals)
69        
70        return EVM.encodeABIWithSignature(signature, [
71            pool,
72            tokenIn,
73            tokenOut,
74            amountInScaled,
75            minOutScaled,
76            to,
77            UInt256(fee)
78        ])
79    }
80
81    // ============================================
82    // CONFIG STRUCTS - Shared + Protocol-Specific
83    // ============================================
84
85    /// Shared configuration for all swap types
86    access(all) struct BaseConfig {
87        access(all) let tokenInHex: String
88        access(all) let tokenOutHex: String
89        access(all) let recipientHex: String
90        access(all) var evmGasLimit: UInt64
91        access(all) var amountIn: UFix64
92        access(all) var tokenInDecimals: UInt8
93        access(all) var slippageBps: UInt16
94        access(all) var intervalSeconds: UFix64
95        access(all) var backupOffsetSeconds: UFix64
96
97        init(
98            tokenInHex: String,
99            tokenOutHex: String,
100            recipientHex: String,
101            evmGasLimit: UInt64,
102            amountIn: UFix64,
103            tokenInDecimals: UInt8,
104            slippageBps: UInt16,
105            intervalSeconds: UFix64,
106            backupOffsetSeconds: UFix64
107        ) {
108            self.tokenInHex = tokenInHex
109            self.tokenOutHex = tokenOutHex
110            self.recipientHex = recipientHex
111            self.evmGasLimit = evmGasLimit
112            self.amountIn = amountIn
113            self.tokenInDecimals = tokenInDecimals
114            self.slippageBps = slippageBps
115            self.intervalSeconds = intervalSeconds
116            self.backupOffsetSeconds = backupOffsetSeconds
117        }
118    }
119
120    /// Swap protocol type
121    access(all) enum SwapProtocol: UInt8 {
122        access(all) case V2
123        access(all) case V3
124    }
125
126    /// Interface for protocol-specific parameters
127    access(all) struct interface ProtocolParams {
128        access(all) fun getProtocol(): SwapProtocol
129    }
130
131    /// V2-specific parameters (UniswapV2 style)
132    access(all) struct V2Params: ProtocolParams {
133        access(all) let routerHex: String
134        access(all) var deadlineSlackSeconds: UFix64
135
136        init(routerHex: String, deadlineSlackSeconds: UFix64) {
137            self.routerHex = routerHex
138            self.deadlineSlackSeconds = deadlineSlackSeconds
139        }
140
141        access(all) fun getProtocol(): SwapProtocol {
142            return SwapProtocol.V2
143        }
144    }
145
146    /// V3-specific parameters (UniswapV3 style)
147    access(all) struct V3Params: ProtocolParams {
148        access(all) let helperHex: String      // V3 swap helper contract
149        access(all) let factoryHex: String     // V3 factory for pool lookup
150        access(all) var fee: UInt32            // Fee tier (500, 3000, 10000)
151
152        init(helperHex: String, factoryHex: String, fee: UInt32) {
153            self.helperHex = helperHex
154            self.factoryHex = factoryHex
155            self.fee = fee
156        }
157
158        access(all) fun getProtocol(): SwapProtocol {
159            return SwapProtocol.V3
160        }
161    }
162
163    /// Full configuration - base + protocol-specific
164    access(all) struct Config {
165        access(all) let base: BaseConfig
166        access(all) let protocolParams: {ProtocolParams}
167
168        init(base: BaseConfig, protocolParams: {ProtocolParams}) {
169            self.base = base
170            self.protocolParams = protocolParams
171        }
172    }
173
174    /// Pending transaction info - stored locally, keyed by txn ID
175    access(all) struct PendingTxParams {
176        access(all) let isPrimary: Bool
177
178        init(isPrimary: Bool) {
179            self.isPrimary = isPrimary
180        }
181    }
182
183    /// Handler resource that implements the Scheduled Transaction interface
184    access(all) resource Handler: FlowTransactionScheduler.TransactionHandler {
185        /// Persistent configuration and capabilities
186        access(self) var config: Config
187
188        /// Vault capability used to withdraw Flow fees for scheduling
189        access(self) let vaultCap: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>
190        /// Entitled capability to this handler, required when scheduling
191        access(self) let handlerCap: Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>
192        /// Capability to COA for EVM calls
193        access(self) let evmCap: Capability<auth(EVM.Call) &EVM.CadenceOwnedAccount>
194
195        /// deprecated test
196        access(self) var scheduledTxParams: {UInt64: PendingTxParams}
197        
198        access(all) var primaryReceipts: @{UInt64: FlowTransactionScheduler.ScheduledTransaction}
199        access(all) var recoveryReceipts: @{UInt64: FlowTransactionScheduler.ScheduledTransaction}
200
201        /// Farthest scheduled timestamps (for scheduling relative to these)
202        access(all) var farthestPrimaryTs: UFix64
203        access(all) var farthestRecoveryTs: UFix64
204
205
206        init(
207            config: Config,
208            vaultCap: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>,
209            handlerCap: Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>,
210            evmCap: Capability<auth(EVM.Call) &EVM.CadenceOwnedAccount>
211        ) {
212            self.config = config
213            self.vaultCap = vaultCap
214            self.handlerCap = handlerCap
215            self.evmCap = evmCap
216            self.scheduledTxParams = {}
217            self.primaryReceipts <- {}
218            self.recoveryReceipts <- {}
219            self.farthestPrimaryTs = 0.0
220            self.farthestRecoveryTs = 0.0
221        }
222
223        /// Execute the scheduled work - lookup txn info by ID, no args needed
224        access(FlowTransactionScheduler.Execute) fun executeTransaction(id: UInt64, data: AnyStruct?) {
225            let currentTs = getCurrentBlock().timestamp
226
227            let params = data as! PendingTxParams
228            
229            // Remove receipt from appropriate storage
230            if params.isPrimary {
231                let receipt <- self.primaryReceipts.remove(key: id)
232                destroy receipt
233                
234                // Primary: swap + schedule next primary (fixed/linear compute)
235                // Recovery scheduling is handled by recovery itself
236                self.performSwap(nowTs: currentTs)
237                self.scheduleOnePrimaryIfNeeded(nowTs: currentTs, systemBehind: false)
238            } else {
239                let receipt <- self.recoveryReceipts.remove(key: id)
240                destroy receipt
241                
242                // Recovery: handle scheduling gaps (no swap)
243                let systemBehind = self.primaryReceipts.keys.length < 5
244                
245                // Fill primaries if any were missed
246                self.scheduleOnePrimaryIfNeeded(nowTs: currentTs, systemBehind: systemBehind)
247                
248                // Schedule next recovery
249                self.scheduleRecoveryIfNeeded(nowTs: currentTs)
250            }
251        }
252
253        /// Schedule one primary transaction if we have < 10 scheduled
254        access(self) fun scheduleOnePrimaryIfNeeded(nowTs: UFix64, systemBehind: Bool) {
255            let primaryCount = self.primaryReceipts.keys.length
256            if primaryCount >= 10 {
257                return
258            }
259            
260            let useMedium = systemBehind && primaryCount == 0
261            let priority = useMedium 
262                ? FlowTransactionScheduler.Priority.Medium 
263                : FlowTransactionScheduler.Priority.Low
264            let effort: UInt64 = 375
265            
266            // Schedule relative to farthest primary (or from now if none)
267            let baseTs = self.farthestPrimaryTs > nowTs ? self.farthestPrimaryTs : nowTs
268            let ts = baseTs + self.config.base.intervalSeconds
269            
270            self.scheduleTransaction(timestamp: ts, isPrimary: true, priority: priority, effort: effort)
271        }
272        
273        /// Schedule recovery transaction if none pending
274        access(self) fun scheduleRecoveryIfNeeded(nowTs: UFix64) {
275            // Check if we already have a recovery scheduled
276            if self.recoveryReceipts.keys.length > 0 {
277                return
278            }
279            
280            let priority = FlowTransactionScheduler.Priority.Medium
281            let effort: UInt64 = 300
282            
283            // Schedule recovery relative to farthest primary + offset
284            let baseTs = self.farthestPrimaryTs > nowTs ? self.farthestPrimaryTs : nowTs
285            let ts = baseTs + self.config.base.backupOffsetSeconds
286            
287            self.scheduleTransaction(timestamp: ts, isPrimary: false, priority: priority, effort: effort)
288        }
289
290        /// Core scheduling function
291        access(self) fun scheduleTransaction(
292            timestamp: UFix64,
293            isPrimary: Bool,
294            priority: FlowTransactionScheduler.Priority,
295            effort: UInt64
296        ) {
297            let vaultRef = self.vaultCap.borrow()
298                ?? panic("SwapKeepAliveHandlerV2: invalid vault capability")
299
300            let txParams = PendingTxParams(isPrimary: isPrimary)
301            
302            var ts = timestamp
303            var step: UFix64 = 1.0
304            var attempts: UInt64 = 0
305            let maxAttempts: UInt64 = 20
306
307            while attempts < maxAttempts {
308                let est = FlowTransactionScheduler.estimate(
309                    data: txParams,
310                    timestamp: ts,
311                    priority: priority,
312                    executionEffort: effort
313                )
314                
315                if est.timestamp != nil {
316                    let fees <- vaultRef.withdraw(amount: est.flowFee ?? 0.0) as! @FlowToken.Vault
317                    let receipt <- FlowTransactionScheduler.schedule(
318                        handlerCap: self.handlerCap,
319                        data: txParams,
320                        timestamp: ts,
321                        priority: priority,
322                        executionEffort: effort,
323                        fees: <-fees
324                    )
325                    
326                    // Store txn info locally, keyed by the receipt's ID
327                    let txnId = receipt.id
328                    
329                    // Store receipt in appropriate dict and update farthest timestamp
330                    if isPrimary {
331                        let old <- self.primaryReceipts.insert(key: txnId, <-receipt)
332                        destroy old
333                        if ts > self.farthestPrimaryTs {
334                            self.farthestPrimaryTs = ts
335                        }
336                    } else {
337                        let old <- self.recoveryReceipts.insert(key: txnId, <-receipt)
338                        destroy old
339                        if ts > self.farthestRecoveryTs {
340                            self.farthestRecoveryTs = ts
341                        }
342                    }
343                    
344                    return
345                }
346                
347                // Exponential backoff
348                ts = ts + step
349                step = step * 2.0
350                attempts = attempts + 1
351            }
352            
353            panic("SwapKeepAliveHandlerV2: Failed to schedule after ".concat(maxAttempts.toString()).concat(" attempts"))
354        }
355
356        /// Executes a token swap via the COA
357        access(self) fun performSwap(nowTs: UFix64) {
358            let coa = self.evmCap.borrow()
359                ?? panic("SwapKeepAliveHandlerV2: invalid COA capability")
360            
361            let base = self.config.base
362            let tokenIn = EVM.addressFromString(base.tokenInHex)
363            let amountInScaled: UInt256 = FlowEVMBridgeUtils.ufix64ToUInt256(
364                value: base.amountIn,
365                decimals: base.tokenInDecimals
366            )
367            
368            // Calculate min output with slippage
369            let bps: UFix64 = UFix64(base.slippageBps)
370            let minOutFactor: UFix64 = (10000.0 - bps) / 10000.0
371            let _minOut: UFix64 = base.amountIn * minOutFactor
372
373            // Execute based on protocol type
374            let params = self.config.protocolParams
375            switch params.getProtocol() {
376                case SwapProtocol.V2:
377                    let v2 = params as! V2Params
378                    self.performV2Swap(coa: coa, tokenIn: tokenIn, amountInScaled: amountInScaled, minOut: _minOut, nowTs: nowTs, v2: v2)
379                case SwapProtocol.V3:
380                    let v3 = params as! V3Params
381                    self.performV3Swap(coa: coa, tokenIn: tokenIn, amountInScaled: amountInScaled, minOut: _minOut, v3: v3)
382            }
383        }
384
385        /// V2 swap implementation
386        access(self) fun performV2Swap(
387            coa: auth(EVM.Call) &EVM.CadenceOwnedAccount,
388            tokenIn: EVM.EVMAddress,
389            amountInScaled: UInt256,
390            minOut: UFix64,
391            nowTs: UFix64,
392            v2: V2Params
393        ) {
394            let base = self.config.base
395            let router = EVM.addressFromString(v2.routerHex)
396            
397            // Check and approve allowance
398            self.ensureAllowance(coa: coa, tokenIn: tokenIn, spender: router, amount: amountInScaled)
399            
400            // Build V2 swap payload
401            let deadline: UFix64 = nowTs + v2.deadlineSlackSeconds
402            let payload = SwapKeepAliveHandlerV2.encodeSwapExactTokensForTokens(
403                amountIn: base.amountIn,
404                amountOutMin: minOut,
405                tokenInHex: base.tokenInHex,
406                tokenOutHex: base.tokenOutHex,
407                toHex: base.recipientHex,
408                deadline: deadline,
409                decimals: base.tokenInDecimals
410            )
411            
412            // Execute
413            let res = coa.call(
414                to: router,
415                data: payload,
416                gasLimit: base.evmGasLimit,
417                value: EVM.Balance(attoflow: 0)
418            )
419            
420            if res.status != EVM.Status.successful {
421                panic("SwapKeepAliveHandlerV2: V2 swap failed | Status: ".concat(res.status.rawValue.toString()))
422            }
423        }
424
425        /// V3 swap implementation
426        access(self) fun performV3Swap(
427            coa: auth(EVM.Call) &EVM.CadenceOwnedAccount,
428            tokenIn: EVM.EVMAddress,
429            amountInScaled: UInt256,
430            minOut: UFix64,
431            v3: V3Params
432        ) {
433            let base = self.config.base
434            let helper = EVM.addressFromString(v3.helperHex)
435            let factory = EVM.addressFromString(v3.factoryHex)
436            
437            // Lookup pool from factory
438            let getPoolCalldata = SwapKeepAliveHandlerV2.encodeGetPool(
439                tokenAHex: base.tokenInHex,
440                tokenBHex: base.tokenOutHex,
441                fee: v3.fee
442            )
443            
444            let poolRes = coa.call(
445                to: factory,
446                data: getPoolCalldata,
447                gasLimit: 100000,
448                value: EVM.Balance(attoflow: 0)
449            )
450            
451            assert(poolRes.status == EVM.Status.successful, message: "SwapKeepAliveHandlerV2: factory.getPool failed")
452            let decoded = EVM.decodeABI(types: [Type<EVM.EVMAddress>()], data: poolRes.data)
453            let poolAddress = decoded[0] as! EVM.EVMAddress
454            
455            let zeroAddress = EVM.addressFromString("0x0000000000000000000000000000000000000000")
456            assert(poolAddress.bytes != zeroAddress.bytes, message: "SwapKeepAliveHandlerV2: pool does not exist")
457            
458            // Transfer tokens to helper (V3 helper uses callback pattern)
459            let transferCalldata = EVM.encodeABIWithSignature(
460                "transfer(address,uint256)",
461                [helper, amountInScaled]
462            )
463            let transferRes = coa.call(
464                to: tokenIn,
465                data: transferCalldata,
466                gasLimit: 100000,
467                value: EVM.Balance(attoflow: 0)
468            )
469            assert(transferRes.status == EVM.Status.successful, message: "SwapKeepAliveHandlerV2: token transfer to helper failed")
470            
471            // Build V3 swap payload
472            let payload = SwapKeepAliveHandlerV2.encodeV3SwapExactInGeneric(
473                poolHex: poolAddress.toString(),
474                tokenInHex: base.tokenInHex,
475                tokenOutHex: base.tokenOutHex,
476                amountIn: base.amountIn,
477                amountOutMin: minOut,
478                toHex: base.recipientHex,
479                fee: v3.fee,
480                decimals: base.tokenInDecimals
481            )
482            
483            // Execute
484            let res = coa.call(
485                to: helper,
486                data: payload,
487                gasLimit: base.evmGasLimit,
488                value: EVM.Balance(attoflow: 0)
489            )
490            
491            if res.status != EVM.Status.successful {
492                panic("SwapKeepAliveHandlerV2: V3 swap failed | Status: ".concat(res.status.rawValue.toString()))
493            }
494        }
495
496        /// Helper: ensure token allowance for spender
497        access(self) fun ensureAllowance(
498            coa: auth(EVM.Call) &EVM.CadenceOwnedAccount,
499            tokenIn: EVM.EVMAddress,
500            spender: EVM.EVMAddress,
501            amount: UInt256
502        ) {
503            let allowanceCalldata = EVM.encodeABIWithSignature("allowance(address,address)", [coa.address(), spender])
504            let allowanceRes = coa.call(
505                to: tokenIn,
506                data: allowanceCalldata,
507                gasLimit: 100000,
508                value: EVM.Balance(attoflow: 0)
509            )
510            
511            if allowanceRes.status == EVM.Status.successful {
512                let decoded = EVM.decodeABI(types: [Type<UInt256>()], data: allowanceRes.data)
513                let currentAllowance = decoded[0] as! UInt256
514                
515                if currentAllowance < amount {
516                    let maxApproval: UInt256 = 115792089237316195423570985008687907853269984665640564039457584007913129639935
517                    let approveCalldata = EVM.encodeABIWithSignature("approve(address,uint256)", [spender, maxApproval])
518                    let approveRes = coa.call(
519                        to: tokenIn,
520                        data: approveCalldata,
521                        gasLimit: 100000,
522                        value: EVM.Balance(attoflow: 0)
523                    )
524                    
525                    if approveRes.status != EVM.Status.successful {
526                        panic("SwapKeepAliveHandlerV2: Token approval failed")
527                    }
528                }
529            }
530        }
531
532        // ============================================
533        // VIEW FUNCTIONS
534        // ============================================
535
536        access(all) view fun getViews(): [Type] {
537            return []
538        }
539
540        access(all) fun resolveView(_ view: Type): AnyStruct? {
541            return nil
542        }
543
544        access(all) view fun getPrimaryCount(): Int {
545            return self.primaryReceipts.keys.length
546        }
547
548        access(all) view fun getRecoveryCount(): Int {
549            return self.recoveryReceipts.keys.length
550        }
551
552        access(all) view fun getPendingCount(): Int {
553            return self.primaryReceipts.keys.length + self.recoveryReceipts.keys.length
554        }
555
556        access(all) view fun getScheduledIds(): [UInt64] {
557            return self.primaryReceipts.keys.concat(self.recoveryReceipts.keys)
558        }
559
560        // ============================================
561        // ADMIN FUNCTIONS
562        // ============================================
563
564        /// Schedule a batch of transactions
565        access(Admin) fun scheduleBatch(count: UInt64, escalateFirst: Bool) {
566            let nowTs = getCurrentBlock().timestamp
567            var i: UInt64 = 0
568            
569            while i < count && self.primaryReceipts.keys.length < 10 {
570                let escalated = (i == 0 && escalateFirst)
571                let priority = escalated 
572                    ? FlowTransactionScheduler.Priority.Medium 
573                    : FlowTransactionScheduler.Priority.Low
574                
575                // Use farthest timestamp tracking - scheduleTransaction updates it
576                self.scheduleOnePrimaryIfNeeded(nowTs: nowTs, systemBehind: escalated)
577                i = i + 1
578            }
579            
580            // Schedule recovery if needed
581            self.scheduleRecoveryIfNeeded(nowTs: nowTs)
582        }
583
584        /// Cancel a specific transaction by ID
585        access(Admin) fun cancelById(txnId: UInt64) {
586            if let receipt <- self.primaryReceipts.remove(key: txnId) {
587                let status = FlowTransactionScheduler.getStatus(id: txnId)
588                if status == FlowTransactionScheduler.Status.Scheduled {
589                    destroy FlowTransactionScheduler.cancel(scheduledTx: <-receipt)
590                } else {
591                    // Transaction is not in Scheduled state (Executed, Canceled, Unknown, or nil)
592                    // Just destroy the local receipt without calling cancel
593                    destroy receipt
594                }
595            }
596            if let receipt <- self.recoveryReceipts.remove(key: txnId) {
597                let status = FlowTransactionScheduler.getStatus(id: txnId)
598                if status == FlowTransactionScheduler.Status.Scheduled {
599                    destroy FlowTransactionScheduler.cancel(scheduledTx: <-receipt)
600                } else {
601                    destroy receipt
602                }
603            }
604        }
605
606        /// Cancel all transactions
607        access(Admin) fun cancelAll() {
608            // Cancel primaries
609            let primaryKeys = self.primaryReceipts.keys
610            for id in primaryKeys {
611                self.cancelById(txnId: id)
612            }
613            
614            // Cancel recoveries
615            let recoveryKeys = self.recoveryReceipts.keys
616            for id in recoveryKeys {
617                self.cancelById(txnId: id)
618            }
619            
620            self.scheduledTxParams = {}
621            self.farthestPrimaryTs = 0.0
622            self.farthestRecoveryTs = 0.0
623        }
624    }
625
626    /// Factory for the handler resource
627    access(all) fun createHandler(
628        config: Config,
629        vaultCap: Capability<auth(FungibleToken.Withdraw) &FlowToken.Vault>,
630        handlerCap: Capability<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>,
631        evmCap: Capability<auth(EVM.Call) &EVM.CadenceOwnedAccount>
632    ): @Handler {
633        return <- create Handler(
634            config: config,
635            vaultCap: vaultCap,
636            handlerCap: handlerCap,
637            evmCap: evmCap
638        )
639    }
640
641    access(all) init() {}
642}
643
644