Smart Contract

FixesHeartbeat

A.d2abb5dbf5e08666.FixesHeartbeat

Valid From

86,128,569

Deployed

2d ago
Feb 24, 2026, 11:54:28 PM UTC

Dependents

19 imports
1/**
2> Author: Fixes Lab <https://github.com/fixes-world/>
3
4# FixesHeartbeat
5
6The `FixesHeartbeat` contract is a contract that provides a heartbeat mechanism for the Flow blockchain.
7It allows developers to add hooks to the contract and execute them periodically.
8The contract is designed to be simple and easy to use.
9It is suitable for various use cases, such as updating the contract state, sending notifications, and more.
10
11*/
12
13/// The `FixesHeartbeat` contract
14///
15access(all) contract FixesHeartbeat {
16    /* --- Events --- */
17    /// Event emitted when the contract is initialized
18    access(all) event ContractInitialized()
19    /// Event emitted when a hook is added
20    access(all) event HookAdded(scope: String, hookAddr: Address, hookType: Type)
21    /// Event emitted when a hook is removed
22    access(all) event HookRemoved(scope: String, hookAddr: Address)
23    /// Event emitted when the heartbeat time is updated
24    access(all) event HeartbeatExecuted(scope: String, lastHeartbeatTime: UFix64, deltaTime: UFix64)
25
26    /* --- Variable, Enums and Structs --- */
27
28    access(all)
29    let storagePath: StoragePath
30
31    /// Record the last heartbeat time of each scope
32    /// Scope => Last Heartbeat Time
33    access(all)
34    let lastHeartbeatTime: {String: UFix64}
35
36    /// Record the hooks of each scope
37    /// Scope => Hooks' Addresses
38    access(all)
39    let heartbeatScopes: {String: {Address: PublicPath}}
40
41    /// The minimum interval between two heartbeats
42    access(all)
43    var heartbeatMinInterval: UFix64
44
45    /* --- Interfaces & Resources --- */
46
47    /// The interface that all the hooks must implement
48    ///
49    access(all) resource interface IHeartbeatHook {
50        /// The methods that is invoked when the heartbeat is executed
51        /// Before try-catch is deployed, please ensure that there will be no panic inside the method.
52        ///
53        access(account)
54        fun onHeartbeat(_ deltaTime: UFix64)
55    }
56
57    /// Heartbeat resource, provides the heartbeat function
58    /// The heartbeat function will invoke all the hooks that are bound to the specified scope
59    /// The heartbeatTime is the time when the heartbeat function is invoked, it is stored in the contract storage
60    /// The deltaTime is the time interval between the last heartbeat and the current heartbeat
61    ///
62    access(all) resource Heartbeat {
63        /// The heartbeat function
64        ///
65        /// - Parameter scope: The scope of the heartbeat
66        ///
67        access(all)
68        fun tick(scope: String) {
69            // Check if the scope exists
70            if FixesHeartbeat.heartbeatScopes[scope] == nil {
71                log("The scope does not exist: ".concat(scope))
72                return
73            } else {
74                log("Ticking Heartbeart for scope: ".concat(scope))
75            }
76
77            if let hooks = FixesHeartbeat.borrowHooksDictRef(scope: scope) {
78                let now = getCurrentBlock().timestamp
79                let lastHeartbeatTime = FixesHeartbeat.lastHeartbeatTime[scope] ?? (now - FixesHeartbeat.heartbeatMinInterval)
80                let deltaTime = now - lastHeartbeatTime
81                // Check if the interval is too short
82                if deltaTime < FixesHeartbeat.heartbeatMinInterval {
83                    return
84                }
85
86                // iterate all the hooks
87                for hookAddr in hooks.keys {
88                    if let hookRef = getAccount(hookAddr)
89                        .capabilities.get<&{IHeartbeatHook}>(hooks[hookAddr]!)
90                        .borrow()
91                    {
92                        hookRef.onHeartbeat(deltaTime)
93                    }
94                }
95
96                // Update the last heartbeat time
97                FixesHeartbeat.lastHeartbeatTime[scope] = now
98
99                // Emit the event
100                emit HeartbeatExecuted(scope: scope, lastHeartbeatTime: now, deltaTime: deltaTime)
101            }
102        }
103    }
104
105    /// Borrow the hook dictionary reference
106    ///
107    access(contract)
108    view fun borrowHooksDictRef(scope: String): auth(Mutate) &{Address: PublicPath}? {
109        return &self.heartbeatScopes[scope]
110    }
111
112    /** --- Account Level Functions --- */
113
114    /// Add a hook to the specified scope
115    ///
116    /// - Parameter scope: The scope of the hook
117    /// - Parameter hook: The hook to be added
118    ///
119    access(account)
120    fun addHook(scope: String, hookAddr: Address, hookPath: PublicPath) {
121        // Check if the scope exists
122        if self.heartbeatScopes[scope] == nil {
123            self.heartbeatScopes[scope] = {}
124        }
125        let hookCap = getAccount(hookAddr)
126            .capabilities.get<&{IHeartbeatHook}>(hookPath)
127        if hookCap.check() {
128            if let hookRef = hookCap.borrow() {
129                let scopesRef = (self.borrowHooksDictRef(scope: scope))!
130                // check if the hook is already added
131                if scopesRef[hookAddr] != nil {
132                    return
133                }
134                // Add the hook
135                scopesRef[hookAddr] = hookPath
136                // Emit the event
137                emit HookAdded(scope: scope, hookAddr: hookAddr, hookType: hookRef.getType())
138            }
139        }
140    }
141
142    /// Remove a hook from the specified scope
143    ///
144    access(account)
145    fun removeHook(scope: String, hookAddr: Address) {
146        if let hooks = FixesHeartbeat.borrowHooksDictRef(scope: scope) {
147            hooks.remove(key: hookAddr)
148
149            // Emit the event
150            emit HookRemoved(scope: scope, hookAddr: hookAddr)
151        }
152    }
153
154    /* --- Public Functions --- */
155
156    /// Create a new Heartbeat resource
157    ///
158    access(all)
159    fun createHeartbeat(): @Heartbeat {
160        return <-create Heartbeat()
161    }
162
163    /// Check if the hook is added to the specified scope
164    ///
165    access(all)
166    view fun hasHook(scope: String, hookAddr: Address): Bool {
167        if let hooks = FixesHeartbeat.borrowHooksDictRef(scope: scope) {
168            return hooks[hookAddr] != nil
169        }
170        return false
171    }
172
173    init() {
174        let identifier = "FixesHeartbeat_".concat(self.account.address.toString())
175        self.storagePath  = StoragePath(identifier: identifier)!
176
177        self.lastHeartbeatTime = {}
178        self.heartbeatScopes = {}
179
180        // Set the default minimum interval between two heartbeats
181        // 60 seconds
182        self.heartbeatMinInterval = 60.0
183
184        // Register the hooks
185
186        emit ContractInitialized()
187    }
188}
189