Smart Contract

FlowTip

A.6c1b12e35dca8863.FlowTip

Valid From

116,279,800

Deployed

3d ago
Feb 24, 2026, 07:24:42 AM UTC

Dependents

17 imports
1access(all) contract FlowTip {
2    // ✅ ADD THIS: CreatorInfo struct (REQUIRED for scripts to work)
3    access(all) struct CreatorInfo {
4        access(all) let id: UInt64
5        access(all) let address: Address
6        access(all) let name: String
7        access(all) let description: String
8        access(all) let imageURL: String
9        access(all) let tipCount: UInt64
10        access(all) let totalTipped: UFix64
11
12        init(
13            id: UInt64,
14            address: Address,
15            name: String,
16            description: String,
17            imageURL: String,
18            tipCount: UInt64,
19            totalTipped: UFix64
20        ) {
21            self.id = id
22            self.address = address
23            self.name = name
24            self.description = description
25            self.imageURL = imageURL
26            self.tipCount = tipCount
27            self.totalTipped = totalTipped
28        }
29    }
30
31    // Define the Creator resource that creators will own
32    access(all) resource Creator: CreatorPublic {
33        access(all) let id: UInt64
34        access(all) var name: String
35        access(all) var description: String
36        access(all) var imageURL: String
37        access(all) var tipCount: UInt64
38        access(all) var totalTipped: UFix64
39        access(all) var tipHistory: {UInt64: TipRecord}
40        access(all) var nextTipID: UInt64
41
42        init(id: UInt64, name: String, description: String, imageURL: String) {
43            self.id = id
44            self.name = name
45            self.description = description
46            self.imageURL = imageURL
47            self.tipCount = 0
48            self.totalTipped = 0.0
49            self.tipHistory = {}
50            self.nextTipID = 0
51        }
52
53        // Receive a tip and record it
54        access(all) fun receiveTip(amount: UFix64, from: Address, message: String) {
55            let tipRecord = TipRecord(
56                id: self.nextTipID,
57                amount: amount,
58                from: from,
59                message: message,
60                timestamp: getCurrentBlock().timestamp
61            )
62            
63            self.tipHistory[self.nextTipID] = tipRecord
64            self.nextTipID = self.nextTipID + 1
65            self.tipCount = self.tipCount + 1
66            self.totalTipped = self.totalTipped + amount
67        }
68
69         access(all) fun addTip(amount: UFix64, from: Address, message: String) {
70            self.receiveTip(amount: amount, from: from, message: message)
71        }
72
73        // 🆕 NEW: Withdraw tips function
74        access(all) fun withdraw(amount: UFix64) {
75            pre {
76                amount > 0.0: "Withdrawal amount must be greater than 0"
77                amount <= self.totalTipped: "Insufficient balance. Available: ".concat(self.totalTipped.toString()).concat(", Requested: ").concat(amount.toString())
78            }
79            
80            // Reduce the total tipped amount
81            self.totalTipped = self.totalTipped - amount
82            
83            // Emit withdrawal event
84            emit CreatorWithdrawal(
85                creatorID: self.id, 
86                creatorAddress: self.owner?.address, 
87                amount: amount,
88                remainingBalance: self.totalTipped
89            )
90        }
91
92        // 🆕 NEW: Alternative withdrawal function name (in case the modal tries this)
93        access(all) fun withdrawTips(amount: UFix64) {
94            self.withdraw(amount: amount)
95        }
96
97        // 🆕 NEW: Get withdrawable balance (helper function)
98        access(all) fun getWithdrawableBalance(): UFix64 {
99            return self.totalTipped
100        }
101
102        // Update profile information
103        access(all) fun updateProfile(name: String, description: String, imageURL: String) {
104            self.name = name
105            self.description = description
106            self.imageURL = imageURL
107        }
108
109        // Get all tip records
110        access(all) fun getTipHistory(): [TipRecord] {
111            return self.tipHistory.values
112        }
113    }
114
115    // Structure to store information about each tip
116    access(all) struct TipRecord {
117        access(all) let id: UInt64
118        access(all) let amount: UFix64
119        access(all) let from: Address
120        access(all) let message: String
121        access(all) let timestamp: UFix64
122
123        init(id: UInt64, amount: UFix64, from: Address, message: String, timestamp: UFix64) {
124            self.id = id
125            self.amount = amount
126            self.from = from
127            self.message = message
128            self.timestamp = timestamp
129        }
130    }
131
132    // Resource interface to expose read-only functions
133    access(all) resource interface CreatorPublic {
134        access(all) let id: UInt64
135        access(all) var name: String
136        access(all) var description: String
137        access(all) var imageURL: String
138        access(all) var tipCount: UInt64
139        access(all) var totalTipped: UFix64
140        access(all) fun receiveTip(amount: UFix64, from: Address, message: String)
141        access(all) fun getTipHistory(): [TipRecord]
142        // 🆕 NEW: Add withdrawal balance function to public interface
143        access(all) fun getWithdrawableBalance(): UFix64
144    }
145
146    // Public events
147    access(all) event CreatorRegistered(id: UInt64, address: Address)
148    access(all) event TipSent(creatorID: UInt64, amount: UFix64, from: Address)
149    // 🆕 NEW: Withdrawal event
150    access(all) event CreatorWithdrawal(creatorID: UInt64, creatorAddress: Address?, amount: UFix64, remainingBalance: UFix64)
151
152    // Storage paths
153    access(all) let CreatorStoragePath: StoragePath
154    access(all) let CreatorPublicPath: PublicPath
155
156    // Contract state
157    access(all) var nextCreatorID: UInt64
158    access(all) var registeredCreators: {Address: UInt64}
159
160    init() {
161        self.CreatorStoragePath = /storage/FlowTipCreator
162        self.CreatorPublicPath = /public/FlowTipCreator
163        self.nextCreatorID = 1
164        self.registeredCreators = {}
165        
166        emit CreatorRegistered(id: 0, address: self.account.address)
167    }
168
169    // ✅ CHANGE THIS: Fix registerCreator to take address parameter (YOUR SCRIPTS EXPECT THIS)
170    access(all) fun registerCreator(address: Address): UInt64 {
171        let creatorID = self.nextCreatorID
172        self.registeredCreators[address] = creatorID
173        self.nextCreatorID = self.nextCreatorID + 1
174        
175        emit CreatorRegistered(id: creatorID, address: address)
176        return creatorID
177    }
178
179    // ✅ ADD THIS: Required function that scripts call
180    access(all) view fun isCreatorRegistered(address: Address): Bool {
181        return self.registeredCreators[address] != nil
182    }
183
184    // ✅ FIXED: Remove 'view' keyword to allow impure operations
185    access(all) fun getCreatorInfo(address: Address): CreatorInfo? {
186        if !self.isCreatorRegistered(address: address) {
187            return nil
188        }
189        
190        let account = getAccount(address)
191        let creatorCap = account.capabilities.get<&Creator>(self.CreatorPublicPath)
192        
193        if !creatorCap.check() {
194            return nil
195        }
196        
197        if let creatorRef = creatorCap.borrow() {
198            return CreatorInfo(
199                id: creatorRef.id,
200                address: address,
201                name: creatorRef.name,
202                description: creatorRef.description,
203                imageURL: creatorRef.imageURL,
204                tipCount: creatorRef.tipCount,
205                totalTipped: creatorRef.totalTipped
206            )
207        }
208        
209        return nil
210    }
211
212    // ✅ FIXED: Remove 'view' keyword to allow impure operations
213    access(all) fun getAllCreators(): [CreatorInfo] {
214        let creators: [CreatorInfo] = []
215        
216        for address in self.registeredCreators.keys {
217            if let creatorInfo = self.getCreatorInfo(address: address) {
218                creators.append(creatorInfo)
219            }
220        }
221        
222        return creators
223    }
224
225    // ✅ FIXED: Remove 'view' keyword from functions that call getAllCreators()
226    access(all) fun getCreatorsPaginated(offset: UInt64, limit: UInt64): [CreatorInfo] {
227        return self.getAllCreators() // Simple implementation for now
228    }
229
230    access(all) fun getTopCreators(limit: UInt64): [CreatorInfo] {
231        return self.getAllCreators() // Simple implementation for now
232    }
233
234    access(all) fun searchCreators(query: String): [CreatorInfo] {
235        return self.getAllCreators() // Simple implementation for now
236    }
237
238    access(all) view fun getCreatorCount(): Int {
239        return self.registeredCreators.keys.length
240    }
241
242    // Get a list of all registered creators
243    access(all) view fun getRegisteredCreators(): {Address: UInt64} {
244        return self.registeredCreators
245    }
246
247    // Create a new Creator resource
248    access(all) fun createCreator(id: UInt64, name: String, description: String, imageURL: String): @Creator {
249        return <- create Creator(id: id, name: name, description: description, imageURL: imageURL)
250    }
251}