Smart Contract
DCATransactionHandlerEVMMinimal
A.ca7ee55e4fc3251a.DCATransactionHandlerEVMMinimal
1import FlowTransactionScheduler from 0xe467b9dd11fa00df
2import DCAControllerUnified from 0xca7ee55e4fc3251a
3import DCAPlanUnified from 0xca7ee55e4fc3251a
4import FungibleToken from 0xf233dcee88fe0abe
5import FlowToken from 0x1654653399040a61
6import EVM from 0xe467b9dd11fa00df
7import DeFiActions from 0xca7ee55e4fc3251a
8import UniswapV3SwapperConnector from 0xca7ee55e4fc3251a
9
10/// DCATransactionHandlerEVMMinimal: Incremental EVM swap handler
11///
12/// PURPOSE: Test EVM swap execution step by step
13/// Step 3: Add UniswapV3 swap logic
14///
15/// Storage: /storage/DCATransactionHandlerEVMMinimal
16access(all) contract DCATransactionHandlerEVMMinimal {
17
18 /// Simple transaction data
19 access(all) struct SimpleTransactionData {
20 access(all) let planId: UInt64
21 init(planId: UInt64) { self.planId = planId }
22 }
23
24 /// Events
25 access(all) event HandlerExecuted(
26 transactionId: UInt64,
27 planId: UInt64,
28 owner: Address,
29 requiresEVM: Bool,
30 timestamp: UFix64
31 )
32
33 access(all) event SwapCompleted(
34 transactionId: UInt64,
35 planId: UInt64,
36 amountIn: UFix64,
37 amountOut: UFix64
38 )
39
40 // Debug events for step-by-step tracing
41 access(all) event DebugStep(step: String, transactionId: UInt64)
42 access(all) event DebugQuote(expectedAmount: UFix64, transactionId: UInt64)
43
44 /// Handler resource
45 access(all) resource Handler: FlowTransactionScheduler.TransactionHandler {
46 access(self) let controllerCap: Capability<auth(DCAControllerUnified.Owner) &DCAControllerUnified.Controller>
47
48 init(controllerCap: Capability<auth(DCAControllerUnified.Owner) &DCAControllerUnified.Controller>) {
49 pre { controllerCap.check(): "Invalid controller capability" }
50 self.controllerCap = controllerCap
51 }
52
53 access(FlowTransactionScheduler.Execute) fun executeTransaction(id: UInt64, data: AnyStruct?) {
54 emit DebugStep(step: "1_start", transactionId: id)
55
56 let timestamp = getCurrentBlock().timestamp
57 let ownerAddress = self.controllerCap.address
58
59 // Parse data
60 let txData = data as? SimpleTransactionData
61 if txData == nil {
62 emit DebugStep(step: "FAIL_invalid_data", transactionId: id)
63 return
64 }
65 let planId = txData!.planId
66
67 emit DebugStep(step: "2_data_parsed", transactionId: id)
68
69 // Borrow controller
70 let controller = self.controllerCap.borrow()
71 if controller == nil {
72 emit DebugStep(step: "FAIL_no_controller", transactionId: id)
73 return
74 }
75
76 emit DebugStep(step: "3_controller_borrowed", transactionId: id)
77
78 // Borrow plan
79 let planRef = controller!.borrowPlan(id: planId)
80 if planRef == nil {
81 emit DebugStep(step: "FAIL_no_plan", transactionId: id)
82 return
83 }
84
85 emit DebugStep(step: "4_plan_borrowed", transactionId: id)
86
87 let requiresEVM = planRef!.requiresEVM()
88
89 if !requiresEVM {
90 emit DebugStep(step: "5_not_evm_plan", transactionId: id)
91 emit HandlerExecuted(transactionId: id, planId: planId, owner: ownerAddress, requiresEVM: false, timestamp: timestamp)
92 return
93 }
94
95 emit DebugStep(step: "5_evm_plan_confirmed", transactionId: id)
96
97 // Get COA capability
98 let coaCap = controller!.getCOACapability()
99 if coaCap == nil || !coaCap!.check() {
100 emit DebugStep(step: "FAIL_coa_issue", transactionId: id)
101 return
102 }
103
104 emit DebugStep(step: "6_coa_valid", transactionId: id)
105
106 // Get source vault
107 let sourceVaultCap = controller!.getSourceVaultCapability()
108 if sourceVaultCap == nil || !sourceVaultCap!.check() {
109 emit DebugStep(step: "FAIL_source_vault", transactionId: id)
110 return
111 }
112 let sourceVault = sourceVaultCap!.borrow()!
113
114 emit DebugStep(step: "7_source_vault_valid", transactionId: id)
115
116 // Get target vault
117 let targetVaultCap = controller!.getTargetVaultCapability()
118 if targetVaultCap == nil || !targetVaultCap!.check() {
119 emit DebugStep(step: "FAIL_target_vault", transactionId: id)
120 return
121 }
122
123 emit DebugStep(step: "8_target_vault_valid", transactionId: id)
124
125 let amountIn = planRef!.amountPerInterval
126 if sourceVault.balance < amountIn {
127 emit DebugStep(step: "FAIL_insufficient_balance", transactionId: id)
128 return
129 }
130
131 emit DebugStep(step: "9_balance_ok", transactionId: id)
132
133 // Withdraw FLOW for swap
134 let tokensToSwap <- sourceVault.withdraw(amount: amountIn) as! @FlowToken.Vault
135
136 emit DebugStep(step: "10_flow_withdrawn", transactionId: id)
137
138 // Setup EVM addresses
139 let usdfEVMAddress = EVM.addressFromString("0x2aabea2058b5ac2d339b163c6ab6f2b6d53aabed")
140 let wflowEVMAddress = EVM.addressFromString("0xd3bF53DAC106A0290B0483EcBC89d40FcC961f3e")
141 let tokenPath: [EVM.EVMAddress] = [wflowEVMAddress, usdfEVMAddress]
142
143 emit DebugStep(step: "11_addresses_setup", transactionId: id)
144
145 // Create swapper
146 let swapper <- UniswapV3SwapperConnector.createSwapperWithDefaults(
147 tokenPath: tokenPath,
148 feePath: [3000], // 0.3% fee tier
149 inVaultType: planRef!.sourceTokenType,
150 outVaultType: planRef!.targetTokenType,
151 coaCapability: coaCap!
152 )
153
154 emit DebugStep(step: "12_swapper_created", transactionId: id)
155
156 // Get quote
157 let quote = swapper.getQuote(
158 fromTokenType: planRef!.sourceTokenType,
159 toTokenType: planRef!.targetTokenType,
160 amount: amountIn
161 )
162
163 emit DebugStep(step: "13_quote_received", transactionId: id)
164 emit DebugQuote(expectedAmount: quote.expectedAmount, transactionId: id)
165
166 // Apply slippage
167 let minAmount = quote.expectedAmount * (10000.0 - UFix64(planRef!.maxSlippageBps)) / 10000.0
168 let adjustedQuote = DeFiActions.Quote(
169 expectedAmount: quote.expectedAmount,
170 minAmount: minAmount,
171 slippageTolerance: UFix64(planRef!.maxSlippageBps) / 10000.0,
172 deadline: nil,
173 data: quote.data
174 )
175
176 emit DebugStep(step: "14_quote_adjusted", transactionId: id)
177
178 // Execute swap
179 let swapped <- swapper.swap(inVault: <-tokensToSwap, quote: adjustedQuote)
180 let amountOut = swapped.balance
181
182 emit DebugStep(step: "15_swap_executed", transactionId: id)
183
184 // Deposit to target
185 let targetVault = targetVaultCap!.borrow()!
186 targetVault.deposit(from: <-swapped)
187
188 emit DebugStep(step: "16_deposited", transactionId: id)
189
190 // Cleanup
191 destroy swapper
192
193 emit HandlerExecuted(transactionId: id, planId: planId, owner: ownerAddress, requiresEVM: true, timestamp: timestamp)
194 emit SwapCompleted(transactionId: id, planId: planId, amountIn: amountIn, amountOut: amountOut)
195
196 emit DebugStep(step: "17_complete", transactionId: id)
197 }
198
199 access(all) view fun getViews(): [Type] {
200 return [Type<StoragePath>()]
201 }
202
203 access(all) fun resolveView(_ view: Type): AnyStruct? {
204 switch view {
205 case Type<StoragePath>():
206 return /storage/DCATransactionHandlerEVMMinimal
207 default:
208 return nil
209 }
210 }
211 }
212
213 access(all) fun createHandler(
214 controllerCap: Capability<auth(DCAControllerUnified.Owner) &DCAControllerUnified.Controller>
215 ): @Handler {
216 return <- create Handler(controllerCap: controllerCap)
217 }
218
219 access(all) fun createTransactionData(planId: UInt64): SimpleTransactionData {
220 return SimpleTransactionData(planId: planId)
221 }
222}
223