Smart Contract
FlowTip
A.6c1b12e35dca8863.FlowTip
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}