Smart Contract

DAO

A.4414755a2180da53.DAO

Valid From

136,316,735

Deployed

1w ago
Feb 15, 2026, 09:42:45 PM UTC

Dependents

3 imports
1// MADE BY: NoahOverflow
2
3import FlowTransactionScheduler from 0xe467b9dd11fa00df 
4import FungibleToken from 0xf233dcee88fe0abe
5import FlowToken from 0x1654653399040a61
6import HybridCustody from 0xd8a7e05a7ac670c0
7import Pinnacle from 0xedf9df96c92f4595
8
9access(all)
10contract DAO {
11    access(self) var currentTopicId: UInt64
12    access(self) var currentFounderId: UInt64
13    access(self) var foundersTopicId: UInt64
14    access(self) var founderVoteCounts: {Address: UInt64}
15    access(self) var voters: {Address: Bool}
16    access(self) var founders: {UInt64: Address}
17    access(self) var unclaimedFounders: @{Address: Founder} 
18    // Events
19    access(all) event Closed(topicId: UInt64)
20    access(all) event TopicProposed(topicId: UInt64, proposer: Address, title: String, allowAnyoneAddOptions: Bool)
21    access(all) event TopicScheduled(topicId: UInt64, handlerPath: String)
22    access(all) event FounderVoted(voter: Address, options: [Address])
23    access(all) event TopicVoted(voter: Address, topicId: UInt64, option: UInt64)
24    access(all) event OptionAdded(topicId: UInt64, optionIndex: UInt64, option: String)
25    access(all) event FounderClaimed(recipient: Address)
26    access(all) event VoteCounted(topicId: UInt64)
27    access(all) event FounderDeposited(recipient: Address, founderId: UInt64, path: String)
28    // Entitlements
29    access(all)entitlement FounderActions
30    access(all)entitlement ArsenalActions
31    // Storage paths
32    access(all) let ArsenalStoragePath: StoragePath
33    access(all) let ArsenalPublicPath: PublicPath
34
35    // Founder resource with privileges
36    access(all) resource Founder {
37        access(all) let id: UInt64
38        init() {
39            self.id = DAO.currentFounderId
40            DAO.currentFounderId = DAO.currentFounderId + 1
41        }
42
43        access(FounderActions) fun proposeTopic(title: String, description: String, initialOptions: [String], allowAnyoneAddOptions: Bool) {
44            pre {
45                title.length > 0: "Title cannot be empty"
46                initialOptions.length > 1: "Must have at least two options"
47            }
48            
49            DAO.currentTopicId = DAO.currentTopicId + 1
50            let topicId = DAO.currentTopicId
51            
52            let identifier = "\(DAO.account.address)/DAO_Topics/\(topicId)"
53            let storagePath = StoragePath(identifier: identifier)!
54            let proposer = DAO.founders[self.id]!
55            
56            let topic <- create Topic(
57                title: title,
58                description: description,
59                proposer: proposer,
60                allowAnyoneAddOptions: allowAnyoneAddOptions
61            )
62            
63            // Add initial options
64            var index: UInt64 = 0
65            while index < UInt64(initialOptions.length) {
66                topic.addStringOption(option: initialOptions[index])
67                index = index + 1
68            }
69            
70            DAO.account.storage.save(<-topic, to: storagePath)
71
72            // Schedule vote count for 7 days from this block
73            let delay: UFix64 = 7.0 * 24.0 * 60.0 * 60.0
74            let future = getCurrentBlock().timestamp + delay
75            let priority = FlowTransactionScheduler.Priority.Medium
76            let executionEffort: UInt64 = 1000
77
78            // Withdraw FLOW fees from contract account vault
79            let vaultRef = DAO.account.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault)
80                ?? panic("Missing FlowToken vault in contract account")
81            let fees <- vaultRef.withdraw(amount: 0.0) as! @FlowToken.Vault
82            let handlerStoragePath = StoragePath(identifier: "\(DAO.account.address)/DAO_Handler")!
83                    let handlerCap = DAO.account.capabilities.storage
84            .issue<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>(storagePath)
85            let receipt <- FlowTransactionScheduler.schedule(
86            handlerCap: handlerCap,
87            data: "",
88            timestamp: future,
89            priority: priority,
90            executionEffort: executionEffort,
91            fees: <-fees
92        )
93
94        // Store receipt
95        let receiptIdentifier = "\(DAO.account.address)/DAO_Receipts/\(receipt.id)"
96        let receiptStoragePath = StoragePath(identifier: receiptIdentifier)!
97        DAO.account.storage.save(<-receipt, to: receiptStoragePath)
98            
99        emit TopicProposed(topicId: topicId, proposer: proposer, title: title, allowAnyoneAddOptions: allowAnyoneAddOptions)
100        emit TopicScheduled(topicId: topicId, handlerPath: handlerStoragePath.toString())
101            
102        }
103
104        access(FounderActions) fun addOption(topicId: UInt64, option: String) {
105            pre {
106                option.length > 0: "Option cannot be empty"
107            }
108            
109            let identifier = "\(DAO.account.address)/DAO_Topics/\(topicId)"
110            let storagePath = StoragePath(identifier: identifier)!
111            let topic = DAO.account.storage.borrow<&Topic>(from: storagePath)
112                ?? panic("Topic not found")
113            
114            topic.addStringOption(option: option)
115            
116            emit OptionAdded(topicId: topicId, optionIndex: topic.getOptionCount() - 1, option: option)
117        }
118    }
119
120    access(all) resource Arsenal {
121        access(all) let pinnacleAccount: Address
122        access(all) var founder: @{UInt64: Founder}
123        
124        init(pinnacleAccount: Address) {
125            self.founder <- {}
126            self.pinnacleAccount = pinnacleAccount
127        }
128        
129        access(contract) fun depositFounder(founder: @Founder) {
130            self.founder[founder.id] <-! founder
131        }
132        // function to propose a topic
133        access(FounderActions) fun proposeTopic(title: String, description: String, initialOptions: [String], allowAnyoneAddOptions: Bool) {
134            pre {
135                self.founder.length > 0: "You are NOT a Founder of the DPIN DAO"
136            }
137            let founderKey = self.founder.keys[0]
138            let founder <- self.founder.remove(key: founderKey)!
139            founder.proposeTopic(title: title, description: description, initialOptions: initialOptions, allowAnyoneAddOptions: allowAnyoneAddOptions)
140            self.founder[founderKey] <-! founder
141
142        }
143        // function to vote on a topic
144        access(ArsenalActions) fun voteTopic(topicId: UInt64, option: UInt64) {
145            let identifier = "\(DAO.account.address)/DAO_Topics/\(topicId)"
146            let storagePath = StoragePath(identifier: identifier)!
147            let topic = DAO.account.storage.borrow<&Topic>(from: storagePath)
148                ?? panic("Topic not found")
149            
150            topic.vote(option: option, voter: self.owner!.address)
151        }
152
153        // function to add option (if allowed)
154        access(ArsenalActions) fun addOption (topicId: UInt64, option: String) {
155            pre {
156                option.length > 0: "Option cannot be empty"
157            }
158            
159            let identifier = "\(DAO.account.address)/DAO_Topics/\(topicId)"
160            let storagePath = StoragePath(identifier: identifier)!
161            let topic = DAO.account.storage.borrow<&Topic>(from: storagePath)
162                ?? panic("Topic not found")
163            
164            topic.addStringOption(option: option)
165            emit OptionAdded(topicId: topicId, optionIndex: topic.getOptionCount() - 1, option: option)
166        }  
167
168        access(ArsenalActions) fun voteFounder(options: [Address])  {
169            pre {
170                options.length > 0 && options.length <= 3: "Must provide 1-3 options"
171                DAO.voters[self.owner!.address] == nil: "You have already voted"
172            }
173            let identifier = "\(DAO.account.address)/DAO_Topics/0"
174            let storagePath = StoragePath(identifier: identifier)!
175            let topic = DAO.account.storage.borrow<&Topic>(from: storagePath)
176                ?? panic("Founders topic not found")
177            topic.voteFounder(options: options, voter: self.owner!.address)
178
179            emit FounderVoted(voter: self.owner!.address, options: options)
180        }
181    }
182
183    access(all)
184    resource Topic {
185        access(all) var id: UInt64
186        access(all) var title: String
187        access(all) var description: String
188        access(all) var proposer: Address
189        access(all) var allowAnyoneAddOptions: Bool
190        access(all) var isFoundersTopic: Bool
191        
192        access(all) var stringOptions: [String]
193        access(all) var addressOptions: [Address]
194        access(all) var votes: {UInt64: [Address]}
195        access(all) var closed: Bool
196        access(all) var voters: {Address: Bool}
197
198        init(title: String, description: String, proposer: Address, allowAnyoneAddOptions: Bool) {
199            self.id = DAO.currentTopicId
200            self.title = title
201            self.description = description
202            self.proposer = proposer
203            self.allowAnyoneAddOptions = allowAnyoneAddOptions
204            self.isFoundersTopic = false
205            self.stringOptions = []
206            self.addressOptions = []
207            self.votes = {}
208            self.closed = false
209            self.voters = {}
210
211            DAO.currentTopicId = DAO.currentTopicId + 1
212
213            // Create and save handler 
214            let handlerIdentifier = "\(DAO.account.address)/DAO_Handler/\(self.id)"
215            let handlerStoragePath = StoragePath(identifier: handlerIdentifier)!
216            let handler <- create Handler(topicId: self.id)
217            DAO.account.storage.save(<-handler, to: handlerStoragePath)
218            // Issue handler capability to the handler
219            let handlerCap = DAO.account.capabilities.storage
220                .issue<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>(handlerStoragePath)
221
222            // Schedule vote count for 7 days from this block
223            let delay: UFix64 = 7.0 * 24.0 * 60.0 * 60.0
224            let future = getCurrentBlock().timestamp + delay
225            let priority = FlowTransactionScheduler.Priority.Medium
226            let executionEffort: UInt64 = 1000
227            // Estimate the cost
228            let estimate = FlowTransactionScheduler.estimate(
229            data: "",
230            timestamp: future,
231            priority: priority,
232            executionEffort: executionEffort
233            )
234
235            assert(
236                estimate.timestamp != nil || priority == FlowTransactionScheduler.Priority.Low,
237                message: estimate.error ?? "estimation failed"
238            )
239            // Withdraw FLOW fees from contract account vault
240            let vaultRef = DAO.account.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault)
241                ?? panic("Missing FlowToken vault in contract account")
242            let fees <- vaultRef.withdraw(amount: estimate.flowFee ?? 0.0) as! @FlowToken.Vault
243            let receipt <- FlowTransactionScheduler.schedule(
244            handlerCap: handlerCap,
245            data: "",
246            timestamp: future,
247            priority: priority,
248            executionEffort: executionEffort,
249            fees: <-fees
250            )
251        
252            // Store receipt
253            let receiptIdentifier = "\(DAO.account.address)/DAO_Receipts/\(receipt.id)"
254            let receiptStoragePath = StoragePath(identifier: receiptIdentifier)!
255            DAO.account.storage.save(<-receipt, to: receiptStoragePath)
256
257            emit TopicScheduled(topicId: self.id, handlerPath: handlerStoragePath.toString())
258
259        }
260
261        access(contract) fun initFoundersTopic() {
262            self.title = "Founders"
263            self.description = "Vote for the top 5 founders of the DPIN DAO"
264            self.proposer = DAO.account.address
265            self.allowAnyoneAddOptions = true
266            self.isFoundersTopic = true
267        }
268
269        access(all) fun addStringOption(option: String) {
270            pre {
271                self.closed == false: "Topic is closed"
272            }
273            self.stringOptions.append(option)
274            emit OptionAdded(topicId: DAO.currentTopicId, optionIndex: UInt64(self.stringOptions.length - 1), option: option)
275        }
276
277        access(all) fun addAddressOption(option: Address) {
278            pre {
279                self.closed == false: "Topic is closed"
280            }
281            // Only add the address to the options list if it doesn't exist
282            // Vote counting is handled in voteFounder, not here
283            if !self.addressOptions.contains(option) {
284                self.addressOptions.append(option)
285            }
286            emit OptionAdded(topicId: DAO.currentTopicId, optionIndex: UInt64(self.addressOptions.length - 1), option: option.toString())
287        }
288
289        access(all) view fun getOptionCount(): UInt64 {
290            if self.isFoundersTopic {
291                return UInt64(self.addressOptions.length)
292            } else {
293                return UInt64(self.stringOptions.length)
294            }
295        }
296
297        access(all) fun vote(option: UInt64, voter: Address) {
298            pre {
299                self.closed == false: "Topic is closed"
300                self.voters[voter] == nil: "Voter has already voted"
301                (self.isFoundersTopic && option < UInt64(self.addressOptions.length)) || 
302                (!self.isFoundersTopic && option < UInt64(self.stringOptions.length)): "Invalid option index"
303            }
304            
305            if self.votes[option] == nil {
306                self.votes[option] = []
307            }
308            self.votes[option]!.append(voter)
309            self.voters[voter] = true
310        }
311
312        access(all) fun voteFounder(options: [Address], voter: Address) {
313            pre {
314                self.isFoundersTopic: "This function is only for founders topic"
315                options.length > 0 && options.length <= 3: "Must provide 1-3 options"
316                DAO.voters[voter] == nil: "You have already voted"
317            }
318            
319            // add each address option and vote for it
320            var i: UInt64 = 0
321            while i < UInt64(options.length) {
322                let address = options[i]
323                
324                // Find the index of this address in addressOptions
325                var optionIndex: UInt64? = nil
326                var j: UInt64 = 0
327                while j < UInt64(self.addressOptions.length) {
328                    if self.addressOptions[j] == address {
329                        optionIndex = j
330                        break
331                    }
332                    j = j + 1
333                }
334                
335                // If address not found in options, add it first
336                if optionIndex == nil {
337                    self.addAddressOption(option: address)
338                    optionIndex = UInt64(self.addressOptions.length) - 1
339                }
340                
341                // Initialize votes array for this option if needed
342                if self.votes[optionIndex!] == nil {
343                    self.votes[optionIndex!] = []
344                }
345                
346                // Vote for the address using its actual index in addressOptions
347                self.votes[optionIndex!]!.append(voter)
348                
349                // Update founderVoteCounts for this address
350                if DAO.founderVoteCounts[address] != nil {
351                    DAO.founderVoteCounts[address] = DAO.founderVoteCounts[address]! + 1
352                } else {
353                    DAO.founderVoteCounts[address] = 1
354                }
355                
356                // increment the loop index
357                i = i + 1
358            }
359            
360            // Mark voter as having voted (both contract-level and topic-level)
361            DAO.voters[voter] = true
362            self.voters[voter] = true
363        }
364
365        access(all)
366        view fun getVotes(option: UInt64): UInt64 {
367            if self.votes[option] == nil {
368                return 0
369            }
370            return UInt64(self.votes[option]!.length)
371        }
372
373        access(all)
374        view fun getAllVotes(): {UInt64: UInt64} {
375            let result: {UInt64: UInt64} = {}
376            let optionCount = self.getOptionCount()
377            var i: UInt64 = 0
378            while i < optionCount {
379                result[i] = self.getVotes(option: i)
380                i = i + 1
381            }
382            return result
383        }
384
385        access(contract)
386        fun close() {
387            self.closed = true
388            emit Closed(topicId: DAO.currentTopicId)
389        }
390
391        access(contract)
392        fun getTopAddresses(count: UInt64): [Address] {
393            pre {
394                self.isFoundersTopic: "This function is only for founders topic"
395            }
396            
397            // Create arrays for addresses and vote counts
398            var addresses: [Address] = []
399            var voteCounts: [UInt64] = []
400            var i: UInt64 = 0
401            while i < UInt64(self.addressOptions.length) {
402                let address = self.addressOptions[i]
403                let voteCount = self.getVotes(option: i)
404                addresses.append(address)
405                voteCounts.append(voteCount)
406                i = i + 1
407            }
408            
409            // Sort by vote count (descending) - simple bubble sort
410            var swapped = true
411            while swapped {
412                swapped = false
413                var k: Int = 0
414                while k < addresses.length - 1 {
415                    if voteCounts[k] < voteCounts[k + 1] {
416                        // Swap addresses
417                        let tempAddr = addresses[k]
418                        addresses[k] = addresses[k + 1]
419                        addresses[k + 1] = tempAddr
420                        // Swap vote counts
421                        let tempCount = voteCounts[k]
422                        voteCounts[k] = voteCounts[k + 1]
423                        voteCounts[k + 1] = tempCount
424                        swapped = true
425                    }
426                    k = k + 1
427                }
428            }
429            
430            // Return top N addresses
431            var result: [Address] = []
432            var j: UInt64 = 0
433            while j < count && j < UInt64(addresses.length) {
434                result.append(addresses[j])
435                j = j + 1
436            }
437            
438            return result
439        }
440        // return a list of addresses only
441        access(all)
442        view fun getAllAddresses(): [Address] {
443            return self.addressOptions
444        }
445
446        // return TopicInfo struct with all topic data
447        access(all)
448        fun getTopicInfo(): TopicInfo {
449            return TopicInfo(
450                title: self.title,
451                description: self.description,
452                proposer: self.proposer,
453                allowAnyoneAddOptions: self.allowAnyoneAddOptions,
454                isFoundersTopic: self.isFoundersTopic,
455                stringOptions: self.stringOptions,
456                addressOptions: self.addressOptions,
457                votes: self.votes,
458                closed: self.closed,
459                voters: self.voters
460            )
461        }
462    }
463    // -----------------------------------------------------------------------
464    // "##  ##      ##      ##  ##    ##  ##    ##      ##      ####  "
465    // "##  ##     ####     ## ##     ##  ##    ##      ##      ##  ##"
466    // "##  ##    ##  ##    ####      ##  ##    ##      ##      ##  ##"
467    // "######    ######    ## ##     ##  ##    ##      ##      ####  "
468    // "##  ##    ##  ##    ##  ##    ##  ##    ##      ##      ## ## "
469    // "##  ##    ##  ##    ##  ##    ######    ######  ######  ##  ##"
470    // "##  ##    ##  ##    ##  ##    ##  ##    ##      ##      ##  ##"
471    // -----------------------------------------------------------------------
472    /// Handler resource that implements the Scheduled Transaction interface
473    access(all) resource Handler: FlowTransactionScheduler.TransactionHandler {
474
475        access(all) let topicId: UInt64
476
477        init(topicId: UInt64) {
478            self.topicId = topicId
479        }
480        // This is the function executed by the Flow Protocol
481        access(FlowTransactionScheduler.Execute) fun executeTransaction(id: UInt64, data: AnyStruct?) {
482            let identifier = "\(DAO.account.address)/DAO_Topics/\(self.topicId)"
483            let storagePath = StoragePath(identifier: identifier)!
484            let topic = DAO.account.storage.borrow<&Topic>(from: storagePath)
485                ?? panic("Unable to borrow reference to the topic")
486            
487            // Close the topic
488            topic.close()
489            
490            // If this is the founders topic, distribute Founder resources
491            if topic.isFoundersTopic {
492                let topAddresses = topic.getTopAddresses(count: 5)
493                
494                // Distribute Founder resources to top 5 addresses
495                var i: UInt64 = 0
496                while i < UInt64(topAddresses.length) {
497                    let recipientAddress = topAddresses[i]
498                    
499                    // add the recipient address to the founders list
500                    DAO.founders[DAO.currentFounderId] = recipientAddress
501                    // Mint the Founder resource and send to the recipient
502                    let founder <- create Founder()
503                    // get a ref to the recipient's Founder's arsenal
504                    let account = getAccount(recipientAddress)
505                    let arsenal = account.capabilities.borrow<&Arsenal>(DAO.ArsenalPublicPath)
506                    // if the arsenal is nil,
507                    // save founder resource inside the contract
508                    // deposit the founder into the arsenal
509                    if arsenal == nil {
510                        DAO.unclaimedFounders[recipientAddress] <-! founder
511                    } else {
512                        arsenal!.depositFounder(founder: <-founder)
513                        emit FounderDeposited(recipient: recipientAddress, founderId: DAO.currentFounderId, path: DAO.ArsenalPublicPath.toString())
514                    }
515                    // increment the loop index
516                    i = i + 1
517                }
518        
519        emit Closed(topicId: 0)
520            }
521            
522            emit VoteCounted(topicId: self.topicId)
523
524            // Determine delay for the next transaction (default 3 seconds if none provided)
525            var delay: UFix64 = 10.0 // 10 seconds
526            if data != nil {
527                let t = data!.getType()
528                if t.isSubtype(of: Type<UFix64>()) {
529                    delay = data as! UFix64
530                }
531            }
532
533            let future = getCurrentBlock().timestamp + delay
534            let priority = FlowTransactionScheduler.Priority.Medium
535            let executionEffort: UInt64 = 1000
536
537            let estimate = FlowTransactionScheduler.estimate(
538                data: data,
539                timestamp: future,
540                priority: priority,
541                executionEffort: executionEffort
542            )       
543
544            assert(
545                estimate.timestamp != nil || priority == FlowTransactionScheduler.Priority.Low,
546                message: estimate.error ?? "estimation failed"
547            )   
548
549            // Withdraw FLOW fees from this resource's ownner account vault
550            let vaultRef = DAO.account.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault)
551                ?? panic("Missing FlowToken vault in contract account")
552            let feesVault <- vaultRef.withdraw(amount: estimate.flowFee ?? 0.0) as! @FlowToken.Vault   
553            let handlerStoragePath = StoragePath(identifier: "\(DAO.account.address)/DAO_Handler")!
554
555            // Issue a capability to the handler stored in this contract account
556            let handlerCap = DAO.account.capabilities.storage
557                .issue<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>(handlerStoragePath)
558
559            let receipt: @FlowTransactionScheduler.ScheduledTransaction <- FlowTransactionScheduler.schedule(
560                handlerCap: handlerCap,
561                data: data,
562                timestamp: future,
563                priority: priority,
564                executionEffort: executionEffort,
565                fees: <-feesVault
566            )
567
568            log("Loop transaction id: ".concat(receipt.id.toString()).concat(" at ").concat(receipt.timestamp.toString()))
569            // Save the receipt to the contract account storage
570            let receiptIdentifier = "\(DAO.account.address)/DAO_Receipts/\(receipt.id)"
571            let receiptStoragePath = StoragePath(identifier: receiptIdentifier)!
572            DAO.account.storage.save(<-receipt, to: receiptStoragePath)
573        }
574    }
575    // Topic Struct to store topic information
576    access(all) struct TopicInfo {
577        access(all) var title: String
578        access(all) var description: String
579        access(all) var proposer: Address
580        access(all) var allowAnyoneAddOptions: Bool
581        access(all) var isFoundersTopic: Bool
582        access(all) var stringOptions: [String]
583        access(all) var addressOptions: [Address]
584        access(all) var votes: {UInt64: [Address]}
585        access(all) var closed: Bool
586        access(all) var voters: {Address: Bool}
587        init(title: String, description: String, proposer: Address, allowAnyoneAddOptions: Bool, isFoundersTopic: Bool, stringOptions: [String], addressOptions: [Address], votes: {UInt64: [Address]}, closed: Bool, voters: {Address: Bool}) {
588            self.title = title
589            self.description = description
590            self.proposer = proposer
591            self.allowAnyoneAddOptions = allowAnyoneAddOptions
592            self.isFoundersTopic = isFoundersTopic
593            self.stringOptions = stringOptions
594            self.addressOptions = addressOptions
595            self.votes = votes
596            self.closed = closed
597            self.voters = voters
598        }
599    }
600
601    // Public function to vote get all votes
602    // on FOUNDER TOPICs
603    access(all) view fun getFounderVotes(): {Address: UInt64} {
604        return DAO.founderVoteCounts
605    }
606    // Public function to get all founders
607    access(all) view fun getAllFounders(): {UInt64: Address} {
608        return DAO.founders
609    }
610    // Public function to get all unclaimed founders
611    access(all) view fun getUnclaimedFounders():[Address] {
612        return DAO.unclaimedFounders.keys
613    }
614    // Public function to get the latest topics
615    access(all) fun getLatestTopics(): [TopicInfo] {
616        // loop through to the latest topics based on the amount of Founder
617        // if 5 founders, then get the latest 5 topics
618        // then get the topic info for each topic
619        // and return the topic info
620        var topicInfos: [TopicInfo] = []
621        
622        // Get the number of founders to determine how many topics to retrieve
623        let founderCount = UInt64(DAO.founders.length)
624        let topicCount = founderCount
625        
626        // Calculate the starting topic ID (ensure we don't go below 0)
627        // If we want the latest N topics and currentTopicId = M, we want topics from max(0, M-N+1) to M
628        var startTopicId: UInt64 = 0
629        if DAO.currentTopicId >= topicCount {
630            startTopicId = DAO.currentTopicId - topicCount + 1
631        }
632        
633        // Loop through topics from startTopicId to currentTopicId (inclusive)
634        var i: UInt64 = startTopicId
635        while i <= DAO.currentTopicId {
636            let identifier = "\(DAO.account.address)/DAO_Topics/\(i)"
637            let storagePath = StoragePath(identifier: identifier)!
638            if let topic = DAO.account.storage.borrow<&Topic>(from: storagePath) {
639                topicInfos.append(topic.getTopicInfo())
640            }
641            i = i + 1
642        }
643        
644        return topicInfos
645    }
646
647    // Public function to close a FounderTopic and distribute Founder resources
648    // The founders topic ID is always 0
649     access(all) fun closeFounderTopic() {
650        let identifier = "\(DAO.account.address)/DAO_Topics/\(0)"
651        let storagePath = StoragePath(identifier: identifier)!
652        let topic = DAO.account.storage.borrow<&Topic>(from: storagePath)
653            ?? panic("Founders topic not found")
654        
655        assert(topic.isFoundersTopic, message: "This function can only close FounderTopics")
656        assert(topic.closed == false, message: "Topic is already closed")
657        
658        // Close the topic
659        topic.close()
660        
661        // Distribute Founder resources to top 5 addresses
662        let topAddresses = topic.getTopAddresses(count: 5)
663        
664        var i: UInt64 = 0
665        while i < UInt64(topAddresses.length) {
666            let recipientAddress = topAddresses[i]
667            
668            // add the recipient address to the founders list
669            DAO.founders[DAO.currentFounderId] = recipientAddress
670            // Mint the Founder resource and send to the recipient
671            let founder <- create Founder()
672            // get a ref to the recipient's Founder's arsenal
673            let account = getAccount(recipientAddress)
674            let arsenal = account.capabilities.borrow<&Arsenal>(DAO.ArsenalPublicPath)
675            // if the arsenal is nil,
676            // save founder resource inside the contract
677            // deposit the founder into the arsenal
678            if arsenal == nil {
679                DAO.unclaimedFounders[recipientAddress] <-! founder
680            } else {
681                arsenal!.depositFounder(founder: <-founder)
682                emit FounderDeposited(recipient: recipientAddress, founderId: DAO.currentFounderId, path: DAO.ArsenalPublicPath.toString())
683            }
684            // increment the loop index
685            i = i + 1
686        }
687        
688        emit Closed(topicId: 0)
689    } 
690
691    // Public function to create an Arsenal
692    access(all) fun createArsenal(parentAccount: &Account): @Arsenal? {
693/*         let newArsenal <- create Arsenal(pinnacleAccount: parentAccount.address)
694        return <- newArsenal */
695        // We need to verify that the parent account is a manager
696        // of a Pinnacle Collection owner child account
697     let manager = parentAccount.capabilities.borrow<&HybridCustody.Manager>(HybridCustody.ManagerPublicPath)
698        ?? panic("manager not found")
699        // Get children of the parent account
700        var children = manager.getChildAddresses()
701
702        if children.length > 0 {
703            var childAddress: Address? = nil
704            // loop through each child and look for
705            // /public/PinnacleCollection
706            var i: UInt64 = 0
707            while i < UInt64(children.length) {
708                let child = children[i]
709                let childAcct = getAccount(child)
710                let childManager = childAcct.capabilities.borrow<&Pinnacle.Collection>(Pinnacle.CollectionPublicPath)
711                if childManager != nil {
712                    let ids = childManager!.getIDs()
713                    if ids.length > 9 {
714                        let newArsenal <- create Arsenal(pinnacleAccount: child)
715                        return <- newArsenal
716                    }
717                }
718                i = i + 1
719            }
720            panic("No Pinnacle Collection owner child account found with more than 9 NFTs")
721        } 
722            return nil
723    }
724
725    init() {
726        self.currentTopicId = 0
727        self.currentFounderId = 0
728        self.foundersTopicId = 0
729        self.founderVoteCounts = {}
730        self.unclaimedFounders <- {}
731        self.voters = {}
732        self.founders = {}
733        self.ArsenalStoragePath = StoragePath(identifier: "\(DAO.account.address)/DAO_Arsenal")!
734        self.ArsenalPublicPath = PublicPath(identifier: "\(DAO.account.address)/DAO_Arsenal")!
735        // Create the Founders topic
736        let identifier = "\(DAO.account.address)/DAO_Topics/\(self.currentTopicId)"
737        let storagePath = StoragePath(identifier: identifier)!
738        let foundersTopic <- create Topic(title: "", description: "", proposer: DAO.account.address, allowAnyoneAddOptions: true)
739        foundersTopic.initFoundersTopic()
740        DAO.account.storage.save(<-foundersTopic, to: storagePath)
741
742    }
743}
744