Smart Contract

ActionRouter

A.79f5b5b0f95a160b.ActionRouter

Valid From

125,675,391

Deployed

1w ago
Feb 19, 2026, 08:25:06 PM UTC

Dependents

4 imports
1import FlowToken from 0x1654653399040a61
2import stFlowToken from 0xd6f80565193ad727
3import LiquidStaking from 0xd6f80565193ad727
4import FungibleToken from 0xf233dcee88fe0abe
5
6access(all) contract ActionRouter {
7    
8    access(all) let AdminStoragePath: StoragePath
9    access(all) let FlowVaultStoragePath: StoragePath
10    access(all) let StFlowVaultStoragePath: StoragePath
11    
12    access(self) var authorizedEVMCallers: {Address: Bool}
13    access(self) var isActive: Bool
14    access(self) var minStakeAmount: UFix64
15    access(self) var maxStakeAmount: UFix64
16    access(self) var maxOperationsPerBlock: UInt64
17    access(self) var currentBlockOperations: UInt64
18    access(self) var lastBlockHeight: UInt64
19    
20    access(all) var totalStakeOperations: UInt64
21    access(all) var totalUnstakeOperations: UInt64
22    access(all) var totalFlowStaked: UFix64
23    access(all) var totalStFlowMinted: UFix64
24    
25    access(all) event StakeExecuted(amount: UFix64, recipient: Address, stFlowReceived: UFix64, exchangeRate: UFix64, requestId: String)
26    access(all) event UnstakeExecuted(amount: UFix64, recipient: Address, flowReceived: UFix64, requestId: String)
27    access(all) event EVMCallerAuthorized(caller: Address)
28    access(all) event EVMCallerRevoked(caller: Address)
29    access(all) event RouterConfigUpdated(minStake: UFix64, maxStake: UFix64)
30    
31    // Struct definitions moved inside the contract
32    access(all) struct StakeResult {
33        access(all) let flowAmount: UFix64
34        access(all) let stFlowReceived: UFix64
35        access(all) let exchangeRate: UFix64
36        access(all) let requestId: String
37        access(all) let success: Bool
38        
39        init(flowAmount: UFix64, stFlowReceived: UFix64, exchangeRate: UFix64, requestId: String, success: Bool) {
40            self.flowAmount = flowAmount
41            self.stFlowReceived = stFlowReceived
42            self.exchangeRate = exchangeRate
43            self.requestId = requestId
44            self.success = success
45        }
46    }
47
48    access(all) struct UnstakeResult {
49        access(all) let stFlowAmount: UFix64
50        access(all) let flowReceived: UFix64
51        access(all) let requestId: String
52        access(all) let success: Bool
53        
54        init(stFlowAmount: UFix64, flowReceived: UFix64, requestId: String, success: Bool) {
55            self.stFlowAmount = stFlowAmount
56            self.flowReceived = flowReceived
57            self.requestId = requestId
58            self.success = success
59        }
60    }
61
62    access(all) struct RouterStats {
63        access(all) let totalStakeOps: UInt64
64        access(all) let totalUnstakeOps: UInt64
65        access(all) let totalFlowStaked: UFix64
66        access(all) let totalStFlowMinted: UFix64
67        access(all) let currentFlowBalance: UFix64
68        access(all) let currentStFlowBalance: UFix64
69        access(all) let isActive: Bool
70        
71        init(
72            totalStakeOps: UInt64,
73            totalUnstakeOps: UInt64,
74            totalFlowStaked: UFix64,
75            totalStFlowMinted: UFix64,
76            currentFlowBalance: UFix64,
77            currentStFlowBalance: UFix64,
78            isActive: Bool
79        ) {
80            self.totalStakeOps = totalStakeOps
81            self.totalUnstakeOps = totalUnstakeOps
82            self.totalFlowStaked = totalFlowStaked
83            self.totalStFlowMinted = totalStFlowMinted
84            self.currentFlowBalance = currentFlowBalance
85            self.currentStFlowBalance = currentStFlowBalance
86            self.isActive = isActive
87        }
88    }
89    
90    init() {
91        self.AdminStoragePath = /storage/ActionRouterAdmin
92        self.FlowVaultStoragePath = /storage/ActionRouterFlowVault
93        self.StFlowVaultStoragePath = /storage/ActionRouterStFlowVault
94        
95        self.authorizedEVMCallers = {}
96        self.isActive = true
97        self.minStakeAmount = 1.0
98        self.maxStakeAmount = 10000.0
99        self.maxOperationsPerBlock = 10
100        self.currentBlockOperations = 0
101        self.lastBlockHeight = getCurrentBlock().height
102        
103        self.totalStakeOperations = 0
104        self.totalUnstakeOperations = 0
105        self.totalFlowStaked = 0.0
106        self.totalStFlowMinted = 0.0
107        
108        let admin <- create Admin()
109        self.account.storage.save(<-admin, to: self.AdminStoragePath)
110        
111        let flowVault <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>())
112        self.account.storage.save(<-flowVault, to: self.FlowVaultStoragePath)
113        
114        // Only create the capability if it doesn't already exist
115        if self.account.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver).check() == false {
116            let flowReceiverCap = self.account.capabilities.storage.issue<&{FungibleToken.Receiver}>(self.FlowVaultStoragePath)
117            self.account.capabilities.publish(flowReceiverCap, at: /public/flowTokenReceiver)
118        }
119    }
120    
121    access(all) resource Admin {
122        
123        access(all) fun authorizeEVMCaller(caller: Address) {
124            ActionRouter.authorizedEVMCallers[caller] = true
125            emit EVMCallerAuthorized(caller: caller)
126        }
127        
128        access(all) fun revokeEVMCaller(caller: Address) {
129            ActionRouter.authorizedEVMCallers.remove(key: caller)
130            emit EVMCallerRevoked(caller: caller)
131        }
132        
133        access(all) fun setActive(active: Bool) {
134            ActionRouter.isActive = active
135        }
136        
137        access(all) fun updateLimits(minStake: UFix64, maxStake: UFix64, maxOpsPerBlock: UInt64) {
138            pre {
139                minStake > 0.0: "Minimum stake must be positive"
140                maxStake >= minStake: "Maximum stake must be >= minimum"
141                maxOpsPerBlock > 0: "Max operations per block must be positive"
142            }
143            ActionRouter.minStakeAmount = minStake
144            ActionRouter.maxStakeAmount = maxStake
145            ActionRouter.maxOperationsPerBlock = maxOpsPerBlock
146            emit RouterConfigUpdated(minStake: minStake, maxStake: maxStake)
147        }
148        
149        access(all) fun emergencyWithdraw(amount: UFix64, recipient: Address) {
150            pre {
151                amount > 0.0: "Amount must be positive"
152            }
153            
154            // Fixed: Request authorized reference with Withdraw entitlement
155            let flowVaultRef = ActionRouter.account.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: ActionRouter.FlowVaultStoragePath)
156                ?? panic("Could not borrow FlowToken vault")
157            
158            let withdrawnFlow <- flowVaultRef.withdraw(amount: amount)
159            
160            let recipientAccount = getAccount(recipient)
161            // Fixed: Use FungibleToken.Receiver instead of FlowToken.Receiver
162            let recipientFlowVault = recipientAccount.capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver).borrow()
163                ?? panic("Could not borrow recipient FlowToken receiver")
164            
165            recipientFlowVault.deposit(from: <-withdrawnFlow)
166        }
167    }
168    
169    access(self) fun checkRateLimit() {
170        let currentBlock = getCurrentBlock().height
171        
172        if currentBlock > self.lastBlockHeight {
173            self.currentBlockOperations = 0
174            self.lastBlockHeight = currentBlock
175        }
176        
177        assert(
178            self.currentBlockOperations < self.maxOperationsPerBlock,
179            message: "Rate limit exceeded for this block"
180        )
181        
182        self.currentBlockOperations = self.currentBlockOperations + 1
183    }
184    
185    access(all) fun stakeFlow(amount: UFix64, recipient: Address, requestId: String): StakeResult {
186        pre {
187            self.isActive: "Router is not active"
188            self.authorizedEVMCallers[recipient] == true: "Caller not authorized"
189            amount >= self.minStakeAmount: "Amount below minimum"
190            amount <= self.maxStakeAmount: "Amount above maximum"
191        }
192        
193        self.checkRateLimit()
194        
195        // Fixed: Request authorized reference with Withdraw entitlement
196        let flowVaultRef = self.account.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: self.FlowVaultStoragePath)
197            ?? panic("Could not borrow FlowToken vault")
198        
199        assert(flowVaultRef.balance >= amount, message: "Insufficient FLOW balance in router")
200        
201        let flowToStake <- flowVaultRef.withdraw(amount: amount) as! @FlowToken.Vault
202        // Note: You'll need to check the actual LiquidStaking contract for the correct function signature
203        let exchangeRateBefore = 1.0 // Placeholder - replace with actual call when LiquidStaking contract is available
204        let stFlowVault <- LiquidStaking.stake(flowVault: <-flowToStake)
205        let stFlowAmount = stFlowVault.balance
206        let exchangeRateAfter = 1.0 // Placeholder - replace with actual call when LiquidStaking contract is available
207        
208        let existingStFlowVaultRef = self.account.storage.borrow<&stFlowToken.Vault>(from: self.StFlowVaultStoragePath)
209        if existingStFlowVaultRef != nil {
210            existingStFlowVaultRef!.deposit(from: <-stFlowVault)
211        } else {
212            self.account.storage.save(<-stFlowVault, to: self.StFlowVaultStoragePath)
213        }
214        
215        self.totalStakeOperations = self.totalStakeOperations + 1
216        self.totalFlowStaked = self.totalFlowStaked + amount
217        self.totalStFlowMinted = self.totalStFlowMinted + stFlowAmount
218        
219        emit StakeExecuted(
220            amount: amount,
221            recipient: recipient,
222            stFlowReceived: stFlowAmount,
223            exchangeRate: exchangeRateAfter,
224            requestId: requestId
225        )
226        
227        return StakeResult(
228            flowAmount: amount,
229            stFlowReceived: stFlowAmount,
230            exchangeRate: exchangeRateAfter,
231            requestId: requestId,
232            success: true
233        )
234    }
235    
236    access(all) fun unstakeFlow(stFlowAmount: UFix64, recipient: Address, requestId: String): UnstakeResult {
237        pre {
238            self.isActive: "Router is not active"
239            self.authorizedEVMCallers[recipient] == true: "Caller not authorized"
240            stFlowAmount > 0.0: "Amount must be positive"
241        }
242        
243        self.checkRateLimit()
244        
245        // Fixed: Request authorized reference with Withdraw entitlement
246        let stFlowVaultRef = self.account.storage.borrow<auth(FungibleToken.Withdraw) &stFlowToken.Vault>(from: self.StFlowVaultStoragePath)
247            ?? panic("Could not borrow stFlowToken vault")
248        
249        assert(stFlowVaultRef.balance >= stFlowAmount, message: "Insufficient stFLOW balance")
250        
251        let stFlowToUnstake <- stFlowVaultRef.withdraw(amount: stFlowAmount) as! @stFlowToken.Vault
252        // Note: You'll need to check what LiquidStaking.unstake actually returns
253        let withdrawVoucher <- LiquidStaking.unstake(stFlowVault: <-stFlowToUnstake)
254        
255        // This depends on what LiquidStaking.unstake returns - you may need to redeem the voucher
256        // For now, assuming it returns a flow vault directly
257        let flowAmount = 0.0 // You'll need to get this from the voucher or returned vault
258        
259        // Handle the withdraw voucher appropriately - this is a placeholder
260        destroy withdrawVoucher
261        
262        self.totalUnstakeOperations = self.totalUnstakeOperations + 1
263        
264        emit UnstakeExecuted(
265            amount: stFlowAmount,
266            recipient: recipient,
267            flowReceived: flowAmount,
268            requestId: requestId
269        )
270        
271        return UnstakeResult(
272            stFlowAmount: stFlowAmount,
273            flowReceived: flowAmount,
274            requestId: requestId,
275            success: true
276        )
277    }
278    
279    access(all) view fun getExchangeRate(): UFix64 {
280        // Note: You'll need to check the actual LiquidStaking contract for the correct function
281        return 1.0 // Placeholder - replace with actual call when LiquidStaking contract is available
282    }
283    
284    access(all) view fun getStFlowBalance(): UFix64 {
285        let stFlowVaultRef = self.account.storage.borrow<&stFlowToken.Vault>(from: self.StFlowVaultStoragePath)
286        if stFlowVaultRef != nil {
287            return stFlowVaultRef!.balance
288        }
289        return 0.0
290    }
291    
292    access(all) view fun getFlowBalance(): UFix64 {
293        let flowVaultRef = self.account.storage.borrow<&FlowToken.Vault>(from: self.FlowVaultStoragePath)
294        if flowVaultRef != nil {
295            return flowVaultRef!.balance
296        }
297        return 0.0
298    }
299    
300    // Fixed: Made this function non-view to avoid "impure operation" error
301    access(all) fun getStats(): RouterStats {
302        return RouterStats(
303            totalStakeOps: self.totalStakeOperations,
304            totalUnstakeOps: self.totalUnstakeOperations,
305            totalFlowStaked: self.totalFlowStaked,
306            totalStFlowMinted: self.totalStFlowMinted,
307            currentFlowBalance: self.getFlowBalance(),
308            currentStFlowBalance: self.getStFlowBalance(),
309            isActive: self.isActive
310        )
311    }
312    
313    access(all) view fun isAuthorized(caller: Address): Bool {
314        return self.authorizedEVMCallers[caller] == true
315    }
316}