diff --git a/presentations/2024-08-05--web3kamp--leon/README.md b/presentations/2024-08-05--web3kamp--leon/README.md new file mode 100644 index 0000000..1a87053 --- /dev/null +++ b/presentations/2024-08-05--web3kamp--leon/README.md @@ -0,0 +1,15 @@ +# Web3 Kamp 2024 - gno.land workshop + +Welcome to the Web3 Kamp 2024 gno.land workshop! + +This workshop is meant for developers that have little to no knowledge of Go/Gno, +but have at least a beginner amount of knowledge in other blockchain systems +(such as Ethereum). + +In this workshop, you will learn the basics of programming in gno.land, and build a +simple Twitter clone using the Gno language. + +This workshop contains phases; each phase is meant to be a stepping stone in the +development process to the next one. Check out the [phases](./phases) folder to start. + +Check out the slides for the workshop [here](https://docs.google.com/presentation/d/1tnplCWxhg-RFatDS3w1iJnO0vSfBAuw2ZA0ommNJQOU/edit?usp=sharing). \ No newline at end of file diff --git a/presentations/2024-08-05--web3kamp--leon/phases/finished/gno.mod b/presentations/2024-08-05--web3kamp--leon/phases/finished/gno.mod new file mode 100644 index 0000000..3ed8657 --- /dev/null +++ b/presentations/2024-08-05--web3kamp--leon/phases/finished/gno.mod @@ -0,0 +1 @@ +module gno.land/r/petnica/twitter diff --git a/presentations/2024-08-05--web3kamp--leon/phases/finished/render.gno b/presentations/2024-08-05--web3kamp--leon/phases/finished/render.gno new file mode 100644 index 0000000..1ab3798 --- /dev/null +++ b/presentations/2024-08-05--web3kamp--leon/phases/finished/render.gno @@ -0,0 +1,24 @@ +package twitter + +import ( + "time" + + "gno.land/p/demo/ufmt" +) + +func (p Post) String() string { + return ufmt.Sprintf("Text: %s\n\nAuthor: %s\n\n Time: %s\n\nUp: %d, Down: %d\n\nTotal tips: %dugnot", p.text, p.author.String(), p.createdAt.Format(time.RFC822), p.upvotes, p.downvotes, p.tipTotal) +} + +func Render(_ string) string { + if len(posts) == 0 { + return "No posts currently." + } + + output := "" + for _, post := range posts { + output += ufmt.Sprintf("### Post #%d\n\n%s\n\n", post.id, post.String()) + } + + return output +} \ No newline at end of file diff --git a/presentations/2024-08-05--web3kamp--leon/phases/finished/twitter.gno b/presentations/2024-08-05--web3kamp--leon/phases/finished/twitter.gno new file mode 100644 index 0000000..0df716b --- /dev/null +++ b/presentations/2024-08-05--web3kamp--leon/phases/finished/twitter.gno @@ -0,0 +1,108 @@ +package twitter + +import ( + "std" + "time" +) + +type Post struct { + id uint + + text string + author std.Address + createdAt time.Time + upvotes uint + downvotes uint + + tipTotal int64 +} + +var ( + posts []*Post + idCounter uint +) + +func AddPost(text string) uint { + if text == "" { + panic("post text cannot be empty") + } + + p := &Post{ + text: text, + id: idCounter, + author: std.PrevRealm().Addr(), + createdAt: time.Now(), + upvotes: 0, + downvotes: 0, + tipTotal: 0, + } + + posts = append(posts, p) + idCounter++ + + return p.id +} + +func RemovePost(id uint) { + caller := std.PrevRealm().Addr() + + idx, p := getPost(id) + if p == nil { + panic("could not find post with specified id") + } + + if p.author != caller { + panic("you are not the author of this post!") + } + + posts = append(posts[:idx], posts[idx+1:]...) +} + +func Upvote(id uint) { + _, p := getPost(id) + if p == nil { + panic("could not find post with specified id") + } + + p.upvotes++ +} + +func Downvote(id uint) { + _, p := getPost(id) + if p == nil { + panic("could not find post with specified id") + } + + p.downvotes++ +} + +func TipPost(id uint) { + tipAmt := std.GetOrigSend().AmountOf("ugnot") + if tipAmt <= 0 { + panic("cannot tip 0 or less!") + } + + _, p := getPost(id) + if p == nil { + panic("could not find post with specified id") + } + + banker := std.GetBanker(std.BankerTypeOrigSend) + + coinTip := std.NewCoin("ugnot", tipAmt) + banker.SendCoins(std.CurrentRealm().Addr(), p.author, std.NewCoins(coinTip)) + + p.tipTotal += tipAmt +} + +func getPost(id uint) (int, *Post) { + for i, p := range posts { + if p.id == id { + return i, p + } + } + + return -1, nil +} + + diff --git a/presentations/2024-08-05--web3kamp--leon/phases/p1/README.md b/presentations/2024-08-05--web3kamp--leon/phases/p1/README.md new file mode 100644 index 0000000..0d78474 --- /dev/null +++ b/presentations/2024-08-05--web3kamp--leon/phases/p1/README.md @@ -0,0 +1,45 @@ +# Web3 Kamp - P1 - Local Installation + +Follow the [installation steps](https://docs.gno.land/getting-started/local-setup/installation) +to install the tools necessary to complete the workshop. First verify the prerequisite requirements +are installed before installing `gno`, `gnodev`, and `gnokey`. + +## Syntax highlighting + +This step is optional but convenient. We have a few [supported editor extensions](https://github.com/gnolang/gno/blob/master/CONTRIBUTING.md#environment) +that enable gno syntax highlighting, including VSCode, ViM, emacs, and Sublime. + +## gnokey - the CLI wallet + +Let's generate a key pair. A key pair is what is used to sign transactions that are broadcast +to the gno.land blockchain. In a real world context, they should not be shared. + +### add + +Add a new key by running `gnokey add `. Choose whichever key name you'd like. Shorter is better +since you'll have to type it at least a few times. A passphrase is optional and in our case unnecessary, so +you can enter through this without typing anything. + +Notice the mnemonic phrase that is generated. In a real world scenario, you would want to record this +and store it offline, ideally on a piece of paper or other method of offline storage for security. + +### list + +Run `gnokey list`. You should see that a key has been added with the specified name. + +## gnodev + +`gnodev` is a tool to more easily facilitate development on gno.land. It's basic features include: + +- spinning up an in-memory node +- automatically deploying local packages to the chain +- reloading packages when file changes are made +- starting a web server using `gnoweb` to provide a UI + +From this directory, try running `gnodev .`. If successful, the last line should be `` --- READY ┃ I for commands and help, press `h` ``. + +## Setup complete! + +You've just set up a local gno.land development environment 🎉 + +To see the Hello World example in action, visit [localhost:8888/r/petnica/hello](http://localhost:8888/r/petnica/hello). diff --git a/presentations/2024-08-05--web3kamp--leon/phases/p1/gno.mod b/presentations/2024-08-05--web3kamp--leon/phases/p1/gno.mod new file mode 100644 index 0000000..64ba999 --- /dev/null +++ b/presentations/2024-08-05--web3kamp--leon/phases/p1/gno.mod @@ -0,0 +1 @@ +module gno.land/r/petnica/hello diff --git a/presentations/2024-08-05--web3kamp--leon/phases/p1/hello.gno b/presentations/2024-08-05--web3kamp--leon/phases/p1/hello.gno new file mode 100644 index 0000000..1089632 --- /dev/null +++ b/presentations/2024-08-05--web3kamp--leon/phases/p1/hello.gno @@ -0,0 +1,15 @@ +package hello + +var msg string + +func init() { + msg = "Hello World!" +} + +func UpdateMsg(newMsg string) { + msg = newMsg +} + +func Render(_ string) string { + return msg +} \ No newline at end of file diff --git a/presentations/2024-08-05--web3kamp--leon/phases/p2/README.md b/presentations/2024-08-05--web3kamp--leon/phases/p2/README.md new file mode 100644 index 0000000..41a8294 --- /dev/null +++ b/presentations/2024-08-05--web3kamp--leon/phases/p2/README.md @@ -0,0 +1,7 @@ +# Web3 Kamp - P2 - Adding & RemovingPosts + +1. Run `gnodev` in this folder with `gnodev .`. +2. Check out the code comments in `twitter.gno` for TODO steps! +3. View the app on [localhost:8888/r/petnica/twitter](http://localhost:8888/r/petnica/twitter). +4. Check out the docs at [docs.gno.land](https://docs.gno.land)! +5. When you finish, try posting and removing a post! diff --git a/presentations/2024-08-05--web3kamp--leon/phases/p2/gno.mod b/presentations/2024-08-05--web3kamp--leon/phases/p2/gno.mod new file mode 100644 index 0000000..3ed8657 --- /dev/null +++ b/presentations/2024-08-05--web3kamp--leon/phases/p2/gno.mod @@ -0,0 +1 @@ +module gno.land/r/petnica/twitter diff --git a/presentations/2024-08-05--web3kamp--leon/phases/p2/render.gno b/presentations/2024-08-05--web3kamp--leon/phases/p2/render.gno new file mode 100644 index 0000000..4b429fa --- /dev/null +++ b/presentations/2024-08-05--web3kamp--leon/phases/p2/render.gno @@ -0,0 +1,24 @@ +package twitter + +import ( + "time" + + "gno.land/p/demo/ufmt" +) + +func (p Post) String() string { + return ufmt.Sprintf("Text: %s\n\nAuthor: %s\n\n Time: %s", p.text, p.author.String(), p.createdAt.Format(time.RFC822)) +} + +func Render(_ string) string { + if len(posts) == 0 { + return "No posts currently." + } + + output := "" + for _, post := range posts { + output += ufmt.Sprintf("### Post #%d\n\n%s\n\n", post.id, post.String()) + } + + return output +} \ No newline at end of file diff --git a/presentations/2024-08-05--web3kamp--leon/phases/p2/twitter.gno b/presentations/2024-08-05--web3kamp--leon/phases/p2/twitter.gno new file mode 100644 index 0000000..fdf6e8a --- /dev/null +++ b/presentations/2024-08-05--web3kamp--leon/phases/p2/twitter.gno @@ -0,0 +1,27 @@ +package twitter + +import ( + "std" + "time" +) + +var ( + posts []*Post + idCounter uint +) + +type Post struct { + id uint + + text string + author std.Address + createdAt time.Time +} + +// TODO: Create an exported function to add a new post +// The function should add a pointer to a newly created post to the posts slice +// Use the idCounter variable to assign an ID to a new post + +// TODO: Create an exported function to remove a post by ID +// Only the author of the post should be able to remove it +// panic if the caller is not the author! diff --git a/presentations/2024-08-05--web3kamp--leon/phases/p3/README.md b/presentations/2024-08-05--web3kamp--leon/phases/p3/README.md new file mode 100644 index 0000000..82acdd5 --- /dev/null +++ b/presentations/2024-08-05--web3kamp--leon/phases/p3/README.md @@ -0,0 +1,6 @@ +# Web3 Kamp - P3 -Upvotes & Downvotes + +1. Run `gnodev` in this folder with `gnodev .`. +2. Check out the code comments in all files for TODO steps. +3. When you finish, try posting, upvoting, and downvoting a post! +4. BONUS: How can we prevent double voting? diff --git a/presentations/2024-08-05--web3kamp--leon/phases/p3/gno.mod b/presentations/2024-08-05--web3kamp--leon/phases/p3/gno.mod new file mode 100644 index 0000000..3ed8657 --- /dev/null +++ b/presentations/2024-08-05--web3kamp--leon/phases/p3/gno.mod @@ -0,0 +1 @@ +module gno.land/r/petnica/twitter diff --git a/presentations/2024-08-05--web3kamp--leon/phases/p3/render.gno b/presentations/2024-08-05--web3kamp--leon/phases/p3/render.gno new file mode 100644 index 0000000..c1914d0 --- /dev/null +++ b/presentations/2024-08-05--web3kamp--leon/phases/p3/render.gno @@ -0,0 +1,25 @@ +package twitter + +import ( + "time" + + "gno.land/p/demo/ufmt" +) + +// TODO: Update this function to show upvotes and downvotes +func (p Post) String() string { + return ufmt.Sprintf("Text: %s\n\nAuthor: %s\n\n Time: %s", p.text, p.author.String(), p.createdAt.Format(time.RFC822)) +} + +func Render(_ string) string { + if len(posts) == 0 { + return "No posts currently." + } + + output := "" + for _, post := range posts { + output += ufmt.Sprintf("### Post #%d\n\n%s\n\n", post.id, post.String()) + } + + return output +} \ No newline at end of file diff --git a/presentations/2024-08-05--web3kamp--leon/phases/p3/twitter.gno b/presentations/2024-08-05--web3kamp--leon/phases/p3/twitter.gno new file mode 100644 index 0000000..d0db061 --- /dev/null +++ b/presentations/2024-08-05--web3kamp--leon/phases/p3/twitter.gno @@ -0,0 +1,66 @@ +package twitter + +import ( + "std" + "time" +) + +var ( + posts []*Post + idCounter uint +) + +type Post struct { + id uint + + text string + author std.Address + createdAt time.Time + // TODO: Add upvotes & downvotes +} + +func AddPost(text string) uint { + if text == "" { + panic("post text cannot be empty") + } + + p := &Post{ + text: text, + id: idCounter, + author: std.PrevRealm().Addr(), + createdAt: time.Now(), + } + + posts = append(posts, p) + idCounter++ + + return p.id +} + +func RemovePost(id uint) { + caller := std.PrevRealm().Addr() + + idx, p := getPost(id) + if p == nil { + panic("could not find post with specified id") + } + + if p.author != caller { + panic("you are not the author of this post!") + } + + posts = append(posts[:idx], posts[idx+1:]...) +} + +// TODO: Add an exported Upvote function +// TODO: Add an exported Downvote function + +func getPost(id uint) (int, *Post) { + for i, p := range posts { + if p.id == id { + return i, p + } + } + + return -1, nil +} diff --git a/presentations/2024-08-05--web3kamp--leon/phases/p4/README.md b/presentations/2024-08-05--web3kamp--leon/phases/p4/README.md new file mode 100644 index 0000000..0bab00b --- /dev/null +++ b/presentations/2024-08-05--web3kamp--leon/phases/p4/README.md @@ -0,0 +1,6 @@ +# Web3 Kamp - P4 - Tipping a post with `ugnot` + +1. Run `gnodev` in this folder with `gnodev .`. +2. Check out the code comments in all files for TODO steps. +3. Hint: check out the Banker, Coins, and `GetOrigSend` in the gno.land documentation. +4. When you finish, try posting and tipping a post! diff --git a/presentations/2024-08-05--web3kamp--leon/phases/p4/gno.mod b/presentations/2024-08-05--web3kamp--leon/phases/p4/gno.mod new file mode 100644 index 0000000..3ed8657 --- /dev/null +++ b/presentations/2024-08-05--web3kamp--leon/phases/p4/gno.mod @@ -0,0 +1 @@ +module gno.land/r/petnica/twitter diff --git a/presentations/2024-08-05--web3kamp--leon/phases/p4/render.gno b/presentations/2024-08-05--web3kamp--leon/phases/p4/render.gno new file mode 100644 index 0000000..9cad019 --- /dev/null +++ b/presentations/2024-08-05--web3kamp--leon/phases/p4/render.gno @@ -0,0 +1,25 @@ +package twitter + +import ( + "time" + + "gno.land/p/demo/ufmt" +) + +// TODO: Update to show total tips the post has received +func (p Post) String() string { + return ufmt.Sprintf("Text: %s\n\nAuthor: %s\n\n Time: %s\n\nUp: %d, Down: %d", p.text, p.author.String(), p.createdAt.Format(time.RFC822), p.upvotes, p.downvotes) +} + +func Render(_ string) string { + if len(posts) == 0 { + return "No posts currently." + } + + output := "" + for _, post := range posts { + output += ufmt.Sprintf("### Post #%d\n\n%s\n\n", post.id, post.String()) + } + + return output +} \ No newline at end of file diff --git a/presentations/2024-08-05--web3kamp--leon/phases/p4/twitter.gno b/presentations/2024-08-05--web3kamp--leon/phases/p4/twitter.gno new file mode 100644 index 0000000..727be9c --- /dev/null +++ b/presentations/2024-08-05--web3kamp--leon/phases/p4/twitter.gno @@ -0,0 +1,88 @@ +package twitter + +import ( + "std" + "time" +) + +var ( + posts []*Post + idCounter uint +) + +type Post struct { + id uint + + text string + author std.Address + createdAt time.Time + upvotes uint + downvotes uint + // TODO: add a total tips counter to measure how many tips the post has received +} + +func AddPost(text string) uint { + if text == "" { + panic("post text cannot be empty") + } + + p := &Post{ + text: text, + id: idCounter, + author: std.PrevRealm().Addr(), + createdAt: time.Now(), + upvotes: 0, + downvotes: 0, + } + + posts = append(posts, p) + idCounter++ + + return p.id +} + +func RemovePost(id uint) { + caller := std.PrevRealm().Addr() + + idx, p := getPost(id) + if p == nil { + panic("could not find post with specified id") + } + + if p.author != caller { + panic("you are not the author of this post!") + } + + posts = append(posts[:idx], posts[idx+1:]...) +} + +func Upvote(id uint) { + _, p := getPost(id) + if p == nil { + panic("could not find post with specified id") + } + + p.upvotes++ +} + +func Downvote(id uint) { + _, p := getPost(id) + if p == nil { + panic("could not find post with specified id") + } + + p.downvotes++ +} + +// TODO: Add an exported TipPost function +// The function will allow a user to tip a post with ugnot + +func getPost(id uint) (int, *Post) { + for i, p := range posts { + if p.id == id { + return i, p + } + } + + return -1, nil +}