Smart Contract

FindThoughts

A.097bafa4e0b48eef.FindThoughts

Deployed

1d ago
Feb 26, 2026, 03:12:51 AM UTC

Dependents

12 imports
1import FindViews from 0x097bafa4e0b48eef
2import FindMarket from 0x097bafa4e0b48eef
3import MetadataViews from 0x1d7e57aa55817448
4import ViewResolver from 0x1d7e57aa55817448
5import FINDNFTCatalog from 0x097bafa4e0b48eef
6import FIND from 0x097bafa4e0b48eef
7import Clock from 0x097bafa4e0b48eef
8
9access(all) contract FindThoughts {
10    // Entitlements
11    access(all) entitlement Owner
12
13    // Events
14    access(all) event Published(id: UInt64, creator: Address, creatorName: String?, header: String, message: String, medias: [String], nfts:[FindMarket.NFTInfo], tags: [String], quoteOwner: Address?, quoteId: UInt64?)
15    access(all) event Edited(id: UInt64, creator: Address, creatorName: String?, header: String, message: String, medias: [String], hide: Bool, tags: [String])
16    access(all) event Deleted(id: UInt64, creator: Address, creatorName: String?, header: String, message: String, medias: [String], tags: [String])
17    access(all) event Reacted(id: UInt64, by: Address, byName: String?, creator: Address, creatorName: String?, header: String, reaction: String?, totalCount: {String : Int})
18
19    // Paths
20    access(all) let CollectionStoragePath : StoragePath 
21    access(all) let CollectionPublicPath : PublicPath 
22
23    access(all) struct ThoughtPointer {
24        access(all) let cap: Capability<&FindThoughts.Collection>
25        access(all) let id: UInt64 
26
27        init(creator: Address, id: UInt64) {
28            let cap = getAccount(creator).capabilities.get<&FindThoughts.Collection>(FindThoughts.CollectionPublicPath)
29            if cap !=nil && !cap!.check() {
30                panic("creator's find thought capability is not valid. Creator : ".concat(creator.toString()))
31            }
32            self.cap = cap!
33            self.id = id 
34        }
35
36        access(all) fun borrowThoughtPublic() : &{ThoughtPublic}? {
37            if self.cap.check() {
38                let ref = self.cap.borrow()!
39                if ref.contains(self.id) {
40                    return ref.borrowThoughtPublic(self.id)
41                }
42            }
43            return nil
44        }
45
46        access(all) fun valid() : Bool {
47            if self.borrowThoughtPublic() != nil {
48                return true
49            }
50            return false
51        }
52
53        access(all) fun owner() : Address {
54            return self.cap.address
55        }
56    }
57
58    access(all) resource interface ThoughtPublic {
59        access(all) let id: UInt64 
60        access(all) let creator: Address 
61        access(all) var header: String 
62        access(all) var body: String 
63        access(all) let created: UFix64 
64        access(all) var lastUpdated: UFix64?
65        access(all) let medias: [MetadataViews.Media]
66        access(all) let nft: [FindViews.ViewReadPointer]
67        access(all) var tags: [String]
68        access(all) var reacted: {Address : String}
69        access(all) var reactions: {String : Int}
70
71        access(contract) fun internal_react(user: Address, reaction: String?) 
72        access(all) fun getQuotedThought() : ThoughtPointer? 
73        access(all) fun getHide() : Bool
74        access(all) fun getTags() : [String] {
75            return self.tags
76        }
77        access(all) fun getReactions() : {String:Int}{
78            return self.reactions
79        }
80
81        access(all) fun getNFTS() : [FindViews.ViewReadPointer] {
82            return self.nft
83        }
84    }
85
86    access(all) resource interface ThoughtPrivate {
87        access(Owner) fun hide(_ hide: Bool)
88        access(Owner) fun edit(header: String , body: String, tags: [String])
89    }
90
91    access(all) resource Thought : ThoughtPublic, ThoughtPrivate, ViewResolver.Resolver {
92        access(all) let id: UInt64 
93        access(all) let creator: Address 
94        access(all) var header: String 
95        access(all) var body: String 
96        access(all) let created: UFix64 
97        access(all) var lastUpdated: UFix64?
98        access(all) var tags: [String]
99        // user : Reactions
100        access(all) var reacted: {Address : String}
101        // Reactions : Counts
102        access(all) var reactions: {String : Int}
103
104        // only one image is enabled at the moment
105        access(all) let medias: [MetadataViews.Media]
106
107        // These are here only for future extension
108        access(all) let nft: [FindViews.ViewReadPointer]
109        access(self) let stringTags: {String : String} 
110        access(self) let scalars: {String : UFix64} 
111        access(self) let extras: {String : AnyStruct} 
112
113        init(creator: Address , header: String , body: String , created: UFix64, tags: [String], medias: [MetadataViews.Media], nft: [FindViews.ViewReadPointer], quote: ThoughtPointer?, stringTags: {String : String}, scalars : {String : UFix64}, extras: {String : AnyStruct} ) {
114            self.id = self.uuid 
115            self.creator = creator
116            self.header = header
117            self.body = body
118            self.created = created
119            self.lastUpdated = nil
120            self.tags = tags
121            self.medias = medias
122
123            self.nft = nft
124            self.stringTags = stringTags
125            self.scalars = scalars
126            extras["quote"] = quote
127            extras["hidden"] = false
128            self.extras = extras
129
130            self.reacted = {}
131            self.reactions = {}
132        }
133
134        access(all) fun getQuotedThought() : ThoughtPointer? {
135            if let r = self.extras["quote"] {
136                return r as! ThoughtPointer
137            }
138            return nil
139        }
140
141        access(all) fun getHide() : Bool {
142            if let r = self.extras["hidden"] {
143                return r as! Bool
144            }
145            return false
146        }
147
148        access(Owner) fun hide(_ hide: Bool) {
149            self.extras["hidden"] = hide
150            let medias : [String] = []
151            for m in self.medias {
152                medias.append(m.file.uri())
153            }
154            emit Edited(id: self.id, creator: self.creator, creatorName: FIND.reverseLookup(self.creator), header: self.header, message: self.body, medias: medias, hide: hide, tags: self.tags)
155        }
156
157        access(Owner) fun edit(header: String , body: String, tags: [String]) {
158            self.header = header 
159            self.body = body 
160            self.tags = tags 
161            let address = self.owner!.address
162            let medias : [String] = []
163            for m in self.medias {
164                medias.append(m.file.uri())
165            }
166            self.lastUpdated = Clock.time()
167            emit Edited(id: self.id, creator: address, creatorName: FIND.reverseLookup(address), header: self.header, message: self.body, medias: medias, hide: self.getHide(), tags: self.tags)
168        }
169
170        // To withdraw reaction, pass in nil
171        access(contract) fun internal_react(user: Address, reaction: String?) {
172            let owner = self.owner!.address
173            if let previousReaction = self.reacted[user] {
174                // reaction here cannot be nil, therefore we can ! 
175                self.reactions[previousReaction] = self.reactions[previousReaction]! - 1
176                if self.reactions[previousReaction]! == 0 {
177                    self.reactions.remove(key: previousReaction)
178                }
179            } 
180
181            self.reacted[user] = reaction
182
183            if reaction != nil {
184                var reacted = self.reactions[reaction!] ?? 0
185                reacted = reacted + 1
186                self.reactions[reaction!] = reacted
187            }
188
189            emit Reacted(id: self.id, by: user, byName: FIND.reverseLookup(user), creator: owner, creatorName: FIND.reverseLookup(owner), header: self.header, reaction: reaction, totalCount: self.reactions)
190        }
191
192        access(all) view fun getViews(): [Type] {
193            return [
194            Type<MetadataViews.Display>()
195            ]
196        }
197
198        access(all) view fun resolveView(_ type: Type) : AnyStruct? {
199            switch type {
200
201                case Type<MetadataViews.Display>(): 
202
203                let content = self.body.concat("  -- FIND Thought by ").concat(self.owner!.address.toString())
204
205                return MetadataViews.Display(
206                    name: self.header, 
207                    description: content,
208                    thumbnail: self.medias[0].file
209                )
210
211            }
212            return nil
213        }
214    }
215
216    access(all) resource interface CollectionPublic {
217        access(all) fun contains(_ id: UInt64) : Bool 
218        access(all) view fun borrowThoughtPublic(_ id: UInt64) : &{FindThoughts.ThoughtPublic}
219    }
220
221    access(all) resource Collection : CollectionPublic, ViewResolver.ResolverCollection {
222        access(self) let ownedThoughts : @{UInt64 : FindThoughts.Thought}
223        access(self) let sequence : [UInt64] 
224
225        init() {
226            self.ownedThoughts <- {}
227            self.sequence = []
228        }
229
230        access(all) fun contains(_ id: UInt64) : Bool {
231            return self.ownedThoughts.containsKey(id)
232        }
233
234        access(all) view fun getIDs() : [UInt64] {
235            return self.ownedThoughts.keys
236        }
237
238        access(Owner) fun borrow(_ id: UInt64) : auth(Owner) &{FindThoughts.ThoughtPrivate} {
239            pre{
240                self.ownedThoughts.containsKey(id) : "Cannot borrow Thought with ID : ".concat(id.toString())
241            }
242            return (&self.ownedThoughts[id])!
243        }
244
245        access(all) view fun borrowThoughtPublic(_ id: UInt64) : &{FindThoughts.ThoughtPublic} {
246            pre{
247                self.ownedThoughts.containsKey(id) : "Cannot borrow Thought with ID : ".concat(id.toString())
248            }
249            return (&self.ownedThoughts[id] as &FindThoughts.Thought?)!
250        }
251
252        access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver} {
253            pre{
254                self.ownedThoughts.containsKey(id) : "Cannot borrow Thought with ID : ".concat(id.toString())
255            }
256            return (&self.ownedThoughts[id] as &FindThoughts.Thought?)!
257        }
258
259        access(Owner) fun publish(header: String , body: String , tags: [String], media: MetadataViews.Media?, nftPointer: FindViews.ViewReadPointer?, quote: FindThoughts.ThoughtPointer?) {
260            let medias : [MetadataViews.Media] = []
261            let m : [String] = []
262            if media != nil {
263                medias.append(media!)
264                m.append(media!.file.uri())
265            }
266            let address = self.owner!.address
267
268            let nfts : [FindMarket.NFTInfo] = []
269            let extra : {String : AnyStruct} = {}
270            let nftPointers : [FindViews.ViewReadPointer] = []
271            if nftPointer != nil {
272                let rv = nftPointer!.getViewResolver()
273                nfts.append(FindMarket.NFTInfo(rv, id: nftPointer!.id, detail: true))
274                nftPointers.append(nftPointer!)
275            }
276
277
278            let thought <- create Thought(creator: address, header: header , body: body , created: Clock.time(), tags: tags, medias: medias, nft: nftPointers, quote: quote, stringTags: {}, scalars : {}, extras: extra)
279
280            self.sequence.append(thought.uuid)
281
282            let creatorName = FIND.reverseLookup(address)
283
284            emit Published(id: thought.id ,creator: address, creatorName: creatorName , header: header, message: body, medias: m, nfts: nfts, tags: tags, quoteOwner: quote?.owner(), quoteId: quote?.id)
285
286
287            self.ownedThoughts[thought.uuid] <-! thought
288        }
289
290        access(Owner) fun delete(_ id: UInt64) {
291            pre{
292                self.ownedThoughts.containsKey(id) : "Does not contains Thought with ID : ".concat(id.toString())
293            }
294
295            let thought <- self.ownedThoughts.remove(key: id)!
296            self.sequence.remove(at: self.sequence.firstIndex(of: id)!)
297
298            let address = self.owner!.address
299
300            let medias : [String] = []
301            for m in thought.medias {
302                medias.append(m.file.uri())
303            }
304
305            var name : String? = nil 
306            if address != nil {
307                name = FIND.reverseLookup(address)
308            }
309            emit Deleted(id: thought.id, creator: thought.creator, creatorName: FIND.reverseLookup(thought.creator), header: thought.header, message: thought.body, medias: medias, tags: thought.tags)
310
311            destroy thought
312        }
313
314        access(Owner) fun react(user: Address, id: UInt64, reaction: String?) {
315            let cap = FindThoughts.getFindThoughtsCapability(user)
316            let ref = cap.borrow() ?? panic("Cannot borrow reference to Find Thoughts Collection from user : ".concat(user.toString()))
317
318            let thought = ref.borrowThoughtPublic(id)
319            thought.internal_react(user: self.owner!.address, reaction: reaction)
320        }
321
322        access(Owner) fun hide(id: UInt64, hide: Bool) {
323            let thought = self.borrow(id)
324            thought.hide(hide)
325        }
326
327    }
328
329    access(all) fun createEmptyCollection() : @FindThoughts.Collection {
330        return <- create Collection()
331    }
332
333    access(all) fun getFindThoughtsCapability(_ user: Address) : Capability<&{FindThoughts.CollectionPublic}> {
334        return getAccount(user).capabilities.get<&{FindThoughts.CollectionPublic}>(FindThoughts.CollectionPublicPath)!
335    }
336
337    init(){
338        self.CollectionStoragePath = /storage/FindThoughts 
339        self.CollectionPublicPath = /public/FindThoughts 
340    }
341}
342