Smart Contract
DCAControllerUnified
A.ca7ee55e4fc3251a.DCAControllerUnified
1import DCAPlanUnified from 0xca7ee55e4fc3251a
2import FungibleToken from 0xf233dcee88fe0abe
3import FlowToken from 0x1654653399040a61
4import EVM from 0xe467b9dd11fa00df
5
6/// DCAControllerUnified: Unified DCA Controller with Optional COA Support
7///
8/// Supports both Cadence-native (IncrementFi) and EVM (UniswapV3) swaps.
9/// COA capability is optional - only required for EVM token swaps.
10///
11/// Storage: /storage/DCAControllerUnified
12access(all) contract DCAControllerUnified {
13
14 access(all) entitlement Owner
15
16 access(all) let ControllerStoragePath: StoragePath
17 access(all) let ControllerPublicPath: PublicPath
18
19 access(all) event ControllerCreated(owner: Address)
20 access(all) event PlanAddedToController(owner: Address, planId: UInt64)
21 access(all) event PlanRemovedFromController(owner: Address, planId: UInt64)
22
23 access(all) resource interface ControllerPublic {
24 access(all) view fun getPlanIds(): [UInt64]
25 access(all) view fun getPlan(id: UInt64): &DCAPlanUnified.Plan?
26 access(all) fun getAllPlans(): [DCAPlanUnified.PlanDetails]
27 access(all) fun getActivePlans(): [DCAPlanUnified.PlanDetails]
28 }
29
30 access(all) resource Controller: ControllerPublic {
31 access(self) let plans: @{UInt64: DCAPlanUnified.Plan}
32
33 /// Source vault capability (withdraw for swap input)
34 access(self) var sourceVaultCap: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>?
35
36 /// Target vault capability (deposit for swap output)
37 access(self) var targetVaultCap: Capability<&{FungibleToken.Receiver}>?
38
39 /// Fee vault capability (withdraw FLOW for scheduler fees)
40 access(self) var feeVaultCap: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>?
41
42 /// COA capability - OPTIONAL (only needed for EVM token swaps)
43 access(self) var coaCap: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>?
44
45 init() {
46 self.plans <- {}
47 self.sourceVaultCap = nil
48 self.targetVaultCap = nil
49 self.feeVaultCap = nil
50 self.coaCap = nil
51 }
52
53 access(all) fun setSourceVaultCapability(
54 cap: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>
55 ) {
56 pre {
57 cap.check(): "Invalid source vault capability"
58 }
59 self.sourceVaultCap = cap
60 }
61
62 access(all) fun setTargetVaultCapability(
63 cap: Capability<&{FungibleToken.Receiver}>
64 ) {
65 pre {
66 cap.check(): "Invalid target vault capability"
67 }
68 self.targetVaultCap = cap
69 }
70
71 access(all) fun setFeeVaultCapability(
72 cap: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>
73 ) {
74 pre {
75 cap.check(): "Invalid fee vault capability"
76 }
77 self.feeVaultCap = cap
78 }
79
80 /// Set COA capability (optional - only for EVM swaps)
81 access(all) fun setCOACapability(
82 cap: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>
83 ) {
84 pre {
85 cap.check(): "Invalid COA capability"
86 }
87 self.coaCap = cap
88 }
89
90 access(all) fun getSourceVaultCapability(): Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>? {
91 return self.sourceVaultCap
92 }
93
94 access(all) fun getTargetVaultCapability(): Capability<&{FungibleToken.Receiver}>? {
95 return self.targetVaultCap
96 }
97
98 access(all) fun getFeeVaultCapability(): Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>? {
99 return self.feeVaultCap
100 }
101
102 access(all) fun getCOACapability(): Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>? {
103 return self.coaCap
104 }
105
106 access(all) fun addPlan(plan: @DCAPlanUnified.Plan) {
107 let planId = plan.id
108 assert(!self.plans.containsKey(planId), message: "Plan already exists")
109 self.plans[planId] <-! plan
110 emit PlanAddedToController(owner: self.owner!.address, planId: planId)
111 }
112
113 access(all) fun removePlan(id: UInt64): @DCAPlanUnified.Plan {
114 pre {
115 self.plans.containsKey(id): "Plan does not exist"
116 }
117 let plan <- self.plans.remove(key: id)!
118 emit PlanRemovedFromController(owner: self.owner!.address, planId: id)
119 return <- plan
120 }
121
122 access(Owner) fun borrowPlan(id: UInt64): &DCAPlanUnified.Plan? {
123 return &self.plans[id]
124 }
125
126 access(all) view fun getPlanIds(): [UInt64] {
127 return self.plans.keys
128 }
129
130 access(all) view fun getPlan(id: UInt64): &DCAPlanUnified.Plan? {
131 return &self.plans[id]
132 }
133
134 access(all) fun getAllPlans(): [DCAPlanUnified.PlanDetails] {
135 let details: [DCAPlanUnified.PlanDetails] = []
136 for id in self.plans.keys {
137 if let plan = &self.plans[id] as &DCAPlanUnified.Plan? {
138 details.append(plan.getDetails())
139 }
140 }
141 return details
142 }
143
144 access(all) fun getActivePlans(): [DCAPlanUnified.PlanDetails] {
145 let details: [DCAPlanUnified.PlanDetails] = []
146 for id in self.plans.keys {
147 if let plan = &self.plans[id] as &DCAPlanUnified.Plan? {
148 if plan.status == DCAPlanUnified.PlanStatus.Active {
149 details.append(plan.getDetails())
150 }
151 }
152 }
153 return details
154 }
155
156 /// Check if controller is configured for Cadence-native swaps
157 access(all) view fun isConfiguredForCadence(): Bool {
158 if let sourceCap = self.sourceVaultCap {
159 if let targetCap = self.targetVaultCap {
160 if let feeCap = self.feeVaultCap {
161 return sourceCap.check() && targetCap.check() && feeCap.check()
162 }
163 }
164 }
165 return false
166 }
167
168 /// Check if controller is configured for EVM swaps (includes COA)
169 access(all) view fun isConfiguredForEVM(): Bool {
170 if !self.isConfiguredForCadence() {
171 return false
172 }
173 if let coaCap = self.coaCap {
174 return coaCap.check()
175 }
176 return false
177 }
178
179 /// Legacy: Check if fully configured (for backward compatibility)
180 /// Returns true if configured for Cadence OR (Cadence + EVM)
181 access(all) view fun isFullyConfigured(): Bool {
182 return self.isConfiguredForCadence()
183 }
184
185 /// Check if configured for a specific plan
186 access(all) fun isConfiguredForPlan(planId: UInt64): Bool {
187 if let plan = &self.plans[planId] as &DCAPlanUnified.Plan? {
188 if plan.requiresEVM() {
189 return self.isConfiguredForEVM()
190 } else {
191 return self.isConfiguredForCadence()
192 }
193 }
194 return false
195 }
196 }
197
198 access(all) fun createController(): @Controller {
199 return <- create Controller()
200 }
201
202 init() {
203 self.ControllerStoragePath = /storage/DCAControllerUnified
204 self.ControllerPublicPath = /public/DCAControllerUnified
205 }
206}
207