Smart Contract

DCAControllerUnified

A.ca7ee55e4fc3251a.DCAControllerUnified

Valid From

135,618,053

Deployed

5d ago
Feb 23, 2026, 12:44:11 AM UTC

Dependents

30 imports
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