Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add NFT Event Ticketing System #3509

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
119 changes: 119 additions & 0 deletions examples/gno.land/r/jjoptimist/eventix/eventix.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package eventix

import (
"std"
"strconv"
"time"

"gno.land/p/demo/avl"
"gno.land/p/demo/grc/grc721"
"gno.land/p/demo/ufmt"
)

type Event struct {
name string
description string
date time.Time
maxTickets int
price uint64 // in gnot
ticketsSold int
}

var (
events *avl.Tree
eventCounter uint64 = 0
tickets = grc721.NewBasicNFT("Event Ticket", "EVTIX")
)

func init() {
events = avl.NewTree()
}


func CreateEvent(name, description string, dateStr string, maxTickets int, price uint64) uint64 {

date, err := time.Parse("2006-01-02T15:04:05Z", dateStr)
if err != nil {
panic("Invalid date format. Use: YYYY-MM-DDThh:mm:ssZ")
}

eventCounter++
event := Event{
name: name,
description: description,
date: date,
maxTickets: maxTickets,
price: price,
ticketsSold: 0,
}
events.Set(strconv.FormatUint(eventCounter, 10), event)
return eventCounter
}


func getEvent(eventId uint64) (Event, bool) {
value, exists := events.Get(strconv.FormatUint(eventId, 10))
if !exists {
return Event{}, false
}
return value.(Event), true
}


func setEvent(eventId uint64, event Event) {
events.Set(strconv.FormatUint(eventId, 10), event)
}


func BuyTicket(eventId uint64) {
event, exists := getEvent(eventId)
if !exists {
panic("Event does not exist")
}

if event.ticketsSold >= event.maxTickets {
panic("Event is sold out")
}


sent := std.GetOrigSend()
amount := sent.AmountOf("ugnot")
if amount != int64(event.price) {
panic(ufmt.Sprintf("Please send exactly %d ugnot", event.price))
}


caller := std.PrevRealm().Addr()
tokenId := grc721.TokenID(strconv.FormatUint(eventId, 10) + "-" + strconv.Itoa(event.ticketsSold+1))
tickets.Mint(caller, tokenId)


event.ticketsSold++
setEvent(eventId, event)
}

// all events and ticket info
func Render(path string) string {
output := "# Event Ticketing System\n\n"

events.Iterate("", "", func(key string, value interface{}) bool {
id, _ := strconv.ParseUint(key, 10, 64)
event := value.(Event)

output += ufmt.Sprintf("## Event #%d: %s\n", id, event.name)
output += ufmt.Sprintf("Description: %s\n", event.description)
output += ufmt.Sprintf("Date: %s\n", event.date.Format("2006-01-02 15:04:05"))
output += ufmt.Sprintf("Tickets: %d/%d\n", event.ticketsSold, event.maxTickets)
output += ufmt.Sprintf("Price: %d ugnot\n\n", event.price)

if event.ticketsSold < event.maxTickets {
output += ufmt.Sprintf("[Buy Ticket](/r/jjoptimist/eventix/BuyTicket?eventId=%d)\n", id)
} else {
output += "**SOLD OUT**\n"
}
output += "---\n\n"
return false
})

return output
}
93 changes: 93 additions & 0 deletions examples/gno.land/r/jjoptimist/eventix/eventix_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package eventix

import (
"std"
"testing"
"time"

"gno.land/p/demo/grc/grc721"
)

func TestCreateEvent(t *testing.T) {
// Test successful event creation
eventId := CreateEvent(
"Test Event",
"A test event",
"2024-12-31T23:59:59Z",
100,
1000000,
)

if eventId != 1 {
t.Errorf("Expected first event ID to be 1, got %d", eventId)
}

event, exists := getEvent(eventId)
if !exists {
t.Error("Event was not created")
}

if event.name != "Test Event" {
t.Errorf("Expected event name 'Test Event', got '%s'", event.name)
}

// Test invalid date format
defer func() {
if r := recover(); r == nil {
t.Error("Expected panic with invalid date format")
}
}()
CreateEvent("Test", "Test", "invalid-date", 100, 1000000)
}

func TestBuyTicket(t *testing.T) {
// Setup test event
eventId := CreateEvent(
"Test Event",
"A test event",
"2024-12-31T23:59:59Z",
2,
1000000,
)

// Setup test buyer
buyer := std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
std.TestSetOrigCaller(buyer)

// Test buying without payment
defer func() {
if r := recover(); r == nil {
t.Error("Expected panic when buying without payment")
}
}()
BuyTicket(eventId)

// Test successful purchase
std.TestSetOrigSend(std.Coins{{"ugnot", 1000000}}, nil)
BuyTicket(eventId)

event, _ := getEvent(eventId)
if event.ticketsSold != 1 {
t.Errorf("Expected 1 ticket sold, got %d", event.ticketsSold)
}

// Verify NFT ownership
tokenId := grc721.TokenID("1-1")
owner, err := tickets.OwnerOf(tokenId) // Handle both return values
if err != nil {
t.Errorf("Error getting token owner: %v", err)
}
if owner != buyer {
t.Errorf("Expected ticket owner to be %s, got %s", buyer, owner)
}

// Test buying sold out event
std.TestSetOrigSend(std.Coins{{"ugnot", 1000000}}, nil)
BuyTicket(eventId) // Buy second ticket
defer func() {
if r := recover(); r == nil {
t.Error("Expected panic when event is sold out")
}
}()
BuyTicket(eventId) // Should panic - sold out
}
7 changes: 7 additions & 0 deletions examples/gno.land/r/jjoptimist/eventix/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module gno.land/r/jjoptimist/eventix

dependencies (
gno.land/p/demo/avl v0.0.0
gno.land/p/demo/grc/grc721 v0.0.0
gno.land/p/demo/ufmt v0.0.0
)
Loading