From f7c67ab2da3d367ffddc197c6aa9806c8019b819 Mon Sep 17 00:00:00 2001
From: moul
Date: Tue, 22 Oct 2024 14:04:52 +0000
Subject: [PATCH] chore: update portal-loop backup
---
portal-loop/README.md | 2 +-
portal-loop/backup_portal_loop_txs.jsonl | 4 +-
portal-loop/extracted/r/stefann/home/home.gno | 207 ++++++-------
.../extracted/r/stefann/home/home_test.gno | 291 ++++++++++++++++++
.../r/stefann/registry/pkg_metadata.json | 1 +
.../extracted/r/stefann/registry/registry.gno | 51 +++
6 files changed, 451 insertions(+), 105 deletions(-)
create mode 100644 portal-loop/extracted/r/stefann/home/home_test.gno
create mode 100644 portal-loop/extracted/r/stefann/registry/pkg_metadata.json
create mode 100644 portal-loop/extracted/r/stefann/registry/registry.gno
diff --git a/portal-loop/README.md b/portal-loop/README.md
index 93874e3b..89e6b858 100644
--- a/portal-loop/README.md
+++ b/portal-loop/README.md
@@ -2,7 +2,7 @@
## TXs
```
-5890
+5892
```
## addpkgs
diff --git a/portal-loop/backup_portal_loop_txs.jsonl b/portal-loop/backup_portal_loop_txs.jsonl
index 3b06c7a5..3c9167e9 100755
--- a/portal-loop/backup_portal_loop_txs.jsonl
+++ b/portal-loop/backup_portal_loop_txs.jsonl
@@ -118,6 +118,8 @@
{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"present","path":"gno.land/r/manfred/present","files":[{"name":"admin.gno","body":"package present\n\nimport (\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n)\n\nvar (\n\tadminAddr std.Address\n\tmoderatorList avl.Tree\n\tinPause bool\n)\n\nfunc init() {\n\t// adminAddr = std.GetOrigCaller() // FIXME: find a way to use this from the main's genesis.\n\tadminAddr = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\"\n}\n\nfunc AdminSetAdminAddr(addr std.Address) {\n\tassertIsAdmin()\n\tadminAddr = addr\n}\n\nfunc AdminSetInPause(state bool) {\n\tassertIsAdmin()\n\tinPause = state\n}\n\nfunc AdminAddModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), true)\n}\n\nfunc AdminRemoveModerator(addr std.Address) {\n\tassertIsAdmin()\n\tmoderatorList.Set(addr.String(), false) // XXX: delete instead?\n}\n\nfunc ModAddPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\tcaller := std.GetOrigCaller()\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.NewPost(caller, slug, title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc ModEditPost(slug, title, body, publicationDate, authors, tags string) {\n\tassertIsModerator()\n\n\ttagList := strings.Split(tags, \",\")\n\tauthorList := strings.Split(authors, \",\")\n\n\terr := b.GetPost(slug).Update(title, body, publicationDate, authorList, tagList)\n\tcheckErr(err)\n}\n\nfunc isAdmin(addr std.Address) bool {\n\treturn addr == adminAddr\n}\n\nfunc isModerator(addr std.Address) bool {\n\t_, found := moderatorList.Get(addr.String())\n\treturn found\n}\n\nfunc assertIsAdmin() {\n\tcaller := std.GetOrigCaller()\n\tif !isAdmin(caller) {\n\t\tpanic(\"access restricted.\")\n\t}\n}\n\nfunc assertIsModerator() {\n\tcaller := std.GetOrigCaller()\n\tif isAdmin(caller) || isModerator(caller) {\n\t\treturn\n\t}\n\tpanic(\"access restricted\")\n}\n\nfunc assertNotInPause() {\n\tif inPause {\n\t\tpanic(\"access restricted (pause)\")\n\t}\n}\n\nfunc checkErr(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n"},{"name":"present_miami23.gno","body":"package present\n\nfunc init() {\n\tpath := \"miami23\"\n\ttitle := \"Portal Loop Demo (Miami 2023)\"\n\tbody := `\nRendered by Gno.\n\n[Source (WIP)](https://github.com/gnolang/gno/pull/1176)\n\n## Portal Loop\n\n- DONE: Dynamic homepage, key pages, aliases, and redirects.\n- TODO: Deploy with history, complete worxdao v0.\n- Will replace the static gno.land site.\n- Enhances local development.\n\n[GitHub Issue](https://github.com/gnolang/gno/issues/1108)\n\n## Roadmap\n\n- Crafting the roadmap this week, open to collaboration.\n- Combining onchain (portal loop) and offchain (GitHub).\n- Next week: Unveiling the official v0 roadmap.\n\n## Teams, DAOs, Projects\n\n- Developing worxDAO contracts for directories of projects and teams.\n- GitHub teams and projects align with this structure.\n- CODEOWNER file updates coming.\n- Initial teams announced next week.\n\n## Tech Team Retreat Plan\n\n- Continue Portal Loop.\n- Consider dApp development.\n- Explore new topics [here](https://github.com/orgs/gnolang/projects/15/).\n- Engage in workshops.\n- Connect and have fun with colleagues.\n`\n\t_ = b.NewPost(adminAddr, path, title, body, \"2023-10-15T13:17:24Z\", []string{\"moul\"}, []string{\"demo\", \"portal-loop\", \"miami\"})\n}\n"},{"name":"present_miami23_filetest.gno","body":"package main\n\nimport (\n\t\"gno.land/r/manfred/present\"\n)\n\nfunc main() {\n\tprintln(present.Render(\"\"))\n\tprintln(\"------------------------------------\")\n\tprintln(present.Render(\"p/miami23\"))\n}\n"},{"name":"presentations.gno","body":"package present\n\nimport (\n\t\"gno.land/p/demo/blog\"\n)\n\n// TODO: switch from p/blog to p/present\n\nvar b = \u0026blog.Blog{\n\tTitle: \"Manfred's Presentations\",\n\tPrefix: \"/r/manfred/present:\",\n\tNoBreadcrumb: true,\n}\n\nfunc Render(path string) string {\n\treturn b.Render(path)\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}
{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"guestbook","path":"gno.land/r/morgan/guestbook","files":[{"name":"admin.gno","body":"package guestbook\n\nimport (\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/seqid\"\n)\n\nvar owner = ownable.New()\n\n// AdminDelete removes the guestbook message with the given ID.\n// The user will still be marked as having submitted a message, so they\n// won't be able to re-submit a new message.\nfunc AdminDelete(signatureID string) {\n\towner.AssertCallerIsOwner()\n\n\tid, err := seqid.FromString(signatureID)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tidb := id.Binary()\n\tif !guestbook.Has(idb) {\n\t\tpanic(\"signature does not exist\")\n\t}\n\tguestbook.Remove(idb)\n}\n"},{"name":"guestbook.gno","body":"// Realm guestbook contains an implementation of a simple guestbook.\n// Come and sign yourself up!\npackage guestbook\n\nimport (\n\t\"std\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/seqid\"\n)\n\n// Signature is a single entry in the guestbook.\ntype Signature struct {\n\tMessage string\n\tAuthor std.Address\n\tTime time.Time\n}\n\nconst (\n\tmaxMessageLength = 140\n\tmaxPerPage = 25\n)\n\nvar (\n\tsignatureID seqid.ID\n\tguestbook avl.Tree // id -\u003e Signature\n\thasSigned avl.Tree // address -\u003e struct{}\n)\n\nfunc init() {\n\tSign(\"You reached the end of the guestbook!\")\n}\n\nconst (\n\terrNotAUser = \"this guestbook can only be signed by users\"\n\terrAlreadySigned = \"you already signed the guestbook!\"\n\terrInvalidCharacterInMessage = \"invalid character in message\"\n)\n\n// Sign signs the guestbook, with the specified message.\nfunc Sign(message string) {\n\tprev := std.PrevRealm()\n\tswitch {\n\tcase !prev.IsUser():\n\t\tpanic(errNotAUser)\n\tcase hasSigned.Has(prev.Addr().String()):\n\t\tpanic(errAlreadySigned)\n\t}\n\tmessage = validateMessage(message)\n\n\tguestbook.Set(signatureID.Next().Binary(), Signature{\n\t\tMessage: message,\n\t\tAuthor: prev.Addr(),\n\t\t// NOTE: time.Now() will yield the \"block time\", which is deterministic.\n\t\tTime: time.Now(),\n\t})\n\thasSigned.Set(prev.Addr().String(), struct{}{})\n}\n\nfunc validateMessage(msg string) string {\n\tif len(msg) \u003e maxMessageLength {\n\t\tpanic(\"Keep it brief! (max \" + strconv.Itoa(maxMessageLength) + \" bytes!)\")\n\t}\n\tout := \"\"\n\tfor _, ch := range msg {\n\t\tswitch {\n\t\tcase unicode.IsLetter(ch),\n\t\t\tunicode.IsNumber(ch),\n\t\t\tunicode.IsSpace(ch),\n\t\t\tunicode.IsPunct(ch):\n\t\t\tout += string(ch)\n\t\tdefault:\n\t\t\tpanic(errInvalidCharacterInMessage)\n\t\t}\n\t}\n\treturn out\n}\n\nfunc Render(maxID string) string {\n\tvar bld strings.Builder\n\n\tbld.WriteString(\"# Guestbook 📝\\n\\n[Come sign the guestbook!](./guestbook?help\u0026__func=Sign)\\n\\n---\\n\\n\")\n\n\tvar maxIDBinary string\n\tif maxID != \"\" {\n\t\tmid, err := seqid.FromString(maxID)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\t// AVL iteration is exclusive, so we need to decrease the ID value to get the \"true\" maximum.\n\t\tmid--\n\t\tmaxIDBinary = mid.Binary()\n\t}\n\n\tvar lastID seqid.ID\n\tvar printed int\n\tguestbook.ReverseIterate(\"\", maxIDBinary, func(key string, val interface{}) bool {\n\t\tsig := val.(Signature)\n\t\tmessage := strings.ReplaceAll(sig.Message, \"\\n\", \"\\n\u003e \")\n\t\tbld.WriteString(\"\u003e \" + message + \"\\n\u003e\\n\")\n\t\tidValue, ok := seqid.FromBinary(key)\n\t\tif !ok {\n\t\t\tpanic(\"invalid seqid id\")\n\t\t}\n\n\t\tbld.WriteString(\"\u003e _Written by \" + sig.Author.String() + \" at \" + sig.Time.Format(time.DateTime) + \"_ (#\" + idValue.String() + \")\\n\\n---\\n\\n\")\n\t\tlastID = idValue\n\n\t\tprinted++\n\t\t// stop after exceeding limit\n\t\treturn printed \u003e= maxPerPage\n\t})\n\n\tif printed == 0 {\n\t\tbld.WriteString(\"No messages!\")\n\t} else if printed \u003e= maxPerPage {\n\t\tbld.WriteString(\"\u003cp style='text-align:right'\u003e\u003ca href='./guestbook:\" + lastID.String() + \"'\u003eNext page\u003c/a\u003e\u003c/p\u003e\")\n\t}\n\n\treturn bld.String()\n}\n"},{"name":"guestbook_test.gno","body":"package guestbook\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n)\n\nfunc TestSign(t *testing.T) {\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user\"))\n\tSign(\"Hello!\")\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user2\"))\n\tSign(\"Hello2!\")\n\n\tres := Render(\"\")\n\tt.Log(res)\n\tif !strings.Contains(res, \"\u003e Hello!\\n\u003e\\n\u003e _Written by g1user \") {\n\t\tt.Error(\"does not contain first user's message\")\n\t}\n\tif !strings.Contains(res, \"\u003e Hello2!\\n\u003e\\n\u003e _Written by g1user2 \") {\n\t\tt.Error(\"does not contain second user's message\")\n\t}\n\tif guestbook.Size() != 2 {\n\t\tt.Error(\"invalid guestbook size\")\n\t}\n}\n\nfunc TestSign_FromRealm(t *testing.T) {\n\tstd.TestSetRealm(std.NewCodeRealm(\"gno.land/r/demo/users\"))\n\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec == nil {\n\t\t\tt.Fatal(\"expected panic\")\n\t\t}\n\t\trecString, ok := rec.(string)\n\t\tif !ok {\n\t\t\tt.Fatal(\"not a string\", rec)\n\t\t} else if recString != errNotAUser {\n\t\t\tt.Fatal(\"invalid error\", recString)\n\t\t}\n\t}()\n\tSign(\"Hey!\")\n}\n\nfunc TestSign_Double(t *testing.T) {\n\t// Should not allow signing twice.\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user\"))\n\tSign(\"Hello!\")\n\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec == nil {\n\t\t\tt.Fatal(\"expected panic\")\n\t\t}\n\t\trecString, ok := rec.(string)\n\t\tif !ok {\n\t\t\tt.Error(\"type assertion failed\", rec)\n\t\t} else if recString != errAlreadySigned {\n\t\t\tt.Error(\"invalid error message\", recString)\n\t\t}\n\t}()\n\n\tSign(\"Hello again!\")\n}\n\nfunc TestSign_InvalidMessage(t *testing.T) {\n\t// Should not allow control characters in message.\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\n\tstd.TestSetRealm(std.NewUserRealm(\"g1user\"))\n\n\tdefer func() {\n\t\trec := recover()\n\t\tif rec == nil {\n\t\t\tt.Fatal(\"expected panic\")\n\t\t}\n\t\trecString, ok := rec.(string)\n\t\tif !ok {\n\t\t\tt.Error(\"type assertion failed\", rec)\n\t\t} else if recString != errInvalidCharacterInMessage {\n\t\t\tt.Error(\"invalid error message\", recString)\n\t\t}\n\t}()\n\tSign(\"\\x00Hello!\")\n}\n\nfunc TestAdminDelete(t *testing.T) {\n\tconst (\n\t\tuserAddr std.Address = \"g1user\"\n\t\tadminAddr std.Address = \"g1admin\"\n\t)\n\n\tguestbook = avl.Tree{}\n\thasSigned = avl.Tree{}\n\towner = ownable.NewWithAddress(adminAddr)\n\tsignatureID = 0\n\n\tstd.TestSetRealm(std.NewUserRealm(userAddr))\n\n\tconst bad = \"Very Bad Message! Nyeh heh heh!\"\n\tSign(bad)\n\n\tif rnd := Render(\"\"); !strings.Contains(rnd, bad) {\n\t\tt.Fatal(\"render does not contain bad message\", rnd)\n\t}\n\n\tstd.TestSetRealm(std.NewUserRealm(adminAddr))\n\tAdminDelete(signatureID.String())\n\n\tif rnd := Render(\"\"); strings.Contains(rnd, bad) {\n\t\tt.Error(\"render contains bad message\", rnd)\n\t}\n\tif guestbook.Size() != 0 {\n\t\tt.Error(\"invalid guestbook size\")\n\t}\n\tif hasSigned.Size() != 1 {\n\t\tt.Error(\"invalid hasSigned size\")\n\t}\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}
{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"home","path":"gno.land/r/morgan/home","files":[{"name":"home.gno","body":"package home\n\nconst staticHome = `# morgan's (gn)home\n\n- [📝 sign my guestbook](/r/morgan/guestbook)\n`\n\nfunc Render(path string) string {\n\treturn staticHome\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}
+{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"registry","path":"gno.land/r/stefann/registry","files":[{"name":"registry.gno","body":"package registry\n\nimport (\n\t\"errors\"\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable\"\n)\n\nvar (\n\tmainAddr std.Address\n\tbackupAddr std.Address\n\towner *ownable.Ownable\n)\n\nfunc init() {\n\tmainAddr = \"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\"\n\tbackupAddr = \"g13awn2575t8s2vf3svlprc4dg0e9z5wchejdxk8\"\n\n\towner = ownable.NewWithAddress(mainAddr)\n}\n\nfunc MainAddr() std.Address {\n\treturn mainAddr\n}\n\nfunc BackupAddr() std.Address {\n\treturn backupAddr\n}\n\nfunc SetMainAddr(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn errors.New(\"config: invalid address\")\n\t}\n\n\towner.AssertCallerIsOwner()\n\n\tmainAddr = addr\n\treturn nil\n}\n\nfunc SetBackupAddr(addr std.Address) error {\n\tif !addr.IsValid() {\n\t\treturn errors.New(\"config: invalid address\")\n\t}\n\n\towner.AssertCallerIsOwner()\n\n\tbackupAddr = addr\n\treturn nil\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}
+{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"home","path":"gno.land/r/stefann/home","files":[{"name":"home.gno","body":"package home\n\nimport (\n\t\"sort\"\n\t\"std\"\n\t\"strings\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/p/demo/ufmt\"\n\n\t\"gno.land/r/stefann/registry\"\n)\n\ntype City struct {\n\tName string\n\tURL string\n}\n\ntype Sponsor struct {\n\tAddress std.Address\n\tAmount std.Coins\n}\n\ntype Profile struct {\n\tpfp string\n\taboutMe []string\n}\n\ntype Travel struct {\n\tcities []City\n\tcurrentCityIndex int\n\tjarLink string\n}\n\ntype Sponsorship struct {\n\tmaxSponsors int\n\tsponsors *avl.Tree\n\tDonationsCount int\n\tsponsorsCount int\n}\n\nvar (\n\tprofile Profile\n\ttravel Travel\n\tsponsorship Sponsorship\n\towner *ownable.Ownable\n)\n\nfunc init() {\n\towner = ownable.NewWithAddress(registry.MainAddr())\n\n\tprofile = Profile{\n\t\tpfp: \"https://i.ibb.co/Bc5YNCx/DSC-0095a.jpg\",\n\t\taboutMe: []string{\n\t\t\t`### About Me`,\n\t\t\t`Hey there! I’m Stefan, a student of Computer Science. I’m all about exploring and adventure — whether it’s diving into the latest tech or discovering a new city, I’m always up for the challenge!`,\n\n\t\t\t`### Contributions`,\n\t\t\t`I'm just getting started, but you can follow my journey through Gno.land right [here](https://github.com/gnolang/hackerspace/issues/94) 🔗`,\n\t\t},\n\t}\n\n\ttravel = Travel{\n\t\tcities: []City{\n\t\t\t{Name: \"Venice\", URL: \"https://i.ibb.co/1mcZ7b1/venice.jpg\"},\n\t\t\t{Name: \"Tokyo\", URL: \"https://i.ibb.co/wNDJv3H/tokyo.jpg\"},\n\t\t\t{Name: \"São Paulo\", URL: \"https://i.ibb.co/yWMq2Sn/sao-paulo.jpg\"},\n\t\t\t{Name: \"Toronto\", URL: \"https://i.ibb.co/pb95HJB/toronto.jpg\"},\n\t\t\t{Name: \"Bangkok\", URL: \"https://i.ibb.co/pQy3w2g/bangkok.jpg\"},\n\t\t\t{Name: \"New York\", URL: \"https://i.ibb.co/6JWLm0h/new-york.jpg\"},\n\t\t\t{Name: \"Paris\", URL: \"https://i.ibb.co/q9vf6Hs/paris.jpg\"},\n\t\t\t{Name: \"Kandersteg\", URL: \"https://i.ibb.co/60DzywD/kandersteg.jpg\"},\n\t\t\t{Name: \"Rothenburg\", URL: \"https://i.ibb.co/cr8d2rQ/rothenburg.jpg\"},\n\t\t\t{Name: \"Capetown\", URL: \"https://i.ibb.co/bPGn0v3/capetown.jpg\"},\n\t\t\t{Name: \"Sydney\", URL: \"https://i.ibb.co/TBNzqfy/sydney.jpg\"},\n\t\t\t{Name: \"Oeschinen Lake\", URL: \"https://i.ibb.co/QJQwp2y/oeschinen-lake.jpg\"},\n\t\t\t{Name: \"Barra Grande\", URL: \"https://i.ibb.co/z4RXKc1/barra-grande.jpg\"},\n\t\t\t{Name: \"London\", URL: \"https://i.ibb.co/CPGtvgr/london.jpg\"},\n\t\t},\n\t\tcurrentCityIndex: 0,\n\t\tjarLink: \"https://TODO\", // This value should be injected through UpdateJarLink after deployment.\n\t}\n\n\tsponsorship = Sponsorship{\n\t\tmaxSponsors: 5,\n\t\tsponsors: avl.NewTree(),\n\t\tDonationsCount: 0,\n\t\tsponsorsCount: 0,\n\t}\n}\n\nfunc UpdateCities(newCities []City) {\n\towner.AssertCallerIsOwner()\n\ttravel.cities = newCities\n}\n\nfunc AddCities(newCities ...City) {\n\towner.AssertCallerIsOwner()\n\n\ttravel.cities = append(travel.cities, newCities...)\n}\n\nfunc UpdateJarLink(newLink string) {\n\towner.AssertCallerIsOwner()\n\ttravel.jarLink = newLink\n}\n\nfunc UpdatePFP(url string) {\n\towner.AssertCallerIsOwner()\n\tprofile.pfp = url\n}\n\nfunc UpdateAboutMe(aboutMeStr string) {\n\towner.AssertCallerIsOwner()\n\tprofile.aboutMe = strings.Split(aboutMeStr, \"|\")\n}\n\nfunc AddAboutMeRows(newRows ...string) {\n\towner.AssertCallerIsOwner()\n\n\tprofile.aboutMe = append(profile.aboutMe, newRows...)\n}\n\nfunc UpdateMaxSponsors(newMax int) {\n\towner.AssertCallerIsOwner()\n\tif newMax \u003c= 0 {\n\t\tpanic(\"maxSponsors must be greater than zero\")\n\t}\n\tsponsorship.maxSponsors = newMax\n}\n\nfunc Donate() {\n\taddress := std.GetOrigCaller()\n\tamount := std.GetOrigSend()\n\n\tif amount.AmountOf(\"ugnot\") == 0 {\n\t\tpanic(\"Donation must include GNOT\")\n\t}\n\n\texistingAmount, exists := sponsorship.sponsors.Get(address.String())\n\tif exists {\n\t\tupdatedAmount := existingAmount.(std.Coins).Add(amount)\n\t\tsponsorship.sponsors.Set(address.String(), updatedAmount)\n\t} else {\n\t\tsponsorship.sponsors.Set(address.String(), amount)\n\t\tsponsorship.sponsorsCount++\n\t}\n\n\ttravel.currentCityIndex++\n\tsponsorship.DonationsCount++\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\townerAddr := registry.MainAddr()\n\tbanker.SendCoins(std.CurrentRealm().Addr(), ownerAddr, banker.GetCoins(std.CurrentRealm().Addr()))\n}\n\ntype SponsorSlice []Sponsor\n\nfunc (s SponsorSlice) Len() int {\n\treturn len(s)\n}\n\nfunc (s SponsorSlice) Less(i, j int) bool {\n\treturn s[i].Amount.AmountOf(\"ugnot\") \u003e s[j].Amount.AmountOf(\"ugnot\")\n}\n\nfunc (s SponsorSlice) Swap(i, j int) {\n\ts[i], s[j] = s[j], s[i]\n}\n\nfunc GetTopSponsors() []Sponsor {\n\tvar sponsorSlice SponsorSlice\n\n\tsponsorship.sponsors.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\taddr := std.Address(key)\n\t\tamount := value.(std.Coins)\n\t\tsponsorSlice = append(sponsorSlice, Sponsor{Address: addr, Amount: amount})\n\t\treturn false\n\t})\n\n\tsort.Sort(sponsorSlice)\n\treturn sponsorSlice\n}\n\nfunc GetTotalDonations() int {\n\ttotal := 0\n\tsponsorship.sponsors.Iterate(\"\", \"\", func(key string, value interface{}) bool {\n\t\ttotal += int(value.(std.Coins).AmountOf(\"ugnot\"))\n\t\treturn false\n\t})\n\treturn total\n}\n\nfunc Render(path string) string {\n\tout := ufmt.Sprintf(\"# Exploring %s!\\n\\n\", travel.cities[travel.currentCityIndex].Name)\n\n\tout += renderAboutMe()\n\tout += \"\\n\\n\"\n\tout += renderTips()\n\n\treturn out\n}\n\nfunc renderAboutMe() string {\n\tout := \"\u003cdiv class='rows-3'\u003e\"\n\n\tout += \"\u003cdiv style='position: relative; text-align: center;'\u003e\\n\\n\"\n\n\tout += ufmt.Sprintf(\"\u003cdiv style='background-image: url(%s); background-size: cover; background-position: center; width: 100%%; height: 600px; position: relative; border-radius: 15px; overflow: hidden;'\u003e\\n\\n\", travel.cities[travel.currentCityIndex%len(travel.cities)].URL)\n\n\tout += ufmt.Sprintf(\"\u003cimg src='%s' alt='my profile pic' style='width: 250px; height: auto; aspect-ratio: 1 / 1; object-fit: cover; border-radius: 50%%; border: 3px solid #1e1e1e; position: absolute; top: 75%%; left: 50%%; transform: translate(-50%%, -50%%);'\u003e\\n\\n\", profile.pfp)\n\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tfor _, rows := range profile.aboutMe {\n\t\tout += \"\u003cdiv\u003e\\n\\n\"\n\t\tout += rows + \"\\n\\n\"\n\t\tout += \"\u003c/div\u003e\\n\\n\"\n\t}\n\n\tout += \"\u003c/div\u003e\u003c!-- /rows-3 --\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderTips() string {\n\tout := `\u003cdiv class=\"jumbotron\" style=\"display: flex; flex-direction: column; justify-content: flex-start; align-items: center; padding-top: 40px; padding-bottom: 50px; text-align: center;\"\u003e` + \"\\n\\n\"\n\n\tout += `\u003cdiv class=\"rows-2\" style=\"max-width: 500px; width: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center;\"\u003e` + \"\\n\"\n\n\tout += `\u003ch1 style=\"margin-bottom: 50px;\"\u003eHelp Me Travel The World\u003c/h1\u003e` + \"\\n\\n\"\n\n\tout += renderTipsJar() + \"\\n\"\n\n\tout += ufmt.Sprintf(`\u003cstrong style=\"font-size: 1.2em;\"\u003eI am currently in %s, \u003cbr\u003e tip the jar to send me somewhere else!\u003c/strong\u003e`, travel.cities[travel.currentCityIndex].Name)\n\n\tout += `\u003cbr\u003e\u003cspan style=\"font-size: 1.2em; font-style: italic; margin-top: 10px; display: inline-block;\"\u003eClick the jar, tip in GNOT coins, and watch my background change as I head to a new adventure!\u003c/span\u003e\u003c/p\u003e` + \"\\n\\n\"\n\n\tout += renderSponsors()\n\n\tout += `\u003c/div\u003e\u003c!-- /rows-2 --\u003e` + \"\\n\\n\"\n\n\tout += `\u003c/div\u003e\u003c!-- /jumbotron --\u003e` + \"\\n\"\n\n\treturn out\n}\n\nfunc formatAddress(address string) string {\n\tif len(address) \u003c= 8 {\n\t\treturn address\n\t}\n\treturn address[:4] + \"...\" + address[len(address)-4:]\n}\n\nfunc renderSponsors() string {\n\tout := `\u003ch3 style=\"margin-top: 5px; margin-bottom: 20px\"\u003eSponsor Leaderboard\u003c/h3\u003e` + \"\\n\"\n\n\tif sponsorship.sponsorsCount == 0 {\n\t\treturn out + `\u003cp style=\"text-align: center;\"\u003eNo sponsors yet. Be the first to tip the jar!\u003c/p\u003e` + \"\\n\"\n\t}\n\n\ttopSponsors := GetTopSponsors()\n\tnumSponsors := len(topSponsors)\n\tif numSponsors \u003e sponsorship.maxSponsors {\n\t\tnumSponsors = sponsorship.maxSponsors\n\t}\n\n\tout += `\u003cul style=\"list-style-type: none; padding: 0; border: 1px solid #ddd; border-radius: 8px; width: 100%; max-width: 300px; margin: 0 auto;\"\u003e` + \"\\n\"\n\n\tfor i := 0; i \u003c numSponsors; i++ {\n\t\tsponsor := topSponsors[i]\n\t\tisLastItem := (i == numSponsors-1)\n\n\t\tpadding := \"10px 5px\"\n\t\tborder := \"border-bottom: 1px solid #ddd;\"\n\n\t\tif isLastItem {\n\t\t\tpadding = \"8px 5px\"\n\t\t\tborder = \"\"\n\t\t}\n\n\t\tout += ufmt.Sprintf(\n\t\t\t`\u003cli style=\"padding: %s; %s text-align: left;\"\u003e\n\t\t\t\t\u003cstrong style=\"padding-left: 5px;\"\u003e%d. %s\u003c/strong\u003e \n\t\t\t\t\u003cspan style=\"float: right; padding-right: 5px;\"\u003e%s\u003c/span\u003e\n\t\t\t\u003c/li\u003e`,\n\t\t\tpadding, border, i+1, formatAddress(sponsor.Address.String()), sponsor.Amount.String(),\n\t\t)\n\t}\n\n\treturn out\n}\n\nfunc renderTipsJar() string {\n\tout := ufmt.Sprintf(`\u003ca href=\"%s\" target=\"_blank\" style=\"display: block; text-decoration: none;\"\u003e`, travel.jarLink) + \"\\n\"\n\n\tout += `\u003cimg src=\"https://i.ibb.co/4TH9zbw/tips-jar.png\" alt=\"Tips Jar\" style=\"width: 300px; height: auto; display: block; margin: 0 auto;\"\u003e` + \"\\n\"\n\n\tout += `\u003c/a\u003e` + \"\\n\"\n\n\treturn out\n}\n"},{"name":"home_test.gno","body":"package home\n\nimport (\n\t\"std\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"gno.land/p/demo/avl\"\n\t\"gno.land/p/demo/testutils\"\n)\n\nfunc TestUpdatePFP(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\tprofile.pfp = \"\"\n\n\tUpdatePFP(\"https://example.com/pic.png\")\n\n\tif profile.pfp != \"https://example.com/pic.png\" {\n\t\tt.Fatalf(\"expected pfp to be https://example.com/pic.png, got %s\", profile.pfp)\n\t}\n}\n\nfunc TestUpdateAboutMe(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\tprofile.aboutMe = []string{}\n\n\tUpdateAboutMe(\"This is my new bio.|I love coding!\")\n\n\texpected := []string{\"This is my new bio.\", \"I love coding!\"}\n\n\tif len(profile.aboutMe) != len(expected) {\n\t\tt.Fatalf(\"expected aboutMe to have length %d, got %d\", len(expected), len(profile.aboutMe))\n\t}\n\n\tfor i := range profile.aboutMe {\n\t\tif profile.aboutMe[i] != expected[i] {\n\t\t\tt.Fatalf(\"expected aboutMe[%d] to be %s, got %s\", i, expected[i], profile.aboutMe[i])\n\t\t}\n\t}\n}\n\nfunc TestUpdateCities(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\ttravel.cities = []City{}\n\n\tnewCities := []City{\n\t\t{Name: \"Berlin\", URL: \"https://example.com/berlin.jpg\"},\n\t\t{Name: \"Vienna\", URL: \"https://example.com/vienna.jpg\"},\n\t}\n\n\tUpdateCities(newCities)\n\n\tif len(travel.cities) != 2 {\n\t\tt.Fatalf(\"expected 2 cities, got %d\", len(travel.cities))\n\t}\n\n\tif travel.cities[0].Name != \"Berlin\" || travel.cities[1].Name != \"Vienna\" {\n\t\tt.Fatalf(\"expected cities to be updated to Berlin and Vienna, got %+v\", travel.cities)\n\t}\n}\n\nfunc TestUpdateJarLink(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\ttravel.jarLink = \"\"\n\n\tUpdateJarLink(\"https://example.com/jar\")\n\n\tif travel.jarLink != \"https://example.com/jar\" {\n\t\tt.Fatalf(\"expected jarLink to be https://example.com/jar, got %s\", travel.jarLink)\n\t}\n}\n\nfunc TestUpdateMaxSponsors(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\tsponsorship.maxSponsors = 0\n\n\tUpdateMaxSponsors(10)\n\n\tif sponsorship.maxSponsors != 10 {\n\t\tt.Fatalf(\"expected maxSponsors to be 10, got %d\", sponsorship.maxSponsors)\n\t}\n\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Fatalf(\"expected panic for setting maxSponsors to 0\")\n\t\t}\n\t}()\n\tUpdateMaxSponsors(0)\n}\n\nfunc TestAddCities(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\ttravel.cities = []City{}\n\n\tAddCities(City{Name: \"Berlin\", URL: \"https://example.com/berlin.jpg\"})\n\n\tif len(travel.cities) != 1 {\n\t\tt.Fatalf(\"expected 1 city, got %d\", len(travel.cities))\n\t}\n\tif travel.cities[0].Name != \"Berlin\" || travel.cities[0].URL != \"https://example.com/berlin.jpg\" {\n\t\tt.Fatalf(\"expected city to be Berlin, got %+v\", travel.cities[0])\n\t}\n\n\tAddCities(\n\t\tCity{Name: \"Paris\", URL: \"https://example.com/paris.jpg\"},\n\t\tCity{Name: \"Tokyo\", URL: \"https://example.com/tokyo.jpg\"},\n\t)\n\n\tif len(travel.cities) != 3 {\n\t\tt.Fatalf(\"expected 3 cities, got %d\", len(travel.cities))\n\t}\n\tif travel.cities[1].Name != \"Paris\" || travel.cities[2].Name != \"Tokyo\" {\n\t\tt.Fatalf(\"expected cities to be Paris and Tokyo, got %+v\", travel.cities[1:])\n\t}\n}\n\nfunc TestAddAboutMeRows(t *testing.T) {\n\tvar owner = std.Address(\"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8\")\n\tstd.TestSetOrigCaller(owner)\n\n\tprofile.aboutMe = []string{}\n\n\tAddAboutMeRows(\"I love exploring new technologies!\")\n\n\tif len(profile.aboutMe) != 1 {\n\t\tt.Fatalf(\"expected 1 aboutMe row, got %d\", len(profile.aboutMe))\n\t}\n\tif profile.aboutMe[0] != \"I love exploring new technologies!\" {\n\t\tt.Fatalf(\"expected first aboutMe row to be 'I love exploring new technologies!', got %s\", profile.aboutMe[0])\n\t}\n\n\tAddAboutMeRows(\"Travel is my passion!\", \"Always learning.\")\n\n\tif len(profile.aboutMe) != 3 {\n\t\tt.Fatalf(\"expected 3 aboutMe rows, got %d\", len(profile.aboutMe))\n\t}\n\tif profile.aboutMe[1] != \"Travel is my passion!\" || profile.aboutMe[2] != \"Always learning.\" {\n\t\tt.Fatalf(\"expected aboutMe rows to be 'Travel is my passion!' and 'Always learning.', got %+v\", profile.aboutMe[1:])\n\t}\n}\n\nfunc TestDonate(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.DonationsCount = 0\n\tsponsorship.sponsorsCount = 0\n\ttravel.currentCityIndex = 0\n\n\tcoinsSent := std.NewCoins(std.NewCoin(\"ugnot\", 500))\n\tstd.TestSetOrigSend(coinsSent, std.NewCoins())\n\tDonate()\n\n\texistingAmount, exists := sponsorship.sponsors.Get(string(user))\n\tif !exists {\n\t\tt.Fatalf(\"expected sponsor to be added, but it was not found\")\n\t}\n\n\tif existingAmount.(std.Coins).AmountOf(\"ugnot\") != 500 {\n\t\tt.Fatalf(\"expected donation amount to be 500ugnot, got %d\", existingAmount.(std.Coins).AmountOf(\"ugnot\"))\n\t}\n\n\tif sponsorship.DonationsCount != 1 {\n\t\tt.Fatalf(\"expected DonationsCount to be 1, got %d\", sponsorship.DonationsCount)\n\t}\n\n\tif sponsorship.sponsorsCount != 1 {\n\t\tt.Fatalf(\"expected sponsorsCount to be 1, got %d\", sponsorship.sponsorsCount)\n\t}\n\n\tif travel.currentCityIndex != 1 {\n\t\tt.Fatalf(\"expected currentCityIndex to be 1, got %d\", travel.currentCityIndex)\n\t}\n\n\tcoinsSent = std.NewCoins(std.NewCoin(\"ugnot\", 300))\n\tstd.TestSetOrigSend(coinsSent, std.NewCoins())\n\tDonate()\n\n\texistingAmount, exists = sponsorship.sponsors.Get(string(user))\n\tif !exists {\n\t\tt.Fatalf(\"expected sponsor to exist after second donation, but it was not found\")\n\t}\n\n\tif existingAmount.(std.Coins).AmountOf(\"ugnot\") != 800 {\n\t\tt.Fatalf(\"expected total donation amount to be 800ugnot, got %d\", existingAmount.(std.Coins).AmountOf(\"ugnot\"))\n\t}\n\n\tif sponsorship.DonationsCount != 2 {\n\t\tt.Fatalf(\"expected DonationsCount to be 2 after second donation, got %d\", sponsorship.DonationsCount)\n\t}\n\n\tif travel.currentCityIndex != 2 {\n\t\tt.Fatalf(\"expected currentCityIndex to be 2 after second donation, got %d\", travel.currentCityIndex)\n\t}\n}\n\nfunc TestGetTopSponsors(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.sponsorsCount = 0\n\n\tsponsorship.sponsors.Set(\"g1address1\", std.NewCoins(std.NewCoin(\"ugnot\", 300)))\n\tsponsorship.sponsors.Set(\"g1address2\", std.NewCoins(std.NewCoin(\"ugnot\", 500)))\n\tsponsorship.sponsors.Set(\"g1address3\", std.NewCoins(std.NewCoin(\"ugnot\", 200)))\n\tsponsorship.sponsorsCount = 3\n\n\ttopSponsors := GetTopSponsors()\n\n\tif len(topSponsors) != 3 {\n\t\tt.Fatalf(\"expected 3 sponsors, got %d\", len(topSponsors))\n\t}\n\n\tif topSponsors[0].Address.String() != \"g1address2\" || topSponsors[0].Amount.AmountOf(\"ugnot\") != 500 {\n\t\tt.Fatalf(\"expected top sponsor to be g1address2 with 500ugnot, got %s with %dugnot\", topSponsors[0].Address.String(), topSponsors[0].Amount.AmountOf(\"ugnot\"))\n\t}\n\n\tif topSponsors[1].Address.String() != \"g1address1\" || topSponsors[1].Amount.AmountOf(\"ugnot\") != 300 {\n\t\tt.Fatalf(\"expected second sponsor to be g1address1 with 300ugnot, got %s with %dugnot\", topSponsors[1].Address.String(), topSponsors[1].Amount.AmountOf(\"ugnot\"))\n\t}\n\n\tif topSponsors[2].Address.String() != \"g1address3\" || topSponsors[2].Amount.AmountOf(\"ugnot\") != 200 {\n\t\tt.Fatalf(\"expected third sponsor to be g1address3 with 200ugnot, got %s with %dugnot\", topSponsors[2].Address.String(), topSponsors[2].Amount.AmountOf(\"ugnot\"))\n\t}\n}\n\nfunc TestGetTotalDonations(t *testing.T) {\n\tvar user = testutils.TestAddress(\"user\")\n\tstd.TestSetOrigCaller(user)\n\n\tsponsorship.sponsors = avl.NewTree()\n\tsponsorship.sponsorsCount = 0\n\n\tsponsorship.sponsors.Set(\"g1address1\", std.NewCoins(std.NewCoin(\"ugnot\", 300)))\n\tsponsorship.sponsors.Set(\"g1address2\", std.NewCoins(std.NewCoin(\"ugnot\", 500)))\n\tsponsorship.sponsors.Set(\"g1address3\", std.NewCoins(std.NewCoin(\"ugnot\", 200)))\n\tsponsorship.sponsorsCount = 3\n\n\ttotalDonations := GetTotalDonations()\n\n\tif totalDonations != 1000 {\n\t\tt.Fatalf(\"expected total donations to be 1000ugnot, got %dugnot\", totalDonations)\n\t}\n}\n\nfunc TestRender(t *testing.T) {\n\ttravel.currentCityIndex = 0\n\ttravel.cities = []City{\n\t\t{Name: \"Venice\", URL: \"https://example.com/venice.jpg\"},\n\t\t{Name: \"Paris\", URL: \"https://example.com/paris.jpg\"},\n\t}\n\n\toutput := Render(\"\")\n\n\texpectedCity := \"Venice\"\n\tif !strings.Contains(output, expectedCity) {\n\t\tt.Fatalf(\"expected output to contain city name '%s', got %s\", expectedCity, output)\n\t}\n\n\texpectedURL := \"https://example.com/venice.jpg\"\n\tif !strings.Contains(output, expectedURL) {\n\t\tt.Fatalf(\"expected output to contain city URL '%s', got %s\", expectedURL, output)\n\t}\n\n\ttravel.currentCityIndex = 1\n\toutput = Render(\"\")\n\n\texpectedCity = \"Paris\"\n\tif !strings.Contains(output, expectedCity) {\n\t\tt.Fatalf(\"expected output to contain city name '%s', got %s\", expectedCity, output)\n\t}\n\n\texpectedURL = \"https://example.com/paris.jpg\"\n\tif !strings.Contains(output, expectedURL) {\n\t\tt.Fatalf(\"expected output to contain city URL '%s', got %s\", expectedURL, output)\n\t}\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}
{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"rewards","path":"gno.land/r/sys/rewards","files":[{"name":"rewards.gno","body":"// This package will be used to manage proof-of-contributions on the exposed smart-contract side.\npackage rewards\n\n// TODO: write specs.\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}
{"msg":[{"@type":"/vm.m_addpkg","creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","package":{"name":"users","path":"gno.land/r/sys/users","files":[{"name":"verify.gno","body":"package users\n\nimport (\n\t\"std\"\n\n\t\"gno.land/p/demo/ownable\"\n\t\"gno.land/r/demo/users\"\n)\n\nconst admin = \"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq\" // @moul\n\ntype VerifyNameFunc func(enabled bool, address std.Address, name string) bool\n\nvar (\n\towner = ownable.NewWithAddress(admin) // Package owner\n\tcheckFunc = VerifyNameByUser // Checking namespace callback\n\tenabled = false // For now this package is disabled by default\n)\n\nfunc IsEnabled() bool { return enabled }\n\n// This method ensures that the given address has ownership of the given name.\nfunc IsAuthorizedAddressForName(address std.Address, name string) bool {\n\treturn checkFunc(enabled, address, name)\n}\n\n// VerifyNameByUser checks from the `users` package that the user has correctly\n// registered the given name.\n// This function considers as valid an `address` that matches the `name`.\nfunc VerifyNameByUser(enable bool, address std.Address, name string) bool {\n\tif !enable {\n\t\treturn true\n\t}\n\n\t// Allow user with their own address as name\n\tif address.String() == name {\n\t\treturn true\n\t}\n\n\tif user := users.GetUserByName(name); user != nil {\n\t\treturn user.Address == address\n\t}\n\n\treturn false\n}\n\n// Admin calls\n\n// Enable this package.\nfunc AdminEnable() {\n\tif err := owner.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tenabled = true\n}\n\n// Disable this package.\nfunc AdminDisable() {\n\tif err := owner.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tenabled = false\n}\n\n// AdminUpdateVerifyCall updates the method that verifies the namespace.\nfunc AdminUpdateVerifyCall(check VerifyNameFunc) {\n\tif err := owner.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\tcheckFunc = check\n}\n\n// AdminTransferOwnership transfers the ownership to a new owner.\nfunc AdminTransferOwnership(newOwner std.Address) error {\n\tif err := owner.CallerIsOwner(); err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn owner.TransferOwnership(newOwner)\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"50000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":null,"signature":null}],"memo":""}
{"msg":[{"@type":"/vm.m_call","caller":"g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq","send":"","pkg_path":"gno.land/r/demo/users","func":"Invite","args":["g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj:10\ng1589c8cekvmjfmy0qrd4f3z52r7fn7rgk02667s:1\ng13sm84nuqed3fuank8huh7x9mupgw22uft3lcl8:1\ng1m6732pkrngu9vrt0g7056lvr9kcqc4mv83xl5q:1\ng1wg88rhzlwxjd2z4j5de5v5xq30dcf6rjq3dhsj:1\ng18pmaskasz7mxj6rmgrl3al58xu45a7w0l5nmc0:1\ng19wwhkmqlns70604ksp6rkuuu42qhtvyh05lffz:1\ng187982000zsc493znqt828s90cmp6hcp2erhu6m:1\ng1ndpsnrspdnauckytvkfv8s823t3gmpqmtky8pl:1\ng16ja66d65emkr0zxd2tu7xjvm7utthyhpej0037:1\ng1ds24jj9kqjcskd0gzu24r9e4n62ggye230zuv5:1\ng1trkzq75ntamsnw9xnrav2v7gy2lt5g6p29yhdr:1\ng1rrf8s5mrmu00sx04fzfsvc399fklpeg2x0a7mz:1\ng19p5ntfvpt4lwq4jqsmnxsnelhf3tff9scy3w8w:1\ng1tue8l73d6rq4vhqdsp2sr3zhuzpure3k2rnwpz:1\ng14hhsss4ngx5kq77je5g0tl4vftg8qp45ceadk3:1\ng1768hvkh7anhd40ch4h7jdh6j3mpcs7hrat4gl0:1\ng15fa8kyjhu88t9dr8zzua8fwdvkngv5n8yqsm0n:1\ng1xhccdjcscuhgmt3quww6qdy3j3czqt3urc2eac:1\ng1z629z04f85k4t5gnkk5egpxw9tqxeec435esap:1\ng1pfldkplz9puq0v82lu9vqcve9nwrxuq9qe5ttv:1\ng152pn0g5qfgxr7yx8zlwjq48hytkafd8x7egsfv:1\ng1cf2ye686ke38vjyqakreprljum4xu6rwf5jskq:1\ng1c5shztyaj4gjrc5zlwmh9xhex5w7l4asffs2w6:1\ng1lhpx2ktk0ha3qw42raxq4m24a4c4xqxyrgv54q:1\ng1026p54q0j902059sm2zsv37krf0ghcl7gmhyv7:1\ng1n4yvwnv77frq2ccuw27dmtjkd7u4p4jg0pgm7k:1\ng13m7f2e6r3lh3ykxupacdt9sem2tlvmaamwjhll:1\ng19uxluuecjlsqvwmwu8sp6pxaaqfhk972q975xd:1\ng1j80fpcsumfkxypvydvtwtz3j4sdwr8c2u0lr64:1\ng1tjdpptuk9eysq6z38nscqyycr998xjyx3w8jvw:1\ng19t3n89slfemgd3mwuat4lajwcp0yxrkadgeg7a:1\ng1yqndt8xx92l9h494jfruz2w79swzjes3n4wqjc:1\ng13278z0a5ufeg80ffqxpda9dlp599t7ekregcy6:1\ng1ht236wjd83x96uqwh9rh3fq6pylyn78mtwq9v6:1\ng1fj9jccm3zjnqspq7lp2g7lj4czyfq0s35600g9:1\ng1wwppuzdns5u6c6jqpkzua24zh6ppsus6399cea:1\ng1k8pjnguyu36pkc8hy0ufzgpzfmj2jl78la7ek3:1\ng1e8umkzumtxgs8399lw0us4rclea3xl5gxy9spp:1\ng14qekdkj2nmmwea4ufg9n002a3pud23y8k7ugs5:1\ng19w2488ntfgpduzqq3sk4j5x387zynwknqdvjqf:1\ng1495y3z7zrej4rendysnw5kaeu4g3d7x7w0734g:1\ng1hygx8ga9qakhkczyrzs9drm8j8tu4qds9y5e3r:1\ng1f977l6wxdh3qu60kzl75vx2wmzswu68l03r8su:1\ng1644qje5rx6jsdqfkzmgnfcegx4dxkjh6rwqd69:1\ng1mzjajymvmtksdwh3wkrndwj6zls2awl9q83dh6:1\ng14da4n9hcynyzz83q607uu8keuh9hwlv42ra6fa:10\ng14vhcdsyf83ngsrrqc92kmw8q9xakqjm0v8448t:5\n"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AmG6kzznyo1uNqWPAYU6wDpsmzQKDaEOrVRaZ08vOyX0"},"signature":"S8iMMzlOMK8dmox78R9Z8+pSsS8YaTCXrIcaHDpiOgkOy7gqoQJ0oftM0zf8zAz4xpezK8Lzg8Q0fCdXJxV76w=="}],"memo":""}
@@ -5617,7 +5619,7 @@
{"msg":[{"@type":"/vm.m_call","caller":"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8","send":"100ugnot","pkg_path":"gno.land/r/g1d9s3lkk6r4a9yvygwfyf8madha9uxxavl6e73p/home","func":"Donate","args":null}],"fee":{"gas_wanted":"10000000","gas_fee":"50000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"Aiv9tqagYf7E57BDv13G2FRAt+yvhG915Lu0eJSRl0z4"},"signature":"Pv3bo9WX5Ay/UC6zryrfZZg3FTC5R2fv1P9neCfiHxIbcj0aiacv+SFcTU9OPuEllqulwLfPCSVZxwTIXuifbg=="}],"memo":"Called through gno.studio"}
{"msg":[{"@type":"/vm.m_call","caller":"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8","send":"120ugnot","pkg_path":"gno.land/r/g1d9s3lkk6r4a9yvygwfyf8madha9uxxavl6e73p/home","func":"Donate","args":null}],"fee":{"gas_wanted":"10000000","gas_fee":"50000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"Aiv9tqagYf7E57BDv13G2FRAt+yvhG915Lu0eJSRl0z4"},"signature":"yREPtWvMWZn5tdvkSs6JsycXVtNDTAZl6IQ5w6v7KmwwJPcSpKzN3lM/ZCQVN++4IKVDtPx9OjUVLPbrNLzX0A=="}],"memo":"Called through gno.studio"}
{"msg":[{"@type":"/bank.MsgSend","from_address":"g1qhuef2450xh7g7na8s865nreu2xw8j84kgkvt5","to_address":"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8","amount":"10000000ugnot"}],"fee":{"gas_wanted":"100000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"AwEKz9MRdU4MCAzB3el9G4wLJJQeWiRYtRquzEgm19Z2"},"signature":"MsajM02gLPameeqOJUhisbHk8sZZoAljpImQqBsYIBdtvANcETdJiqigDdPG5FBYp0hFnFZ2BAxF8GdZVCCDuw=="}],"memo":""}
-{"msg":[{"@type":"/vm.m_addpkg","creator":"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8","package":{"name":"home","path":"gno.land/r/stefann/home","files":[{"name":"home.gno","body":"package home\n\nimport (\n\t\"sort\"\n\t\"std\"\n\n\t\"gno.land/p/demo/ufmt\"\n\n\t\"gno.land/r/stefann/config\"\n)\n\ntype City struct {\n\tName string\n\tURL string\n}\n\ntype Sponsor struct {\n\tAddress std.Address\n\tAmount std.Coins\n}\n\nvar (\n\tpfp string\n\tcities []City\n\tcurrentCityIndex int\n\taboutMe [2]string\n\tjarLink string\n\tmaxSponsors int\n\tsponsors []Sponsor\n\ttotalDonated std.Coins\n\ttotalDonations int\n)\n\nfunc init() {\n\tpfp = \"https://i.ibb.co/Bc5YNCx/DSC-0095a.jpg\"\n\tcities = []City{\n\t\t{Name: \"Venice\", URL: \"https://i.ibb.co/1mcZ7b1/venice.jpg\"},\n\t\t{Name: \"Tokyo\", URL: \"https://i.ibb.co/wNDJv3H/tokyo.jpg\"},\n\t\t{Name: \"São Paulo\", URL: \"https://i.ibb.co/yWMq2Sn/sao-paulo.jpg\"},\n\t\t{Name: \"Toronto\", URL: \"https://i.ibb.co/pb95HJB/toronto.jpg\"},\n\t\t{Name: \"Bangkok\", URL: \"https://i.ibb.co/pQy3w2g/bangkok.jpg\"},\n\t\t{Name: \"New York\", URL: \"https://i.ibb.co/6JWLm0h/new-york.jpg\"},\n\t\t{Name: \"Paris\", URL: \"https://i.ibb.co/q9vf6Hs/paris.jpg\"},\n\t\t{Name: \"Kandersteg\", URL: \"https://i.ibb.co/60DzywD/kandersteg.jpg\"},\n\t\t{Name: \"Rothenburg\", URL: \"https://i.ibb.co/cr8d2rQ/rothenburg.jpg\"},\n\t\t{Name: \"Capetown\", URL: \"https://i.ibb.co/bPGn0v3/capetown.jpg\"},\n\t\t{Name: \"Sydney\", URL: \"https://i.ibb.co/TBNzqfy/sydney.jpg\"},\n\t\t{Name: \"Oeschinen Lake\", URL: \"https://i.ibb.co/QJQwp2y/oeschinen-lake.jpg\"},\n\t\t{Name: \"Barra Grande\", URL: \"https://i.ibb.co/z4RXKc1/barra-grande.jpg\"},\n\t\t{Name: \"London\", URL: \"https://i.ibb.co/CPGtvgr/london.jpg\"},\n\t}\n\tcurrentCityIndex = 0\n\tjarLink = \"https://TODO\"\n\tmaxSponsors = 5\n\taboutMe = [2]string{\n\t\t`\u003ch3 style=\"font-size: 1.4em;\"\u003eAbout Me\u003c/h3\u003e\n \u003cp style=\"font-size: 1.1em;\"\u003eHey there! I’m Stefan, a student of Computer Science. I’m all about exploring and adventure — whether it’s diving into the latest tech or discovering a new city, I’m always up for the challenge!\u003c/p\u003e`,\n\t\t`\u003ch3 style=\"font-size: 1.4em;\"\u003eContributions\u003c/h3\u003e\n \u003cp style=\"font-size: 1.1em;\"\u003eI'm just getting started, but you can follow my journey through Gno.land right here \u003ca href=\"https://github.com/gnolang/hackerspace/issues/94\" target=\"_blank\"\u003e🔗\u003c/a\u003e\u003c/p\u003e`,\n\t}\n}\n\nfunc UpdateMaxSponsors(newMax int) {\n\tconfig.AssertAuthorized()\n\tmaxSponsors = newMax\n}\n\nfunc UpdateCities(newCities []City) {\n\tconfig.AssertAuthorized()\n\tcities = newCities\n}\n\nfunc UpdateJarLink(newLink string) {\n\tconfig.AssertAuthorized()\n\tjarLink = newLink\n}\n\nfunc UpdatePFP(url, caption string) {\n\tconfig.AssertAuthorized()\n\tpfp = url\n}\n\nfunc UpdateAboutMe(col1, col2 string) {\n\tconfig.AssertAuthorized()\n\taboutMe[0] = col1\n\taboutMe[1] = col2\n}\n\nfunc Donate() {\n\taddress := std.GetOrigCaller()\n\tamount := std.GetOrigSend()\n\n\tif amount.AmountOf(\"ugnot\") == 0 {\n\t\tpanic(\"Donation must include GNOT\")\n\t}\n\n\tfound := false\n\n\tfor i, sponsor := range sponsors {\n\t\tif sponsor.Address == address {\n\t\t\tsponsors[i].Amount = sponsors[i].Amount.Add(amount)\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif !found {\n\t\tsponsors = append(sponsors, Sponsor{Address: address, Amount: amount})\n\t}\n\n\ttotalDonated = totalDonated.Add(amount)\n\n\ttotalDonations++\n\n\tsortSponsorsByAmount()\n\n\tif len(cities) \u003e 0 {\n\t\tcurrentCityIndex++\n\t\tif currentCityIndex \u003e= len(cities) {\n\t\t\tcurrentCityIndex = 0\n\t\t}\n\t}\n}\n\ntype SponsorSlice []Sponsor\n\nfunc (s SponsorSlice) Len() int {\n\treturn len(s)\n}\n\nfunc (s SponsorSlice) Less(i, j int) bool {\n\treturn s[i].Amount.AmountOf(\"ugnot\") \u003e s[j].Amount.AmountOf(\"ugnot\")\n}\n\nfunc (s SponsorSlice) Swap(i, j int) {\n\ts[i], s[j] = s[j], s[i]\n}\n\nfunc sortSponsorsByAmount() {\n\tsort.Sort(SponsorSlice(sponsors))\n}\n\nfunc GetTopSponsors() []Sponsor {\n\treturn sponsors\n}\n\nfunc CollectDonations() {\n\tconfig.AssertAuthorized()\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\townerAddr := config.Address()\n\tbanker.SendCoins(std.GetOrigPkgAddr(), ownerAddr, banker.GetCoins(std.GetOrigPkgAddr()))\n}\n\nfunc GetTotalDonations() std.Coins {\n\treturn totalDonated\n}\n\nfunc GetDonationCount() int {\n\treturn totalDonations\n}\n\nfunc Render(path string) string {\n\tout := ufmt.Sprintf(\"# Exploring %s!\\n\\n\", cities[currentCityIndex].Name)\n\n\tout += renderAboutMe()\n\tout += \"\\n\\n\"\n\tout += renderTips()\n\n\treturn out\n}\n\nfunc renderAboutMe() string {\n\tout := \"\u003cdiv class='rows-3'\u003e\"\n\n\tout += \"\u003cdiv style='position: relative; text-align: center;'\u003e\\n\\n\"\n\n\tout += ufmt.Sprintf(\"\u003cdiv style='background-image: url(%s); background-size: cover; background-position: center; width: 100%%; height: 600px; position: relative; border-radius: 15px; overflow: hidden;'\u003e\\n\\n\", cities[currentCityIndex].URL)\n\n\tout += ufmt.Sprintf(\"\u003cimg src='%s' alt='my profile pic' style='width: 250px; height: auto; aspect-ratio: 1 / 1; object-fit: cover; border-radius: 50%%; border: 3px solid #1e1e1e; position: absolute; top: 75%%; left: 50%%; transform: translate(-50%%, -50%%);'\u003e\\n\\n\", pfp)\n\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += aboutMe[0] + \"\\n\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += aboutMe[1] + \"\\n\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003c/div\u003e\u003c!-- /rows-3 --\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderTips() string {\n\tout := `\u003cdiv class=\"jumbotron\" style=\"display: flex; flex-direction: column; justify-content: flex-start; align-items: center; padding-top: 40px; padding-bottom: 50px; text-align: center;\"\u003e` + \"\\n\\n\"\n\n\tout += `\u003cdiv class=\"rows-2\" style=\"max-width: 500px; width: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center;\"\u003e` + \"\\n\"\n\n\tout += `\u003ch1 style=\"margin-bottom: 50px;\"\u003eHelp Me Travel The World\u003c/h1\u003e` + \"\\n\\n\"\n\n\tout += renderTipsJar() + \"\\n\"\n\n\tout += ufmt.Sprintf(`\u003cstrong style=\"font-size: 1.2em;\"\u003eI am currently in %s, \u003cbr\u003e tip the jar to send me somewhere else!\u003c/strong\u003e`, cities[currentCityIndex].Name)\n\n\tout += `\u003cbr\u003e\u003cspan style=\"font-size: 1.2em; font-style: italic; margin-top: 10px; display: inline-block;\"\u003eClick the jar, tip in GNOT coins, and watch my background change as I head to a new adventure!\u003c/span\u003e\u003c/p\u003e` + \"\\n\\n\"\n\n\tout += renderSponsors()\n\n\tout += `\u003c/div\u003e\u003c!-- /rows-2 --\u003e` + \"\\n\\n\"\n\n\tout += `\u003c/div\u003e\u003c!-- /jumbotron --\u003e` + \"\\n\"\n\n\treturn out\n}\n\nfunc formatAddress(address string) string {\n\tif len(address) \u003c= 8 {\n\t\treturn address\n\t}\n\treturn address[:4] + \"...\" + address[len(address)-4:]\n}\n\nfunc renderSponsors() string {\n\tout := `\u003ch3 style=\"margin-top: 5px; margin-bottom: 20px\"\u003eSponsor Leaderboard\u003c/h3\u003e` + \"\\n\"\n\n\tif len(sponsors) == 0 {\n\t\tout += `\u003cp style=\"text-align: center;\"\u003eNo sponsors yet. Be the first to tip the jar!\u003c/p\u003e` + \"\\n\"\n\t} else {\n\t\tnumSponsors := len(sponsors)\n\t\tif numSponsors \u003e maxSponsors {\n\t\t\tnumSponsors = maxSponsors\n\t\t}\n\n\t\tout += `\u003cul style=\"list-style-type: none; padding: 0; border: 1px solid #ddd; border-radius: 8px; width: 100%; max-width: 300px; margin: 0 auto;\"\u003e` + \"\\n\"\n\n\t\tfor i := 0; i \u003c numSponsors; i++ {\n\t\t\tsponsor := sponsors[i]\n\t\t\tisLastItem := (i == numSponsors-1)\n\n\t\t\tpadding := \"10px 5px\"\n\t\t\tborder := \"border-bottom: 1px solid #ddd;\"\n\n\t\t\tif isLastItem {\n\t\t\t\tpadding = \"8px 5px\"\n\t\t\t\tborder = \"\"\n\t\t\t}\n\n\t\t\tout += ufmt.Sprintf(\n\t\t\t\t`\u003cli style=\"padding: %s; %s text-align: left;\"\u003e\n \u003cstrong style=\"padding-left: 5px;\"\u003e%d. %s\u003c/strong\u003e \n \u003cspan style=\"float: right; padding-right: 5px;\"\u003e%s\u003c/span\u003e\n \u003c/li\u003e`,\n\t\t\t\tpadding, border, i+1, formatAddress(sponsor.Address.String()), sponsor.Amount.String(),\n\t\t\t)\n\t\t}\n\n\t}\n\n\treturn out\n}\n\nfunc renderTipsJar() string {\n\tout := ufmt.Sprintf(`\u003ca href=\"%s\" target=\"_blank\" style=\"display: block; text-decoration: none;\"\u003e`, jarLink) + \"\\n\"\n\n\tout += `\u003cimg src=\"https://i.ibb.co/4TH9zbw/tips-jar.png\" alt=\"Tips Jar\" style=\"width: 300px; height: auto; display: block; margin: 0 auto;\"\u003e` + \"\\n\"\n\n\tout += `\u003c/a\u003e` + \"\\n\"\n\n\treturn out\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"8000000","gas_fee":"10000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"Aiv9tqagYf7E57BDv13G2FRAt+yvhG915Lu0eJSRl0z4"},"signature":"gBha9lC0HewFreAvzz0q32YDQhG2W3HiZyhU8wTwKNR3Wrz7v6mYBkKQbknlEgrG18sFjTTJCvvvg+yq+lE4rw=="}],"memo":""}
+{"msg":[{"@type":"/vm.m_addpkg","creator":"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8","package":{"name":"home","path":"gno.land/r/stefann/home","files":[{"name":"home.gno","body":"package home\n\nimport (\n \"std\"\n \"sort\"\n\n \"gno.land/p/demo/ufmt\"\n\n \"gno.land/r/stefann/config\"\n)\n\n\ntype City struct {\n\tName string\n\tURL string\n}\n\ntype Sponsor struct {\n\tAddress std.Address\n\tAmount std.Coins\n}\n\nvar (\n pfp string\n cities []City\n currentCityIndex int\n aboutMe [2]string\n jarLink string\n maxSponsors int\n sponsors []Sponsor\n\ttotalDonated std.Coins\n\ttotalDonations int\n)\n\nfunc init() {\n pfp = \"https://i.ibb.co/Bc5YNCx/DSC-0095a.jpg\"\n cities = []City{\n {Name: \"Venice\", URL: \"https://i.ibb.co/1mcZ7b1/venice.jpg\"},\n {Name: \"Tokyo\", URL: \"https://i.ibb.co/wNDJv3H/tokyo.jpg\"},\n {Name: \"São Paulo\", URL: \"https://i.ibb.co/yWMq2Sn/sao-paulo.jpg\"},\n {Name: \"Toronto\", URL: \"https://i.ibb.co/pb95HJB/toronto.jpg\"},\n {Name: \"Bangkok\", URL: \"https://i.ibb.co/pQy3w2g/bangkok.jpg\"},\n {Name: \"New York\", URL: \"https://i.ibb.co/6JWLm0h/new-york.jpg\"},\n {Name: \"Paris\", URL: \"https://i.ibb.co/q9vf6Hs/paris.jpg\"},\n {Name: \"Kandersteg\", URL: \"https://i.ibb.co/60DzywD/kandersteg.jpg\"},\n {Name: \"Rothenburg\", URL: \"https://i.ibb.co/cr8d2rQ/rothenburg.jpg\"},\n {Name: \"Capetown\", URL: \"https://i.ibb.co/bPGn0v3/capetown.jpg\"},\n {Name: \"Sydney\", URL: \"https://i.ibb.co/TBNzqfy/sydney.jpg\"},\n {Name: \"Oeschinen Lake\", URL: \"https://i.ibb.co/QJQwp2y/oeschinen-lake.jpg\"},\n {Name: \"Barra Grande\", URL: \"https://i.ibb.co/z4RXKc1/barra-grande.jpg\"},\n {Name: \"London\", URL: \"https://i.ibb.co/CPGtvgr/london.jpg\"},\n }\n currentCityIndex = 0\n jarLink = \"https://TODO\"\n maxSponsors = 5\n aboutMe = [2]string{\n `\u003ch3 style=\"font-size: 1.4em;\"\u003eAbout Me\u003c/h3\u003e\n \u003cp style=\"font-size: 1.1em;\"\u003eHey there! I’m Stefan, a student of Computer Science. I’m all about exploring and adventure — whether it’s diving into the latest tech or discovering a new city, I’m always up for the challenge!\u003c/p\u003e`,\n `\u003ch3 style=\"font-size: 1.4em;\"\u003eContributions\u003c/h3\u003e\n \u003cp style=\"font-size: 1.1em;\"\u003eI'm just getting started, but you can follow my journey through Gno.land right here \u003ca href=\"https://github.com/gnolang/hackerspace/issues/94\" target=\"_blank\"\u003e🔗\u003c/a\u003e\u003c/p\u003e`,\n }\n}\n\nfunc UpdateMaxSponsors(newMax int) {\n config.AssertAuthorized()\n maxSponsors = newMax\n}\n\nfunc UpdateCities(newCities []City) {\n\tconfig.AssertAuthorized()\n\tcities = newCities\n}\n\nfunc UpdateJarLink(newLink string) {\n\tconfig.AssertAuthorized()\n\tjarLink = newLink\n}\n\nfunc UpdatePFP(url, caption string) {\n\tconfig.AssertAuthorized()\n\tpfp = url\n}\n\nfunc UpdateAboutMe(col1, col2 string) {\n\tconfig.AssertAuthorized()\n\taboutMe[0] = col1\n\taboutMe[1] = col2\n}\n\nfunc Donate() {\n address := std.GetOrigCaller()\n amount := std.GetOrigSend()\n \n if amount.AmountOf(\"ugnot\") == 0 {\n panic(\"Donation must include GNOT\")\n }\n\n found := false\n\n for i, sponsor := range sponsors {\n if sponsor.Address == address {\n sponsors[i].Amount = sponsors[i].Amount.Add(amount)\n found = true\n break\n }\n }\n\n if !found {\n sponsors = append(sponsors, Sponsor{Address: address, Amount: amount})\n }\n\n totalDonated = totalDonated.Add(amount)\n\n totalDonations++\n\n sortSponsorsByAmount()\n\n if len(cities) \u003e 0 {\n currentCityIndex++\n if currentCityIndex \u003e= len(cities) {\n currentCityIndex = 0\n }\n }\n}\n\ntype SponsorSlice []Sponsor\n\nfunc (s SponsorSlice) Len() int {\n\treturn len(s)\n}\n\nfunc (s SponsorSlice) Less(i, j int) bool {\n\treturn s[i].Amount.AmountOf(\"ugnot\") \u003e s[j].Amount.AmountOf(\"ugnot\")\n}\n\nfunc (s SponsorSlice) Swap(i, j int) {\n\ts[i], s[j] = s[j], s[i]\n}\n\nfunc sortSponsorsByAmount() {\n\tsort.Sort(SponsorSlice(sponsors))\n}\n\nfunc GetTopSponsors() []Sponsor {\n\treturn sponsors\n}\n\nfunc CollectDonations() {\n\tconfig.AssertAuthorized()\n\n\tbanker := std.GetBanker(std.BankerTypeRealmSend)\n\n\townerAddr := config.Address()\n\tbanker.SendCoins(std.GetOrigPkgAddr(), ownerAddr, banker.GetCoins(std.GetOrigPkgAddr()))\n}\n\nfunc GetTotalDonations() std.Coins {\n\treturn totalDonated\n}\n\nfunc GetDonationCount() int {\n\treturn totalDonations\n}\n\nfunc Render(path string) string {\n\tout := ufmt.Sprintf(\"# Exploring %s!\\n\\n\", cities[currentCityIndex].Name)\n\n\tout += renderAboutMe()\n\tout += \"\\n\\n\"\n\tout += renderTips()\n\n\treturn out\n}\n\nfunc renderAboutMe() string {\n\tout := \"\u003cdiv class='rows-3'\u003e\"\n\n\tout += \"\u003cdiv style='position: relative; text-align: center;'\u003e\\n\\n\"\n\n\tout += ufmt.Sprintf(\"\u003cdiv style='background-image: url(%s); background-size: cover; background-position: center; width: 100%%; height: 600px; position: relative; border-radius: 15px; overflow: hidden;'\u003e\\n\\n\", cities[currentCityIndex].URL)\n\n\tout += ufmt.Sprintf(\"\u003cimg src='%s' alt='my profile pic' style='width: 250px; height: auto; aspect-ratio: 1 / 1; object-fit: cover; border-radius: 50%%; border: 3px solid #1e1e1e; position: absolute; top: 75%%; left: 50%%; transform: translate(-50%%, -50%%);'\u003e\\n\\n\", pfp)\n\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += aboutMe[0] + \"\\n\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003cdiv\u003e\\n\\n\"\n\tout += aboutMe[1] + \"\\n\\n\"\n\tout += \"\u003c/div\u003e\\n\\n\"\n\n\tout += \"\u003c/div\u003e\u003c!-- /rows-3 --\u003e\\n\\n\"\n\n\treturn out\n}\n\nfunc renderTips() string {\n\tout := `\u003cdiv class=\"jumbotron\" style=\"display: flex; flex-direction: column; justify-content: flex-start; align-items: center; padding-top: 40px; padding-bottom: 50px; text-align: center;\"\u003e` + \"\\n\\n\"\n\n\tout += `\u003cdiv class=\"rows-2\" style=\"max-width: 500px; width: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center;\"\u003e` + \"\\n\"\n\n\tout += `\u003ch1 style=\"margin-bottom: 50px;\"\u003eHelp Me Travel The World\u003c/h1\u003e` + \"\\n\\n\"\n\n\tout += renderTipsJar() + \"\\n\"\n\n out += ufmt.Sprintf(`\u003cstrong style=\"font-size: 1.2em;\"\u003eI am currently in %s, \u003cbr\u003e tip the jar to send me somewhere else!\u003c/strong\u003e`, cities[currentCityIndex].Name)\n \n out += `\u003cbr\u003e\u003cspan style=\"font-size: 1.2em; font-style: italic; margin-top: 10px; display: inline-block;\"\u003eClick the jar, tip in GNOT coins, and watch my background change as I head to a new adventure!\u003c/span\u003e\u003c/p\u003e` + \"\\n\\n\"\n \n\tout += renderSponsors()\n\n\tout += `\u003c/div\u003e\u003c!-- /rows-2 --\u003e` + \"\\n\\n\"\n\n\tout += `\u003c/div\u003e\u003c!-- /jumbotron --\u003e` + \"\\n\"\n\n\treturn out\n}\n\nfunc formatAddress(address string) string {\n if len(address) \u003c= 8 {\n return address\n }\n return address[:4] + \"...\" + address[len(address)-4:]\n}\n\nfunc renderSponsors() string {\n out := `\u003ch3 style=\"margin-top: 5px; margin-bottom: 20px\"\u003eSponsor Leaderboard\u003c/h3\u003e` + \"\\n\"\n\n if len(sponsors) == 0 {\n out += `\u003cp style=\"text-align: center;\"\u003eNo sponsors yet. Be the first to tip the jar!\u003c/p\u003e` + \"\\n\"\n } else {\n numSponsors := len(sponsors)\n if numSponsors \u003e maxSponsors {\n numSponsors = maxSponsors\n }\n\n out += `\u003cul style=\"list-style-type: none; padding: 0; border: 1px solid #ddd; border-radius: 8px; width: 100%; max-width: 300px; margin: 0 auto;\"\u003e` + \"\\n\"\n\n for i := 0; i \u003c numSponsors; i++ {\n sponsor := sponsors[i]\n isLastItem := (i == numSponsors-1)\n\n padding := \"10px 5px\"\n border := \"border-bottom: 1px solid #ddd;\"\n\n if isLastItem {\n padding = \"8px 5px\"\n border = \"\"\n }\n\n out += ufmt.Sprintf(\n `\u003cli style=\"padding: %s; %s text-align: left;\"\u003e\n \u003cstrong style=\"padding-left: 5px;\"\u003e%d. %s\u003c/strong\u003e \n \u003cspan style=\"float: right; padding-right: 5px;\"\u003e%s\u003c/span\u003e\n \u003c/li\u003e`,\n padding, border, i+1, formatAddress(sponsor.Address.String()), sponsor.Amount.String(),\n )\n }\n\n }\n\n return out\n}\n\nfunc renderTipsJar() string {\n\tout := ufmt.Sprintf(`\u003ca href=\"%s\" target=\"_blank\" style=\"display: block; text-decoration: none;\"\u003e`, jarLink) + \"\\n\"\n\n\tout += `\u003cimg src=\"https://i.ibb.co/4TH9zbw/tips-jar.png\" alt=\"Tips Jar\" style=\"width: 300px; height: auto; display: block; margin: 0 auto;\"\u003e` + \"\\n\"\n\n\tout += `\u003c/a\u003e` + \"\\n\"\n\n\treturn out\n}\n"}]},"deposit":""}],"fee":{"gas_wanted":"8000000","gas_fee":"10000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"Aiv9tqagYf7E57BDv13G2FRAt+yvhG915Lu0eJSRl0z4"},"signature":"gBha9lC0HewFreAvzz0q32YDQhG2W3HiZyhU8wTwKNR3Wrz7v6mYBkKQbknlEgrG18sFjTTJCvvvg+yq+lE4rw=="}],"memo":""}
{"msg":[{"@type":"/vm.m_call","caller":"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8","send":"","pkg_path":"gno.land/r/stefann/home","func":"UpdateJarLink","args":["https://gno.studio/connect/view/gno.land/r/stefann/home?network=portal-loop#Donate"]}],"fee":{"gas_wanted":"10000000","gas_fee":"50000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"Aiv9tqagYf7E57BDv13G2FRAt+yvhG915Lu0eJSRl0z4"},"signature":"l1i0d4M7hXEK5UFbYj1geTaI9XCeb1b48Jz+RnHwpKJishsRQekhSJCUGYpG4JEPG7ecBFBlpvy4VybFDvW20Q=="}],"memo":"Called through gno.studio"}
{"msg":[{"@type":"/vm.m_call","caller":"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8","send":"","pkg_path":"gno.land/r/g1d9s3lkk6r4a9yvygwfyf8madha9uxxavl6e73p/home","func":"UpdateAboutMe","args":["`\u003ch3 style=\"font-size: 1.4em;\"\u003eAbout Me\u003c/h3\u003e \u003cp style=\"font-size: 1.1em;\"\u003eHey there\\! I’m Stefan, a student of Computer Science. I’m all about exploring and adventure — whether it’s diving into the latest tech or discovering a new city, I’m always up for the challenge\\!\u003c/p\u003e`","`\u003ch3 style=\"font-size: 1.4em;\"\u003eContributions\u003c/h3\u003e \u003cp style=\"font-size: 1.1em;\"\u003eI\\'m just getting started, but you can follow my journey through Gno.land right \u003ca href=\"https://github.com/gnolang/hackerspace/issues/94\" target=\"_blank\"\u003ehere\u003c/a\u003e ��\u003c/p\u003e`"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"Aiv9tqagYf7E57BDv13G2FRAt+yvhG915Lu0eJSRl0z4"},"signature":"QgGMHjjDOeTkcR0UCvj/p+OuHHJqxl/L0DOg7clQPm9DCgmhGr12KRPzyzTGfRIjuLEIe3FFZfkl0H6REVllfA=="}],"memo":""}
{"msg":[{"@type":"/vm.m_call","caller":"g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8","send":"","pkg_path":"gno.land/r/g1d9s3lkk6r4a9yvygwfyf8madha9uxxavl6e73p/home","func":"UpdateAboutMe","args":["\u003ch3 style=\"font-size: 1.4em;\"\u003eAbout Me\u003c/h3\u003e \u003cp style=\"font-size: 1.1em;\"\u003eHey there\\! I’m Stefan, a student of Computer Science. I’m all about exploring and adventure — whether it’s diving into the latest tech or discovering a new city, I’m always up for the challenge\\!\u003c/p\u003e","\u003ch3 style=\"font-size: 1.4em;\"\u003eContributions\u003c/h3\u003e \u003cp style=\"font-size: 1.1em;\"\u003eI\\'m just getting started, but you can follow my journey through Gno.land right here \u003ca href=\"https://github.com/gnolang/hackerspace/issues/94\" target=\"_blank\"\u003e🔗\u003c/a\u003e\u003c/p\u003e"]}],"fee":{"gas_wanted":"2000000","gas_fee":"1000000ugnot"},"signatures":[{"pub_key":{"@type":"/tm.PubKeySecp256k1","value":"Aiv9tqagYf7E57BDv13G2FRAt+yvhG915Lu0eJSRl0z4"},"signature":"IKg+Hh3zravAQtkCkviHyNaRyl5xnwA8nEHh6rJnWxxk8xHYP0VYJU6EMb5cflimdGuk3F9U0Zf4A9NoAOf5Jg=="}],"memo":""}
diff --git a/portal-loop/extracted/r/stefann/home/home.gno b/portal-loop/extracted/r/stefann/home/home.gno
index 98d41fff..02950f4c 100644
--- a/portal-loop/extracted/r/stefann/home/home.gno
+++ b/portal-loop/extracted/r/stefann/home/home.gno
@@ -1,14 +1,15 @@
package home
import (
- "sort"
- "std"
+ "std"
+ "sort"
- "gno.land/p/demo/ufmt"
+ "gno.land/p/demo/ufmt"
- "gno.land/r/stefann/config"
+ "gno.land/r/stefann/config"
)
+
type City struct {
Name string
URL string
@@ -20,49 +21,49 @@ type Sponsor struct {
}
var (
- pfp string
- cities []City
- currentCityIndex int
- aboutMe [2]string
- jarLink string
- maxSponsors int
- sponsors []Sponsor
+ pfp string
+ cities []City
+ currentCityIndex int
+ aboutMe [2]string
+ jarLink string
+ maxSponsors int
+ sponsors []Sponsor
totalDonated std.Coins
totalDonations int
)
func init() {
- pfp = "https://i.ibb.co/Bc5YNCx/DSC-0095a.jpg"
- cities = []City{
- {Name: "Venice", URL: "https://i.ibb.co/1mcZ7b1/venice.jpg"},
- {Name: "Tokyo", URL: "https://i.ibb.co/wNDJv3H/tokyo.jpg"},
- {Name: "São Paulo", URL: "https://i.ibb.co/yWMq2Sn/sao-paulo.jpg"},
- {Name: "Toronto", URL: "https://i.ibb.co/pb95HJB/toronto.jpg"},
- {Name: "Bangkok", URL: "https://i.ibb.co/pQy3w2g/bangkok.jpg"},
- {Name: "New York", URL: "https://i.ibb.co/6JWLm0h/new-york.jpg"},
- {Name: "Paris", URL: "https://i.ibb.co/q9vf6Hs/paris.jpg"},
- {Name: "Kandersteg", URL: "https://i.ibb.co/60DzywD/kandersteg.jpg"},
- {Name: "Rothenburg", URL: "https://i.ibb.co/cr8d2rQ/rothenburg.jpg"},
- {Name: "Capetown", URL: "https://i.ibb.co/bPGn0v3/capetown.jpg"},
- {Name: "Sydney", URL: "https://i.ibb.co/TBNzqfy/sydney.jpg"},
- {Name: "Oeschinen Lake", URL: "https://i.ibb.co/QJQwp2y/oeschinen-lake.jpg"},
- {Name: "Barra Grande", URL: "https://i.ibb.co/z4RXKc1/barra-grande.jpg"},
- {Name: "London", URL: "https://i.ibb.co/CPGtvgr/london.jpg"},
- }
- currentCityIndex = 0
- jarLink = "https://TODO"
- maxSponsors = 5
- aboutMe = [2]string{
- `About Me
+ pfp = "https://i.ibb.co/Bc5YNCx/DSC-0095a.jpg"
+ cities = []City{
+ {Name: "Venice", URL: "https://i.ibb.co/1mcZ7b1/venice.jpg"},
+ {Name: "Tokyo", URL: "https://i.ibb.co/wNDJv3H/tokyo.jpg"},
+ {Name: "São Paulo", URL: "https://i.ibb.co/yWMq2Sn/sao-paulo.jpg"},
+ {Name: "Toronto", URL: "https://i.ibb.co/pb95HJB/toronto.jpg"},
+ {Name: "Bangkok", URL: "https://i.ibb.co/pQy3w2g/bangkok.jpg"},
+ {Name: "New York", URL: "https://i.ibb.co/6JWLm0h/new-york.jpg"},
+ {Name: "Paris", URL: "https://i.ibb.co/q9vf6Hs/paris.jpg"},
+ {Name: "Kandersteg", URL: "https://i.ibb.co/60DzywD/kandersteg.jpg"},
+ {Name: "Rothenburg", URL: "https://i.ibb.co/cr8d2rQ/rothenburg.jpg"},
+ {Name: "Capetown", URL: "https://i.ibb.co/bPGn0v3/capetown.jpg"},
+ {Name: "Sydney", URL: "https://i.ibb.co/TBNzqfy/sydney.jpg"},
+ {Name: "Oeschinen Lake", URL: "https://i.ibb.co/QJQwp2y/oeschinen-lake.jpg"},
+ {Name: "Barra Grande", URL: "https://i.ibb.co/z4RXKc1/barra-grande.jpg"},
+ {Name: "London", URL: "https://i.ibb.co/CPGtvgr/london.jpg"},
+ }
+ currentCityIndex = 0
+ jarLink = "https://TODO"
+ maxSponsors = 5
+ aboutMe = [2]string{
+ `About Me
Hey there! I’m Stefan, a student of Computer Science. I’m all about exploring and adventure — whether it’s diving into the latest tech or discovering a new city, I’m always up for the challenge!
`,
- `Contributions
+ `Contributions
I'm just getting started, but you can follow my journey through Gno.land right here 🔗
`,
- }
+ }
}
func UpdateMaxSponsors(newMax int) {
- config.AssertAuthorized()
- maxSponsors = newMax
+ config.AssertAuthorized()
+ maxSponsors = newMax
}
func UpdateCities(newCities []City) {
@@ -87,39 +88,39 @@ func UpdateAboutMe(col1, col2 string) {
}
func Donate() {
- address := std.GetOrigCaller()
- amount := std.GetOrigSend()
-
- if amount.AmountOf("ugnot") == 0 {
- panic("Donation must include GNOT")
- }
-
- found := false
-
- for i, sponsor := range sponsors {
- if sponsor.Address == address {
- sponsors[i].Amount = sponsors[i].Amount.Add(amount)
- found = true
- break
- }
- }
-
- if !found {
- sponsors = append(sponsors, Sponsor{Address: address, Amount: amount})
- }
-
- totalDonated = totalDonated.Add(amount)
-
- totalDonations++
-
- sortSponsorsByAmount()
-
- if len(cities) > 0 {
- currentCityIndex++
- if currentCityIndex >= len(cities) {
- currentCityIndex = 0
- }
- }
+ address := std.GetOrigCaller()
+ amount := std.GetOrigSend()
+
+ if amount.AmountOf("ugnot") == 0 {
+ panic("Donation must include GNOT")
+ }
+
+ found := false
+
+ for i, sponsor := range sponsors {
+ if sponsor.Address == address {
+ sponsors[i].Amount = sponsors[i].Amount.Add(amount)
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ sponsors = append(sponsors, Sponsor{Address: address, Amount: amount})
+ }
+
+ totalDonated = totalDonated.Add(amount)
+
+ totalDonations++
+
+ sortSponsorsByAmount()
+
+ if len(cities) > 0 {
+ currentCityIndex++
+ if currentCityIndex >= len(cities) {
+ currentCityIndex = 0
+ }
+ }
}
type SponsorSlice []Sponsor
@@ -204,10 +205,10 @@ func renderTips() string {
out += renderTipsJar() + "\n"
- out += ufmt.Sprintf(`I am currently in %s,
tip the jar to send me somewhere else!`, cities[currentCityIndex].Name)
-
- out += `
Click the jar, tip in GNOT coins, and watch my background change as I head to a new adventure!
` + "\n\n"
-
+ out += ufmt.Sprintf(`I am currently in %s,
tip the jar to send me somewhere else!`, cities[currentCityIndex].Name)
+
+ out += `
Click the jar, tip in GNOT coins, and watch my background change as I head to a new adventure!` + "\n\n"
+
out += renderSponsors()
out += `` + "\n\n"
@@ -218,49 +219,49 @@ func renderTips() string {
}
func formatAddress(address string) string {
- if len(address) <= 8 {
- return address
- }
- return address[:4] + "..." + address[len(address)-4:]
+ if len(address) <= 8 {
+ return address
+ }
+ return address[:4] + "..." + address[len(address)-4:]
}
func renderSponsors() string {
- out := `Sponsor Leaderboard
` + "\n"
+ out := `Sponsor Leaderboard
` + "\n"
- if len(sponsors) == 0 {
- out += `No sponsors yet. Be the first to tip the jar!
` + "\n"
- } else {
- numSponsors := len(sponsors)
- if numSponsors > maxSponsors {
- numSponsors = maxSponsors
- }
+ if len(sponsors) == 0 {
+ out += `No sponsors yet. Be the first to tip the jar!
` + "\n"
+ } else {
+ numSponsors := len(sponsors)
+ if numSponsors > maxSponsors {
+ numSponsors = maxSponsors
+ }
- out += `` + "\n"
+ out += `` + "\n"
- for i := 0; i < numSponsors; i++ {
- sponsor := sponsors[i]
- isLastItem := (i == numSponsors-1)
+ for i := 0; i < numSponsors; i++ {
+ sponsor := sponsors[i]
+ isLastItem := (i == numSponsors-1)
- padding := "10px 5px"
- border := "border-bottom: 1px solid #ddd;"
+ padding := "10px 5px"
+ border := "border-bottom: 1px solid #ddd;"
- if isLastItem {
- padding = "8px 5px"
- border = ""
- }
+ if isLastItem {
+ padding = "8px 5px"
+ border = ""
+ }
- out += ufmt.Sprintf(
- `-
+ out += ufmt.Sprintf(
+ `
-
%d. %s
%s
`,
- padding, border, i+1, formatAddress(sponsor.Address.String()), sponsor.Amount.String(),
- )
- }
+ padding, border, i+1, formatAddress(sponsor.Address.String()), sponsor.Amount.String(),
+ )
+ }
- }
+ }
- return out
+ return out
}
func renderTipsJar() string {
diff --git a/portal-loop/extracted/r/stefann/home/home_test.gno b/portal-loop/extracted/r/stefann/home/home_test.gno
new file mode 100644
index 00000000..ca146b9e
--- /dev/null
+++ b/portal-loop/extracted/r/stefann/home/home_test.gno
@@ -0,0 +1,291 @@
+package home
+
+import (
+ "std"
+ "strings"
+ "testing"
+
+ "gno.land/p/demo/avl"
+ "gno.land/p/demo/testutils"
+)
+
+func TestUpdatePFP(t *testing.T) {
+ var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8")
+ std.TestSetOrigCaller(owner)
+
+ profile.pfp = ""
+
+ UpdatePFP("https://example.com/pic.png")
+
+ if profile.pfp != "https://example.com/pic.png" {
+ t.Fatalf("expected pfp to be https://example.com/pic.png, got %s", profile.pfp)
+ }
+}
+
+func TestUpdateAboutMe(t *testing.T) {
+ var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8")
+ std.TestSetOrigCaller(owner)
+
+ profile.aboutMe = []string{}
+
+ UpdateAboutMe("This is my new bio.|I love coding!")
+
+ expected := []string{"This is my new bio.", "I love coding!"}
+
+ if len(profile.aboutMe) != len(expected) {
+ t.Fatalf("expected aboutMe to have length %d, got %d", len(expected), len(profile.aboutMe))
+ }
+
+ for i := range profile.aboutMe {
+ if profile.aboutMe[i] != expected[i] {
+ t.Fatalf("expected aboutMe[%d] to be %s, got %s", i, expected[i], profile.aboutMe[i])
+ }
+ }
+}
+
+func TestUpdateCities(t *testing.T) {
+ var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8")
+ std.TestSetOrigCaller(owner)
+
+ travel.cities = []City{}
+
+ newCities := []City{
+ {Name: "Berlin", URL: "https://example.com/berlin.jpg"},
+ {Name: "Vienna", URL: "https://example.com/vienna.jpg"},
+ }
+
+ UpdateCities(newCities)
+
+ if len(travel.cities) != 2 {
+ t.Fatalf("expected 2 cities, got %d", len(travel.cities))
+ }
+
+ if travel.cities[0].Name != "Berlin" || travel.cities[1].Name != "Vienna" {
+ t.Fatalf("expected cities to be updated to Berlin and Vienna, got %+v", travel.cities)
+ }
+}
+
+func TestUpdateJarLink(t *testing.T) {
+ var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8")
+ std.TestSetOrigCaller(owner)
+
+ travel.jarLink = ""
+
+ UpdateJarLink("https://example.com/jar")
+
+ if travel.jarLink != "https://example.com/jar" {
+ t.Fatalf("expected jarLink to be https://example.com/jar, got %s", travel.jarLink)
+ }
+}
+
+func TestUpdateMaxSponsors(t *testing.T) {
+ var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8")
+ std.TestSetOrigCaller(owner)
+
+ sponsorship.maxSponsors = 0
+
+ UpdateMaxSponsors(10)
+
+ if sponsorship.maxSponsors != 10 {
+ t.Fatalf("expected maxSponsors to be 10, got %d", sponsorship.maxSponsors)
+ }
+
+ defer func() {
+ if r := recover(); r == nil {
+ t.Fatalf("expected panic for setting maxSponsors to 0")
+ }
+ }()
+ UpdateMaxSponsors(0)
+}
+
+func TestAddCities(t *testing.T) {
+ var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8")
+ std.TestSetOrigCaller(owner)
+
+ travel.cities = []City{}
+
+ AddCities(City{Name: "Berlin", URL: "https://example.com/berlin.jpg"})
+
+ if len(travel.cities) != 1 {
+ t.Fatalf("expected 1 city, got %d", len(travel.cities))
+ }
+ if travel.cities[0].Name != "Berlin" || travel.cities[0].URL != "https://example.com/berlin.jpg" {
+ t.Fatalf("expected city to be Berlin, got %+v", travel.cities[0])
+ }
+
+ AddCities(
+ City{Name: "Paris", URL: "https://example.com/paris.jpg"},
+ City{Name: "Tokyo", URL: "https://example.com/tokyo.jpg"},
+ )
+
+ if len(travel.cities) != 3 {
+ t.Fatalf("expected 3 cities, got %d", len(travel.cities))
+ }
+ if travel.cities[1].Name != "Paris" || travel.cities[2].Name != "Tokyo" {
+ t.Fatalf("expected cities to be Paris and Tokyo, got %+v", travel.cities[1:])
+ }
+}
+
+func TestAddAboutMeRows(t *testing.T) {
+ var owner = std.Address("g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8")
+ std.TestSetOrigCaller(owner)
+
+ profile.aboutMe = []string{}
+
+ AddAboutMeRows("I love exploring new technologies!")
+
+ if len(profile.aboutMe) != 1 {
+ t.Fatalf("expected 1 aboutMe row, got %d", len(profile.aboutMe))
+ }
+ if profile.aboutMe[0] != "I love exploring new technologies!" {
+ t.Fatalf("expected first aboutMe row to be 'I love exploring new technologies!', got %s", profile.aboutMe[0])
+ }
+
+ AddAboutMeRows("Travel is my passion!", "Always learning.")
+
+ if len(profile.aboutMe) != 3 {
+ t.Fatalf("expected 3 aboutMe rows, got %d", len(profile.aboutMe))
+ }
+ if profile.aboutMe[1] != "Travel is my passion!" || profile.aboutMe[2] != "Always learning." {
+ t.Fatalf("expected aboutMe rows to be 'Travel is my passion!' and 'Always learning.', got %+v", profile.aboutMe[1:])
+ }
+}
+
+func TestDonate(t *testing.T) {
+ var user = testutils.TestAddress("user")
+ std.TestSetOrigCaller(user)
+
+ sponsorship.sponsors = avl.NewTree()
+ sponsorship.DonationsCount = 0
+ sponsorship.sponsorsCount = 0
+ travel.currentCityIndex = 0
+
+ coinsSent := std.NewCoins(std.NewCoin("ugnot", 500))
+ std.TestSetOrigSend(coinsSent, std.NewCoins())
+ Donate()
+
+ existingAmount, exists := sponsorship.sponsors.Get(string(user))
+ if !exists {
+ t.Fatalf("expected sponsor to be added, but it was not found")
+ }
+
+ if existingAmount.(std.Coins).AmountOf("ugnot") != 500 {
+ t.Fatalf("expected donation amount to be 500ugnot, got %d", existingAmount.(std.Coins).AmountOf("ugnot"))
+ }
+
+ if sponsorship.DonationsCount != 1 {
+ t.Fatalf("expected DonationsCount to be 1, got %d", sponsorship.DonationsCount)
+ }
+
+ if sponsorship.sponsorsCount != 1 {
+ t.Fatalf("expected sponsorsCount to be 1, got %d", sponsorship.sponsorsCount)
+ }
+
+ if travel.currentCityIndex != 1 {
+ t.Fatalf("expected currentCityIndex to be 1, got %d", travel.currentCityIndex)
+ }
+
+ coinsSent = std.NewCoins(std.NewCoin("ugnot", 300))
+ std.TestSetOrigSend(coinsSent, std.NewCoins())
+ Donate()
+
+ existingAmount, exists = sponsorship.sponsors.Get(string(user))
+ if !exists {
+ t.Fatalf("expected sponsor to exist after second donation, but it was not found")
+ }
+
+ if existingAmount.(std.Coins).AmountOf("ugnot") != 800 {
+ t.Fatalf("expected total donation amount to be 800ugnot, got %d", existingAmount.(std.Coins).AmountOf("ugnot"))
+ }
+
+ if sponsorship.DonationsCount != 2 {
+ t.Fatalf("expected DonationsCount to be 2 after second donation, got %d", sponsorship.DonationsCount)
+ }
+
+ if travel.currentCityIndex != 2 {
+ t.Fatalf("expected currentCityIndex to be 2 after second donation, got %d", travel.currentCityIndex)
+ }
+}
+
+func TestGetTopSponsors(t *testing.T) {
+ var user = testutils.TestAddress("user")
+ std.TestSetOrigCaller(user)
+
+ sponsorship.sponsors = avl.NewTree()
+ sponsorship.sponsorsCount = 0
+
+ sponsorship.sponsors.Set("g1address1", std.NewCoins(std.NewCoin("ugnot", 300)))
+ sponsorship.sponsors.Set("g1address2", std.NewCoins(std.NewCoin("ugnot", 500)))
+ sponsorship.sponsors.Set("g1address3", std.NewCoins(std.NewCoin("ugnot", 200)))
+ sponsorship.sponsorsCount = 3
+
+ topSponsors := GetTopSponsors()
+
+ if len(topSponsors) != 3 {
+ t.Fatalf("expected 3 sponsors, got %d", len(topSponsors))
+ }
+
+ if topSponsors[0].Address.String() != "g1address2" || topSponsors[0].Amount.AmountOf("ugnot") != 500 {
+ t.Fatalf("expected top sponsor to be g1address2 with 500ugnot, got %s with %dugnot", topSponsors[0].Address.String(), topSponsors[0].Amount.AmountOf("ugnot"))
+ }
+
+ if topSponsors[1].Address.String() != "g1address1" || topSponsors[1].Amount.AmountOf("ugnot") != 300 {
+ t.Fatalf("expected second sponsor to be g1address1 with 300ugnot, got %s with %dugnot", topSponsors[1].Address.String(), topSponsors[1].Amount.AmountOf("ugnot"))
+ }
+
+ if topSponsors[2].Address.String() != "g1address3" || topSponsors[2].Amount.AmountOf("ugnot") != 200 {
+ t.Fatalf("expected third sponsor to be g1address3 with 200ugnot, got %s with %dugnot", topSponsors[2].Address.String(), topSponsors[2].Amount.AmountOf("ugnot"))
+ }
+}
+
+func TestGetTotalDonations(t *testing.T) {
+ var user = testutils.TestAddress("user")
+ std.TestSetOrigCaller(user)
+
+ sponsorship.sponsors = avl.NewTree()
+ sponsorship.sponsorsCount = 0
+
+ sponsorship.sponsors.Set("g1address1", std.NewCoins(std.NewCoin("ugnot", 300)))
+ sponsorship.sponsors.Set("g1address2", std.NewCoins(std.NewCoin("ugnot", 500)))
+ sponsorship.sponsors.Set("g1address3", std.NewCoins(std.NewCoin("ugnot", 200)))
+ sponsorship.sponsorsCount = 3
+
+ totalDonations := GetTotalDonations()
+
+ if totalDonations != 1000 {
+ t.Fatalf("expected total donations to be 1000ugnot, got %dugnot", totalDonations)
+ }
+}
+
+func TestRender(t *testing.T) {
+ travel.currentCityIndex = 0
+ travel.cities = []City{
+ {Name: "Venice", URL: "https://example.com/venice.jpg"},
+ {Name: "Paris", URL: "https://example.com/paris.jpg"},
+ }
+
+ output := Render("")
+
+ expectedCity := "Venice"
+ if !strings.Contains(output, expectedCity) {
+ t.Fatalf("expected output to contain city name '%s', got %s", expectedCity, output)
+ }
+
+ expectedURL := "https://example.com/venice.jpg"
+ if !strings.Contains(output, expectedURL) {
+ t.Fatalf("expected output to contain city URL '%s', got %s", expectedURL, output)
+ }
+
+ travel.currentCityIndex = 1
+ output = Render("")
+
+ expectedCity = "Paris"
+ if !strings.Contains(output, expectedCity) {
+ t.Fatalf("expected output to contain city name '%s', got %s", expectedCity, output)
+ }
+
+ expectedURL = "https://example.com/paris.jpg"
+ if !strings.Contains(output, expectedURL) {
+ t.Fatalf("expected output to contain city URL '%s', got %s", expectedURL, output)
+ }
+}
diff --git a/portal-loop/extracted/r/stefann/registry/pkg_metadata.json b/portal-loop/extracted/r/stefann/registry/pkg_metadata.json
new file mode 100644
index 00000000..92e977de
--- /dev/null
+++ b/portal-loop/extracted/r/stefann/registry/pkg_metadata.json
@@ -0,0 +1 @@
+{"creator":"g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5","deposit":""}
\ No newline at end of file
diff --git a/portal-loop/extracted/r/stefann/registry/registry.gno b/portal-loop/extracted/r/stefann/registry/registry.gno
new file mode 100644
index 00000000..6f56d105
--- /dev/null
+++ b/portal-loop/extracted/r/stefann/registry/registry.gno
@@ -0,0 +1,51 @@
+package registry
+
+import (
+ "errors"
+ "std"
+
+ "gno.land/p/demo/ownable"
+)
+
+var (
+ mainAddr std.Address
+ backupAddr std.Address
+ owner *ownable.Ownable
+)
+
+func init() {
+ mainAddr = "g1sd5ezmxt4rwpy52u6wl3l3y085n8x0p6nllxm8"
+ backupAddr = "g13awn2575t8s2vf3svlprc4dg0e9z5wchejdxk8"
+
+ owner = ownable.NewWithAddress(mainAddr)
+}
+
+func MainAddr() std.Address {
+ return mainAddr
+}
+
+func BackupAddr() std.Address {
+ return backupAddr
+}
+
+func SetMainAddr(addr std.Address) error {
+ if !addr.IsValid() {
+ return errors.New("config: invalid address")
+ }
+
+ owner.AssertCallerIsOwner()
+
+ mainAddr = addr
+ return nil
+}
+
+func SetBackupAddr(addr std.Address) error {
+ if !addr.IsValid() {
+ return errors.New("config: invalid address")
+ }
+
+ owner.AssertCallerIsOwner()
+
+ backupAddr = addr
+ return nil
+}