Smart Contract
DCAControllerV2
A.ca7ee55e4fc3251a.DCAControllerV2
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