Smart Contract
DCAController
A.ca7ee55e4fc3251a.DCAController
1import DCAPlan 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 DCAController {
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): &DCAPlan.Plan?
41 access(all) fun getAllPlans(): [DCAPlan.PlanDetails]
42 access(all) fun getActivePlans(): [DCAPlan.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: DCAPlan.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 init() {
61 self.plans <- {}
62 self.sourceVaultCap = nil
63 self.targetVaultCap = nil
64 }
65
66 /// Set the source vault capability
67 ///
68 /// This should be called once during setup to give the controller
69 /// permission to withdraw from the user's source token vault.
70 ///
71 /// @param cap: Capability with withdraw auth to source vault
72 access(all) fun setSourceVaultCapability(
73 cap: Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>
74 ) {
75 pre {
76 cap.check(): "Invalid source vault capability"
77 }
78 self.sourceVaultCap = cap
79 }
80
81 /// Set the target vault capability
82 ///
83 /// This should be called once during setup to give the controller
84 /// permission to deposit to the user's target token vault.
85 ///
86 /// @param cap: Capability to deposit to target vault
87 access(all) fun setTargetVaultCapability(
88 cap: Capability<&{FungibleToken.Receiver}>
89 ) {
90 pre {
91 cap.check(): "Invalid target vault capability"
92 }
93 self.targetVaultCap = cap
94 }
95
96 /// Get source vault capability (for scheduled handler)
97 access(all) fun getSourceVaultCapability(): Capability<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>? {
98 return self.sourceVaultCap
99 }
100
101 /// Get target vault capability (for scheduled handler)
102 access(all) fun getTargetVaultCapability(): Capability<&{FungibleToken.Receiver}>? {
103 return self.targetVaultCap
104 }
105
106 /// Add a new plan to this controller
107 ///
108 /// @param plan: The DCA plan resource to add
109 access(all) fun addPlan(plan: @DCAPlan.Plan) {
110 let planId = plan.id
111
112 // Ensure no duplicate plan IDs
113 assert(!self.plans.containsKey(planId), message: "Plan already exists")
114
115 self.plans[planId] <-! plan
116
117 // Emit event (note: owner address must be obtained from context)
118 emit PlanAddedToController(owner: self.owner!.address, planId: planId)
119 }
120
121 /// Remove and return a plan from this controller
122 ///
123 /// This allows users to cancel plans or transfer them.
124 ///
125 /// @param id: Plan ID to remove
126 /// @return The removed plan resource
127 access(all) fun removePlan(id: UInt64): @DCAPlan.Plan {
128 pre {
129 self.plans.containsKey(id): "Plan does not exist"
130 }
131
132 let plan <- self.plans.remove(key: id)!
133 emit PlanRemovedFromController(owner: self.owner!.address, planId: id)
134 return <- plan
135 }
136
137 /// Borrow a reference to a plan (mutable)
138 ///
139 /// Used by scheduled handlers to update plan state during execution.
140 /// Requires Owner entitlement for privileged access.
141 ///
142 /// @param id: Plan ID
143 /// @return Mutable reference to the plan
144 access(Owner) fun borrowPlan(id: UInt64): &DCAPlan.Plan? {
145 return &self.plans[id]
146 }
147
148 // ========================================
149 // Public Interface Implementation
150 // ========================================
151
152 /// Get all plan IDs in this controller
153 access(all) view fun getPlanIds(): [UInt64] {
154 return self.plans.keys
155 }
156
157 /// Get a read-only reference to a specific plan
158 access(all) view fun getPlan(id: UInt64): &DCAPlan.Plan? {
159 return &self.plans[id]
160 }
161
162 /// Get details of all plans
163 access(all) fun getAllPlans(): [DCAPlan.PlanDetails] {
164 let details: [DCAPlan.PlanDetails] = []
165 for id in self.plans.keys {
166 if let plan = &self.plans[id] as &DCAPlan.Plan? {
167 details.append(plan.getDetails())
168 }
169 }
170 return details
171 }
172
173 /// Get details of only active plans
174 access(all) fun getActivePlans(): [DCAPlan.PlanDetails] {
175 let details: [DCAPlan.PlanDetails] = []
176 for id in self.plans.keys {
177 if let plan = &self.plans[id] as &DCAPlan.Plan? {
178 if plan.status == DCAPlan.PlanStatus.Active {
179 details.append(plan.getDetails())
180 }
181 }
182 }
183 return details
184 }
185
186 /// Check if controller has required capabilities configured
187 access(all) view fun isFullyConfigured(): Bool {
188 if let sourceCap = self.sourceVaultCap {
189 if let targetCap = self.targetVaultCap {
190 return sourceCap.check() && targetCap.check()
191 }
192 }
193 return false
194 }
195 }
196
197 /// Create a new DCA controller
198 ///
199 /// Users call this once to set up their DCA management resource.
200 access(all) fun createController(): @Controller {
201 return <- create Controller()
202 }
203
204 init() {
205 self.ControllerStoragePath = /storage/DCAController
206 self.ControllerPublicPath = /public/DCAController
207 }
208}
209