btree_dao.gno
5.82 Kb ยท 209 lines
1package btree_dao
2
3import (
4 "errors"
5 "std"
6 "strings"
7 "time"
8
9 "gno.land/p/demo/btree"
10 "gno.land/p/demo/grc/grc721"
11 "gno.land/p/demo/ufmt"
12 "gno.land/p/moul/md"
13)
14
15// RegistrationDetails holds the details of a user's registration in the BTree DAO.
16// It stores the user's address, registration time, their B-Tree if they planted one,
17// and their NFT ID.
18type RegistrationDetails struct {
19 Address std.Address
20 RegTime time.Time
21 UserBTree *btree.BTree
22 NFTID string
23}
24
25// Less implements the btree.Record interface for RegistrationDetails.
26// It compares two RegistrationDetails based on their registration time.
27// Returns true if the current registration time is before the other registration time.
28func (rd *RegistrationDetails) Less(than btree.Record) bool {
29 other := than.(*RegistrationDetails)
30 return rd.RegTime.Before(other.RegTime)
31}
32
33var (
34 dao = grc721.NewBasicNFT("BTree DAO", "BTDAO")
35 tokenID = 0
36 members = btree.New()
37)
38
39// PlantTree allows a user to plant their B-Tree in the DAO forest.
40// It mints an NFT to the user and registers their tree in the DAO.
41// Returns an error if the tree is already planted, empty, or if NFT minting fails.
42func PlantTree(userBTree *btree.BTree) error {
43 return plantImpl(userBTree, "")
44}
45
46// PlantSeed allows a user to register as a seed in the DAO with a message.
47// It mints an NFT to the user and registers them as a seed member.
48// Returns an error if the message is empty or if NFT minting fails.
49func PlantSeed(message string) error {
50 return plantImpl(nil, message)
51}
52
53// plantImpl is the internal implementation that handles both tree planting and seed registration.
54// For tree planting (userBTree != nil), it verifies the tree isn't already planted and isn't empty.
55// For seed planting (userBTree == nil), it verifies the seed message isn't empty.
56// In both cases, it mints an NFT to the user and adds their registration details to the members tree.
57// Returns an error if any validation fails or if NFT minting fails.
58func plantImpl(userBTree *btree.BTree, seedMessage string) error {
59 // Get the caller's address
60 userAddress := std.OriginCaller()
61
62 var nftID string
63 var regDetails *RegistrationDetails
64
65 if userBTree != nil {
66 // Handle tree planting
67 var treeExists bool
68 members.Ascend(func(record btree.Record) bool {
69 regDetails := record.(*RegistrationDetails)
70 if regDetails.UserBTree == userBTree {
71 treeExists = true
72 return false
73 }
74 return true
75 })
76 if treeExists {
77 return errors.New("tree is already planted in the forest")
78 }
79
80 if userBTree.Len() == 0 {
81 return errors.New("cannot plant an empty tree")
82 }
83
84 nftID = ufmt.Sprintf("%d", tokenID)
85 regDetails = &RegistrationDetails{
86 Address: userAddress,
87 RegTime: time.Now(),
88 UserBTree: userBTree,
89 NFTID: nftID,
90 }
91 } else {
92 // Handle seed planting
93 if seedMessage == "" {
94 return errors.New("seed message cannot be empty")
95 }
96 nftID = "seed_" + ufmt.Sprintf("%d", tokenID)
97 regDetails = &RegistrationDetails{
98 Address: userAddress,
99 RegTime: time.Now(),
100 UserBTree: nil,
101 NFTID: nftID,
102 }
103 }
104
105 // Mint an NFT to the user
106 err := dao.Mint(userAddress, grc721.TokenID(nftID))
107 if err != nil {
108 return err
109 }
110
111 members.Insert(regDetails)
112 tokenID++
113 return nil
114}
115
116// Render generates a Markdown representation of the DAO members.
117// It displays:
118// - Total number of NFTs minted
119// - Total number of members
120// - Size of the biggest planted tree
121// - The first 3 members (OGs)
122// - The latest 10 members
123// Each member entry includes their address and owned NFTs (๐ณ for trees, ๐ฑ for seeds).
124// The path parameter is currently unused.
125// Returns a formatted Markdown string.
126func Render(path string) string {
127 var latestMembers []string
128 var ogMembers []string
129
130 // Get total size and first member
131 totalSize := members.Len()
132 biggestTree := 0
133 if maxMember := members.Max(); maxMember != nil {
134 if userBTree := maxMember.(*RegistrationDetails).UserBTree; userBTree != nil {
135 biggestTree = userBTree.Len()
136 }
137 }
138
139 // Collect the latest 10 members
140 members.Descend(func(record btree.Record) bool {
141 if len(latestMembers) < 10 {
142 regDetails := record.(*RegistrationDetails)
143 addr := regDetails.Address
144 nftList := ""
145 balance, err := dao.BalanceOf(addr)
146 if err == nil && balance > 0 {
147 nftList = " (NFTs: "
148 for i := int64(0); i < balance; i++ {
149 if i > 0 {
150 nftList += ", "
151 }
152 if regDetails.UserBTree == nil {
153 nftList += "๐ฑ#" + regDetails.NFTID
154 } else {
155 nftList += "๐ณ#" + regDetails.NFTID
156 }
157 }
158 nftList += ")"
159 }
160 latestMembers = append(latestMembers, string(addr)+nftList)
161 return true
162 }
163 return false
164 })
165
166 // Collect the first 3 members (OGs)
167 members.Ascend(func(record btree.Record) bool {
168 if len(ogMembers) < 3 {
169 regDetails := record.(*RegistrationDetails)
170 addr := regDetails.Address
171 nftList := ""
172 balance, err := dao.BalanceOf(addr)
173 if err == nil && balance > 0 {
174 nftList = " (NFTs: "
175 for i := int64(0); i < balance; i++ {
176 if i > 0 {
177 nftList += ", "
178 }
179 if regDetails.UserBTree == nil {
180 nftList += "๐ฑ#" + regDetails.NFTID
181 } else {
182 nftList += "๐ณ#" + regDetails.NFTID
183 }
184 }
185 nftList += ")"
186 }
187 ogMembers = append(ogMembers, string(addr)+nftList)
188 return true
189 }
190 return false
191 })
192
193 var sb strings.Builder
194
195 sb.WriteString(md.H1("B-Tree DAO Members"))
196 sb.WriteString(md.H2("Total NFTs Minted"))
197 sb.WriteString(ufmt.Sprintf("Total NFTs minted: %d\n\n", dao.TokenCount()))
198 sb.WriteString(md.H2("Member Stats"))
199 sb.WriteString(ufmt.Sprintf("Total members: %d\n", totalSize))
200 if biggestTree > 0 {
201 sb.WriteString(ufmt.Sprintf("Biggest tree size: %d\n", biggestTree))
202 }
203 sb.WriteString(md.H2("OG Members"))
204 sb.WriteString(md.BulletList(ogMembers))
205 sb.WriteString(md.H2("Latest Members"))
206 sb.WriteString(md.BulletList(latestMembers))
207
208 return sb.String()
209}