Transaction
771690B0C37851…034A084B873C
Block 321,635 · index 0 · indexed
Summary
- Hash
- 771690B0C37851E51A510ED9B15C641FCA8E69FA8314BABC899B034A084B873C
- Block
- 321,635
- Size
- 12198 bytes
- Gas used
- 15,869,010 / 30,000,000
- Fee
- 30000 ugnot
- Status
- success
Messages
Arguments · 11
- #1avatar
- #2README.md
- #3# Settler avatars This realm's one job is to show off a Settler as a small picture, for anyone who asks. If an account owns a Settler, this is the place that draws it. This realm doesn't own or store anything itself; it just looks up a Settler and draws it, on the spot, whenever it's asked to. Keeping it separate means any other realm or web app on Gno.land can show someone's Settler next to their name, in a forum post, a leaderboard, or a profile, without having to know anything about how the art is put together. They just ask this realm for the picture. The [Settlers nft realm](/r/g17khqpukees4237dtn3astzapmp462vjhsz6st4/settlers/nft) is what actually hands out the gnomes and keeps track of who owns what. ## Usage Example This gives you a Settler image next to the address, ready to write into your own Markdown: ```go import "gno.land/r/g17khqpukees4237dtn3astzapmp462vjhsz6st4/settlers/avatar" func RenderUser(addr address) string { // Size: 32x32 px, Transparent background: true return avatar.Markdown(addr, 32, true) + " " + addr.String() } ``` `Markdown` returns nothing if the address has no Settler, so there's no need to check first. `SVG` and `URI` work the same way, and give you the raw picture instead, if that's what you need.
- #4avatar.gno
- #5package avatar import ( "chain" "chain/runtime" "strings" "gno.land/p/g17khqpukees4237dtn3astzapmp462vjhsz6st4/settlers/nft/v0" "gno.land/p/nt/grc721/v0" "gno.land/p/nt/mdalert/v0" "gno.land/p/nt/ownable/v0" settlers "gno.land/r/g17khqpukees4237dtn3astzapmp462vjhsz6st4/settlers/nft" ) const ( // MinSize is the minimum size allowed for an avatar in pixels. MinSize = 32 // MaxSize is the maximum size allowed for an avatar in pixels. MaxSize = 2048 ) var ( // Notice is a realm-wide message the admin can post at any time, shown at the top of the page. // It never affects SVG, URI or Markdown, it is purely informational. Notice string // NoticeType is the style Notice is shown as. NoticeType mdalert.Type // Realm path is the path of this realm. RealmPath string // Admin account for the realm, only valid to post or clear realm-wide notices. admin *ownable.Ownable // Go snippet is the Go usage example. goSnippet string // Gnokey snippet is the gnokey example. gnokeySnippet string ) func init(cur realm) { RealmPath = strings.TrimPrefix(cur.PkgPath(), runtime.ChainDomain()) admin = ownable.NewWithAddress(cur.Previous().Address()) pkgPath := runtime.ChainDomain() + RealmPath goSnippet = "import \"" + pkgPath + "\"\n\n" + "func RenderUser(addr address) string {\n\treturn avatar.Markdown(addr, 32, true) " + "+ \" \" + addr.String()\n}\n" gnokeySnippet = "gnokey query vm/qrender \\\n" + " -remote <remote> \\\n" + " -data \"" + pkgPath + ":svg/{address}?size=256\" \\\n" + " | tail -n +2 | sed '1s/^data: //' > settler.svg\n" } // Admin returns the address of the admin account. func Admin() address { return admin.Owner() } // TransferAdmin makes another account the admin. Only the admin can call it. func TransferAdmin(cur realm, newAdmin address) { assertIsCurrent(cur) if err := admin.TransferOwnership(0, cur, newAdmin); err != nil { panic(err) } } // SetNotice posts a realm-wide message, shown at the top of the page. func SetNotice(cur realm, alertType mdalert.Type, msg string) { assertIsCurrent(cur) admin.AssertOwnedBy(cur.Previous().Address()) assertIsValidAlertType(alertType) NoticeType = alertType Notice = msg chain.Emit("NoticeSet", "type", string(alertType)) } // ClearNotice removes the current notice. func ClearNotice(cur realm) { assertIsCurrent(cur) admin.AssertOwnedBy(cur.Previous().Address()) NoticeType = "" Notice = "" chain.Emit("NoticeCleared") } // SVG returns the SVG document of the Settler of an address, or an empty text if it has none. // // The size is the width and height in pixels of the square image, from 32 to 2048 (multiples of 32 keep // every art pixel as a whole number of screen pixels). A size of 0 or less leaves out the width and the // height, so that the image scales to its container. If transparent is true, the image has no background. func SVG(addr address, size int, transparent bool) string { if tid, traits := lookup(addr); tid != "" { return nft.RenderSVG(traits, options(size, transparent)) } return "" } // URI returns the image of the Settler of an address as a "data:image/svg+xml;charset=utf-8," URI, that can // be used as the source of an image or as the address of a Markdown image, or an empty text if it has none. // The size and transparent are the same as in SVG. func URI(addr address, size int, transparent bool) string { if tid, traits := lookup(addr); tid != "" { return nft.RenderDataURI(traits, options(size, transparent)) } return "" } // Markdown returns the avatar of an address as a Markdown image, or an empty text if it has none: // //  // // The size and transparent are the same as in SVG. func Markdown(addr address, size int, transparent bool) string { if tid, traits := lookup(addr); tid != "" { uri := nft.RenderDataURI(traits, options(size, transparent)) return "" } return "" } // lookup returns the ID and traits of the Settler of an address, or an empty ID and a zero-value Traits if it // has none. func lookup(addr address) (grc721.TokenID, nft.Traits) { tid := settlers.TokenOf(addr) if tid == "" { return "", nft.Traits{} } traits, err := settlers.TraitsOf(tid) if err != nil { return "", nft.Traits{} } return tid, traits } // options builds the nft.Options for an image; size is clamped between MinSize and MaxSize (0 or less is // left as 0, so the image has no fixed size and scales to its container), and transparent controls whether // the background is drawn. func options(size int, transparent bool) nft.Options { switch { case size <= 0: size = 0 case size < MinSize: size = MinSize case size > MaxSize: size = MaxSize } return nft.Options{ Size: size, NoBackground: transparent, } } func assertIsCurrent(cur realm) { if !cur.IsCurrent() { panic("unauthorized") } } func assertIsValidAlertType(t mdalert.Type) { switch t { case mdalert.TypeCaution, mdalert.TypeInfo, mdalert.TypeNote, mdalert.TypeSuccess, mdalert.TypeTip, mdalert.TypeWarning: return } panic("invalid notice type") }
- #6gnomod.toml
- #7module = "gno.land/r/g17khqpukees4237dtn3astzapmp462vjhsz6st4/settlers/avatar" gno = "0.9"
- #8render.gno
- #9package avatar import ( "strconv" "gno.land/p/moul/md/v0" "gno.land/p/moul/mdtable/v0" "gno.land/p/nt/grc721/v0" "gno.land/p/nt/mdalert/v0" "gno.land/p/nt/mux/v0" settlers "gno.land/r/g17khqpukees4237dtn3astzapmp462vjhsz6st4/settlers/nft" ) const ( // Sample size is the size in pixels of the avatars in the pages. sampleSize = 48 // Samples is how many avatars the home page shows. samples = 6 ) // Render renders the realm. // // By default renders what this realm is, how to use it, and some avatars. // // Paths: // - `svg/{address}`: The SVG of the Settler of an address. // - `md/{address}`: The Markdown that Markdown returns for an address, rendered, with the same parameters. func Render(path string) string { router := mux.NewRouter() router.NotFoundHandler = func(res *mux.ResponseWriter, _ *mux.Request) { res.Write("Path not found") } router.HandleFunc("", renderHome) router.HandleFunc("svg/{addr}", renderSVG) router.HandleFunc("md/{addr}", renderMarkdown) return router.Render(path) } func renderHome(res *mux.ResponseWriter, _ *mux.Request) { res.Write(md.H1("Settler avatars")) if Notice != "" { res.Write(mdalert.New(NoticeType, "Message", Notice, false).String() + "\n\n") } res.Write(md.Paragraph("This realm draws a Settler as a picture give an address.")) res.Write(md.Paragraph( "It doesn't own the Settler, or store anything about it, it just looks one up and draws it, " + "fresh, every time it's asked. That's on purpose, keeping the drawing separate from the main " + "collection means any other realm or web app on Gno.land, a forum, a leaderboard, a profile " + "page, can show a Settler next to an address without needing to understand pixel art, traits, " + "or how any of it's put together.", )) res.Write(md.Paragraph( "The " + md.Link("Settlers nft", settlers.RealmPath) + " realm is still the one that actually mints " + "the Settlers and keeps track of who owns what.", )) res.Write(md.Paragraph("Realm paths:")) pathsTable := mdtable.Table{Headers: []string{"Path", "Description"}} pathsTable.Append([]string{ md.InlineCode("svg/{address}"), "The SVG of the Settler of an address.", }) pathsTable.Append([]string{ md.InlineCode("md/{address}"), "The Markdown that Markdown returns for an address, rendered, with the same parameters.", }) res.Write(pathsTable.String() + "\n") res.Write(md.H2("From a realm")) res.Write(md.Paragraph( "Call it directly from another realm, for example to show a Settler " + "next to an address in your own " + md.InlineCode("Render") + " function:", )) res.Write(md.LanguageCodeBlock("go", goSnippet)) table := mdtable.Table{Headers: []string{"Function", "Description"}} table.Append([]string{ "Markdown(addr, size, transparent)", "The image, or an empty text if there is none", }) table.Append([]string{ "SVG(addr, size, transparent)", "The SVG document, or an empty text if there is none", }) table.Append([]string{ "URI(addr, size, transparent)", "The same image as a data URI", }) res.Write(table.String() + "\n") res.Write(md.Paragraph( "The size is in pixels, from 32 to 2048, and 0 leaves it to the container. " + "With transparent the image has no background. Every call draws the image again and " + "costs gas (about 6 to 8 million), so use it in `Render` and not in a transaction.", )) res.Write(md.H2("From a web app")) res.Write(md.Paragraph( "Ask the node for the page of an address, with `vm/qrender`, and use what it returns as an image: " + md.InlineCode(RealmPath+":svg/{address}?size=64&transparent=1") + ". It is empty if the address has no Settler.", )) res.Write(md.Paragraph("With gnokey, from a terminal, save it straight to a file:")) res.Write(md.LanguageCodeBlock("sh", gnokeySnippet)) if n := settlers.TotalSupply(); n > 0 { res.Write(md.H2("Last Settlers")) for id := n; id > 0 && id > n-samples; id-- { tid := grc721.TokenID(strconv.FormatInt(id, 10)) owner, err := settlers.OwnerOf(tid) if err != nil { continue } tokenPath := settlers.RealmPath + ":token/" + string(tid) table := mdtable.Table{Headers: []string{"", ""}} table.Append([]string{ Markdown(owner, sampleSize, false), md.Link(owner.String(), tokenPath), }) res.Write(table.String() + "\n") } } } func renderSVG(res *mux.ResponseWriter, req *mux.Request) { svg := SVG(address(req.GetVar("addr")), sizeParam(req), transparentParam(req)) res.Write(svg) } func renderMarkdown(res *mux.ResponseWriter, req *mux.Request) { size := sizeParam(req) if size == 0 { size = sampleSize } addr := req.GetVar("addr") if !address(addr).IsValid() { res.Write(md.Paragraph("Not a valid address.")) res.Write(md.Paragraph(md.Link("< Home", RealmPath))) return } res.Write(md.H1("Avatar of " + md.InlineCode(addr))) res.Write(md.Paragraph(Markdown(address(addr), size, transparentParam(req)))) res.Write(md.Paragraph(md.Link("< Home", RealmPath))) } func sizeParam(req *mux.Request) int { size, err := strconv.Atoi(req.Query.Get("size")) if err != nil { return 0 } return size } func transparentParam(req *mux.Request) bool { v := req.Query.Get("transparent") return v == "1" || v == "true" }
- #10/gno.MemPackageType
- #11 MPUserAll
Result log
msg:0,success:true,log:,events:[]