Smart Contract
DAO
A.4414755a2180da53.DAO
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