Smart Contract

DCAControllerV2

A.ca7ee55e4fc3251a.DCAControllerV2

Valid From

135,596,759

Deployed

5d ago
Feb 22, 2026, 02:51:37 AM UTC

Dependents

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