Smart Contract

DCAControllerV3

A.ca7ee55e4fc3251a.DCAControllerV3

Valid From

135,594,705

Deployed

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

Dependents

9 imports
1import DCAPlanV3 from 0xca7ee55e4fc3251a
2import FungibleToken from 0xf233dcee88fe0abe
3import FlowToken from 0x1654653399040a61
4import EVM from 0xe467b9dd11fa00df
5
6/// DCAController: User's DCA management resource
7///
8/// Each user has one DCAController stored in their account that:
9/// - Holds all their DCA plans
10/// - Stores capabilities to their token vaults
11/// - Stores COA capability for EVM swap execution
12/// - Provides public interface for querying plans
13///
14/// Educational Notes:
15/// - One controller per user, stored at /storage/DCAControllerV3
16/// - Controller holds references (not vaults) to user's tokens and COA
17/// - Scheduled handlers borrow capabilities from the controller
18/// - Owner entitlement grants privileged access to handler
19access(all) contract DCAControllerV3 {
20
21    /// Owner entitlement for privileged controller access
22    /// This is required by DCATransactionHandler to update plans
23    access(all) entitlement Owner
24
25    /// Storage paths
26    access(all) let ControllerStoragePath: StoragePath
27    access(all) let ControllerPublicPath: PublicPath
28
29    /// Event emitted when a controller is created
30    access(all) event ControllerCreated(owner: Address)
31
32    /// Event emitted when a plan is added to controller
33    access(all) event PlanAddedToController(owner: Address, planId: UInt64)
34
35    /// Event emitted when a plan is removed from controller
36    access(all) event PlanRemovedFromController(owner: Address, planId: UInt64)
37
38    /// Public interface for reading controller state
39    access(all) resource interface ControllerPublic {
40        access(all) view fun getPlanIds(): [UInt64]
41        access(all) view fun getPlan(id: UInt64): &DCAPlanV3.Plan?
42        access(all) fun getAllPlans(): [DCAPlanV3.PlanDetails]
43        access(all) fun getActivePlans(): [DCAPlanV3.PlanDetails]
44    }
45
46    /// The main Controller resource
47    ///
48    /// Stores all DCA plans for a user and manages vault capabilities.
49    access(all) resource Controller: ControllerPublic {
50        /// Dictionary of all plans owned by this controller
51        access(self) let plans: @{UInt64: DCAPlanV3.Plan}
52
53        /// Capability to withdraw from source token vault (typically FLOW)
54        /// This is used by scheduled handlers to fund DCA executions
55        access(self) var sourceVaultCap: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>?
56
57        /// Capability to deposit to target token vault (typically USDF)
58        /// This is used by scheduled handlers to deposit acquired tokens
59        access(self) var targetVaultCap: Capability<&{FungibleToken.Receiver}>?
60
61        /// Capability to withdraw FLOW for scheduler fees
62        /// This is used by scheduled handlers to pay for autonomous execution
63        access(self) var feeVaultCap: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>?
64
65        /// COA (Cadence-Owned Account) capability for EVM operations
66        /// This is used by scheduled handlers to execute swaps on Flow EVM DEXes
67        access(self) var coaCap: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>?
68
69        init() {
70            self.plans <- {}
71            self.sourceVaultCap = nil
72            self.targetVaultCap = nil
73            self.feeVaultCap = nil
74            self.coaCap = nil
75        }
76
77        /// Set the source vault capability
78        ///
79        /// This should be called once during setup to give the controller
80        /// permission to withdraw from the user's source token vault.
81        ///
82        /// @param cap: Capability with withdraw auth to source vault
83        access(all) fun setSourceVaultCapability(
84            cap: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>
85        ) {
86            pre {
87                cap.check(): "Invalid source vault capability"
88            }
89            self.sourceVaultCap = cap
90        }
91
92        /// Set the target vault capability
93        ///
94        /// This should be called once during setup to give the controller
95        /// permission to deposit to the user's target token vault.
96        ///
97        /// @param cap: Capability to deposit to target vault
98        access(all) fun setTargetVaultCapability(
99            cap: Capability<&{FungibleToken.Receiver}>
100        ) {
101            pre {
102                cap.check(): "Invalid target vault capability"
103            }
104            self.targetVaultCap = cap
105        }
106
107        /// Get source vault capability (for scheduled handler)
108        access(all) fun getSourceVaultCapability(): Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>? {
109            return self.sourceVaultCap
110        }
111
112        /// Get target vault capability (for scheduled handler)
113        access(all) fun getTargetVaultCapability(): Capability<&{FungibleToken.Receiver}>? {
114            return self.targetVaultCap
115        }
116
117        /// Set the fee vault capability
118        ///
119        /// This should be called once during setup to give the controller
120        /// permission to withdraw FLOW from the user's vault to pay scheduler fees.
121        ///
122        /// @param cap: Capability with withdraw auth to FLOW vault
123        access(all) fun setFeeVaultCapability(
124            cap: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>
125        ) {
126            pre {
127                cap.check(): "Invalid fee vault capability"
128            }
129            self.feeVaultCap = cap
130        }
131
132        /// Get fee vault capability (for scheduled handler)
133        ///
134        /// Returns capability to withdraw FLOW for scheduler execution fees.
135        access(all) fun getFeeVaultCapability(): Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>? {
136            return self.feeVaultCap
137        }
138
139        /// Set the COA capability
140        ///
141        /// This should be called during setup to give the controller
142        /// permission to use the user's COA for EVM operations.
143        ///
144        /// @param cap: Capability with owner auth to COA
145        access(all) fun setCOACapability(
146            cap: Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>
147        ) {
148            pre {
149                cap.check(): "Invalid COA capability"
150            }
151            self.coaCap = cap
152        }
153
154        /// Get COA capability (for scheduled handler)
155        ///
156        /// Returns capability to use COA for EVM swap execution.
157        access(all) fun getCOACapability(): Capability<auth(EVM.Owner) &EVM.CadenceOwnedAccount>? {
158            return self.coaCap
159        }
160
161        /// Add a new plan to this controller
162        ///
163        /// @param plan: The DCA plan resource to add
164        access(all) fun addPlan(plan: @DCAPlanV3.Plan) {
165            let planId = plan.id
166
167            // Ensure no duplicate plan IDs
168            assert(!self.plans.containsKey(planId), message: "Plan already exists")
169
170            self.plans[planId] <-! plan
171
172            // Emit event (note: owner address must be obtained from context)
173            emit PlanAddedToController(owner: self.owner!.address, planId: planId)
174        }
175
176        /// Remove and return a plan from this controller
177        ///
178        /// This allows users to cancel plans or transfer them.
179        ///
180        /// @param id: Plan ID to remove
181        /// @return The removed plan resource
182        access(all) fun removePlan(id: UInt64): @DCAPlanV3.Plan {
183            pre {
184                self.plans.containsKey(id): "Plan does not exist"
185            }
186
187            let plan <- self.plans.remove(key: id)!
188            emit PlanRemovedFromController(owner: self.owner!.address, planId: id)
189            return <- plan
190        }
191
192        /// Borrow a reference to a plan (mutable)
193        ///
194        /// Used by scheduled handlers to update plan state during execution.
195        /// Requires Owner entitlement for privileged access.
196        ///
197        /// @param id: Plan ID
198        /// @return Mutable reference to the plan
199        access(Owner) fun borrowPlan(id: UInt64): &DCAPlanV3.Plan? {
200            return &self.plans[id]
201        }
202
203        // ========================================
204        // Public Interface Implementation
205        // ========================================
206
207        /// Get all plan IDs in this controller
208        access(all) view fun getPlanIds(): [UInt64] {
209            return self.plans.keys
210        }
211
212        /// Get a read-only reference to a specific plan
213        access(all) view fun getPlan(id: UInt64): &DCAPlanV3.Plan? {
214            return &self.plans[id]
215        }
216
217        /// Get details of all plans
218        access(all) fun getAllPlans(): [DCAPlanV3.PlanDetails] {
219            let details: [DCAPlanV3.PlanDetails] = []
220            for id in self.plans.keys {
221                if let plan = &self.plans[id] as &DCAPlanV3.Plan? {
222                    details.append(plan.getDetails())
223                }
224            }
225            return details
226        }
227
228        /// Get details of only active plans
229        access(all) fun getActivePlans(): [DCAPlanV3.PlanDetails] {
230            let details: [DCAPlanV3.PlanDetails] = []
231            for id in self.plans.keys {
232                if let plan = &self.plans[id] as &DCAPlanV3.Plan? {
233                    if plan.status == DCAPlanV3.PlanStatus.Active {
234                        details.append(plan.getDetails())
235                    }
236                }
237            }
238            return details
239        }
240
241        /// Check if controller has required capabilities configured
242        access(all) view fun isFullyConfigured(): Bool {
243            if let sourceCap = self.sourceVaultCap {
244                if let targetCap = self.targetVaultCap {
245                    if let feeCap = self.feeVaultCap {
246                        if let coaCap = self.coaCap {
247                            return sourceCap.check() && targetCap.check() && feeCap.check() && coaCap.check()
248                        }
249                    }
250                }
251            }
252            return false
253        }
254    }
255
256    /// Create a new DCA controller
257    ///
258    /// Users call this once to set up their DCA management resource.
259    access(all) fun createController(): @Controller {
260        return <- create Controller()
261    }
262
263    init() {
264        // V3 uses new storage paths to avoid conflicts with V2
265        self.ControllerStoragePath = /storage/DCAControllerV3
266        self.ControllerPublicPath = /public/DCAControllerV3
267    }
268}
269