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}