Transaction

10B85909EDE5BE…5C943DA4DFCB

Block 271,558 · index 0 · indexed

Summary

Hash
10B85909EDE5BEC274F43984449564CA0A1D58741CA7C5A8DFA25C943DA4DFCB
Block
271,558
Size
48877 bytes
Gas used
67,591,249 / 120,000,000
Fee
180000ugnot
Status
success

Messages

#1AddPackagegno.land/r/samcrew/home17 arguments
Attached funds
32000000ugnot

Arguments · 17

  1. #1home
  2. #2banner.gno
  3. #3package home import "encoding/base64" // This file is shared, unchanged, by every Samouraï home realm, so the pages // keep one visual identity. What differs lives in content.gno. // toriiMask is the Samouraï Coop mark, redrawn as vectors: a torii gate cut // out of a full circle, in a 480x480 box. Everything painted through the mask // takes the shape of the mark. const toriiMask = `<mask id="torii"><circle cx="238" cy="240" r="240" fill="#fff"/><g fill="#000">` + `<path d="M-10 90 Q238 164 490 90 L490 140 Q238 190 -10 140 Z"/>` + `<path d="M102 140 L165 140 L162 206 L96 206 Z"/>` + `<path d="M302 140 L364 140 L370 206 L304 206 Z"/>` + `<rect x="-10" y="205" width="500" height="41"/>` + `<path d="M89 245 L159 245 L145 490 L55 490 Z"/>` + `<rect x="-10" y="274" width="100" height="32"/>` + `<path d="M307 245 L377 245 L411 490 L321 490 Z"/>` + `<path d="M382 296 L490 398 L490 455 L390 358 Z"/>` + `</g></mask>` type heroText struct { kicker string // small red caps line above the title title string subtitle string tagline string // red monospace line footline string // grey monospace line alt string } // heroSVG draws the page banner: the mark set in a red sun on the right, the // text stack on the left. func heroSVG(h heroText) string { return `<svg xmlns="http://www.w3.org/2000/svg" width="960" height="300" viewBox="0 0 960 300">` + `<defs><linearGradient id="bg" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#0e0e0e"/><stop offset="1" stop-color="#2a0c10"/></linearGradient>` + toriiMask + `</defs>` + `<rect width="960" height="300" rx="18" fill="url(#bg)"/>` + `<g stroke="#ffffff" stroke-opacity=".05"><path d="M0 60H960M0 120H960M0 180H960M0 240H960"/></g>` + `<circle cx="790" cy="150" r="128" fill="#e63946"/>` + `<g transform="translate(686 46) scale(.4333)"><rect width="480" height="480" fill="#f4efe6" mask="url(#torii)"/></g>` + `<text x="56" y="78" font-family="ui-monospace,Menlo,monospace" font-size="13" letter-spacing="3" fill="#e63946">` + xmlText(h.kicker) + `</text>` + `<text x="52" y="160" font-family="system-ui,-apple-system,Segoe UI,sans-serif" font-weight="800" font-size="76" fill="#ffffff">` + xmlText(h.title) + `</text>` + `<text x="56" y="200" font-family="system-ui,-apple-system,Segoe UI,sans-serif" font-size="22" fill="#e8e2d8">` + xmlText(h.subtitle) + `</text>` + `<text x="56" y="236" font-family="ui-monospace,Menlo,monospace" font-size="16" fill="#e63946">` + xmlText(h.tagline) + `</text>` + `<text x="56" y="266" font-family="ui-monospace,Menlo,monospace" font-size="12" fill="#8a8a8a">` + xmlText(h.footline) + `</text>` + `</svg>` } func heroImage() string { return "![" + mdText(hero.alt) + "](data:image/svg+xml;base64," + base64.StdEncoding.EncodeToString([]byte(heroSVG(hero))) + ")" }
  4. #4content.gno
  5. #5package home // defaultCrew seeds the roster at deploy time: everyone who contributed to // gno with samcrew over the last four years, team by team, in the order the // cards appear. Former members are in the alumni team, drawn in grey. Cards link to a public page until LinkProfile ties a member to their // gno.land identity. const ( gh = "https://github.com/" aboutCrew = "https://www.samourai.world/about/" ) func defaultCrew() []struct{ id, handle, role, team, url string } { return []struct{ id, handle, role, team, url string }{ {"zxxma", "zôÖma", "Founder, CEO & CTO", "engineering", gh + "zxxma"}, {"gh0st", "gh0st", "GnoVM core · 62 PRs in gno", "engineering", gh + "omarsy"}, {"mikecito", "mikecito", "GnoVM · 39 PRs in gno", "engineering", gh + "Villaquiranm"}, {"mikael", "Mikael Vallenet", "gno.land & GnoVM · 41 PRs", "engineering", gh + "mvallenet"}, {"lours", "lours", "Validators & DevOps", "engineering", gh + "louis14448"}, {"david", "david", "DevRel · 70 PRs in gno", "engineering", gh + "davd-gzl"}, {"yohann", "Yohann G.", "Co-founder · Film director", "media", aboutCrew}, {"fanny", "Fanny Z.", "Film maker · Photographer", "media", aboutCrew}, {"thomas", "Thomas I.", "Film maker · Media director", "media", aboutCrew}, {"oriane", "Oriane T.", "Photographer · Reporter", "media", aboutCrew}, {"n0izn0iz", "n0izn0iz", "gno.land · Teritori · Zenao", "alumni", gh + "n0izn0iz"}, {"dadidou", "dadidou", "Front-end · Teritori · Zenao", "alumni", gh + "WaDadidou"}, {"moonia", "moonia", "gnolove · gnodaokit · Zenao", "alumni", gh + "moonia"}, {"hthieu1110", "hthieu1110", "GnoVM · 8 PRs · Teritori", "alumni", gh + "hthieu1110"}, {"omniwired", "omniwired", "tm2 · Teritori", "alumni", gh + "omniwired"}, {"amozpay", "AmozPay", "DevOps · gno docs", "alumni", gh + "AmozPay"}, } } // defaultProfiles are the members already on chain. func defaultProfiles() []struct { id string addr address home string line string } { return []struct { id string addr address home string line string }{ {"zxxma", "g1747t5m2f08plqjlrjk2q0qld7465hxz8gkx59c", "/r/g1747t5m2f08plqjlrjk2q0qld7465hxz8gkx59c/home", "<3 is the encryption key."}, } } const memba = "https://memba.samourai.app/mainnet" var hero = heroText{ kicker: "SAMOURAÏ COOP · GNO.LAND · PARIS", title: "samcrew", subtitle: "The gno.land crew of Samouraï Coop", tagline: "core contributors · genesis validator · design partner", footline: "film crew · engineering studio · since 2016", alt: "samcrew, the gno.land crew of Samouraï Coop", } // defaultSlots and defaultGalleries seed the page at deploy time. They are // functions, not package variables, so the seed text is stored once (in the // slot and gallery trees) instead of twice. After deploy the chain is the // source of truth: edit with Set and SetGallery, not by redeploying. func defaultSlots() []struct{ slug, body string } { return []struct{ slug, body string }{ {"layout", `:hero: :intro: ## 道 Core contributors, four years running :core: ## 源 Open source :oss: :gallery.oss: ## 作 Products :products: :gallery.products: ## 組 Inside Memba :memba: :gallery.memba: ## 守 What we run :run: ## 侍 The crew :crew: ## 録 gno.land on film :gnofilm: :gallery.gno: ## 祭 Events :events: :gallery.events: ## 報 Contribution reports :reports: :gallery.reports: ## 学 Peer Dev: gno.land, taught on video :peerdev: :gallery.peerdev: ## 旅 WEB TRIP :webtrip: :gallery.webtrip: ## 映 Films by the crew :films: :gallery.films: ## 記 From the blog :blog: :gallery.blog: ## 印 On chain :packages: ## 門 Find us :contact: --- *This page is a contract.* ` + "`:realm:`" + ` · rev :rev: · block :height: on :chainid: · every poster on it is drawn on chain · [slots](/r/samcrew/home:slots) :ascii: `}, // The Samouraï Coop mark again, as a sign-off, drawn in half blocks from // the logo's own pixels. {"ascii", "```" + ` ▄▄▄██████████▄▄▄ ▄▄████████████████████▄▄ ▄▄██████████████████████████▄▄ ▄████████████████████████████████▄ ████████████████████████████████████ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▄▄ ▄▄ ████████ ▄▄▄▄▄▄▄▄▄▄▄▄▄ █████████ ▄████████ █████████████ █████████▄ S A M O U R A Ï C R E W ▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ film crew · engineering studio ████████ █████████████▄ █████████ gno.land core contributors ▀▀▀▀▀▀▀▀ ▄██████████████ █████████ Paris · since 2016 ███████████████ ████████▀ ██████▀ ███████████████ ▀██████ █████ ███████████████ ▀███ ████ ███████████████ ▄ ▀ ▀██ ███████████████ ▀█▄ ▀ ███████████████ █▀ ███████████████ ▄████████████████ ▀▀▀██████████▀▀ ` + "```"}, {"intro", `> **samcrew** is the gno.land team of [Samouraï Coop](https://www.samourai.world), a worker cooperative from Paris that is a film crew and an engineering studio in one company. We contribute to the chain's core, run one of its genesis validators, build our own products on it as a design partner, research how organisations govern themselves, and film the ecosystem as it grows.`}, {"core", `The crew has **[256 merged pull requests in gnolang/gno](https://github.com/gnolang/gno/pulls?q=is%3Apr+is%3Amerged+author%3Aomarsy+author%3AVillaquiranm+author%3Amvallenet+author%3Adavd-gzl+author%3An0izn0iz+author%3Ahthieu1110+author%3Amoonia+author%3AWaDadidou+author%3Aomniwired+author%3AAmozPay)**, about one in eleven of every PR merged there, and more each year: 48 in 2024, 79 in 2025, 128 so far in 2026. | | what we do | | --- | --- | | **GnoVM** | The interpreter, its type checker, stdlibs and test suite. | | **Security** | Hardening the VM, consensus and p2p against crashes, overflows and malformed input. | | **Chain & tooling** | gno.land node, tm2, genesis tools, ` + "`gnodev`" + `, docs. | | **Validators** | A genesis validator, [gnomonitoring](https://github.com/samouraiworld/gnomonitoring), [validator tooling](https://github.com/samouraiworld/gno-validator-tools) and the [katana](https://github.com/samouraiworld/katana) GovDAO daemon. | | **Governance research** | Our [research lab](https://samouraiworld.github.io/gov-research-wiki/) studies cooperatives, DAOs and open-source projects. [gnodaokit](https://github.com/samouraiworld/gnodaokit) and Memba are its output. | | **Design partner** | We ship our own products on gno.land and send what they teach us back upstream. | | **Documentation** | Retreat and event reports for the core team, the Peer Dev tutorials, the WEB TRIP interviews. |`}, {"run", `- **A genesis validator.** One of the four validators securing ` + "`gnoland-1`" + ` since mainnet launch on 12 September 2026. - **Validator monitoring.** [gnomonitoring](https://github.com/samouraiworld/gnomonitoring) watches the chain and pages the humans. - **GovDAO tooling.** [katana](https://github.com/samouraiworld/katana) automates validator ejection for GovDAO.`}, {"products", `Products built and run by the coop. The live ones are open to anyone.`}, {"memba", `[Memba](https://memba.samourai.app) is an experimental operating system for decentralised organisations, and the direct output of our governance research. It is MIT-licensed and [open source](https://github.com/samouraiworld/memba). Each of its parts is a product in its own right. Hover a card for details. Every game uses one daily seed for everyone, drawn from a gno.land block, and needs no wallet.`}, {"gnofilm", `The crew films gno.land from the inside: brand films, core-team retreats, portraits of the teams building on it.`}, {"oss", `Everything we can open, we open. These are our public gno repositories on [GitHub](https://github.com/samouraiworld).`}, {"events", `We produce the events we believe in, and we film the rest of the ecosystem's. **Paris P2P Festival**: five days of peer-to-peer at Ground Control, free, for 5,000 people. The Édition #1 talks are [all online](https://p2p.samourai.tv). **Hyper Hacktive Festival**: a film festival for independent documentaries funded by Samouraï Grants. It has had three editions, two at mk2 Quai de Seine, and sells its tickets on Zenao.`}, {"reports", `Each year, one film shows everything the coop shipped.`}, {"peerdev", `A 17-lesson curriculum, produced end to end by the coop, that takes a developer from an empty machine to GovDAO and the interrealm spec. Watch it on [the Peer Dev channel](https://www.youtube.com/@peerdevlearning) or as [one playlist](https://www.youtube.com/playlist?list=PLJZrQikyfMc-kBojXgAojOz4UQPuq4DiY).`}, {"webtrip", `Eight conversations with gno.land and web3 builders, filmed by the crew in 2024.`}, {"films", `40 festival selections, 3 wins. [The Underground Sistine Chapel](https://www.youtube.com/watch?v=fsRFHmdiyGE) took Best Documentary at the London Independent Film Awards and was released free on both IPFS and YouTube.`}, {"blog", `Research notes, essays and product write-ups from the coop. [All posts](https://www.samourai.world/blog/) · [RSS](https://www.samourai.world/blog/rss.xml)`}, {"packages", `**Realms:** [App Store](/r/samcrew/memba_appstore_v3) · [feed](/r/samcrew/memba_feed_v1) · [DAO channels](/r/samcrew/memba_dao_channels_v2) · [quests](/r/samcrew/memba_quest_attestation_v1) · [reviews](/r/samcrew/memba_reviews_v2) · [feedback](/r/samcrew/memba_feedback_v2) · [escrow](/r/samcrew/escrow_v3) · [market config](/r/samcrew/memba_market_config) · [arcade leaderboard](/r/samcrew/memba_arcade_leaderboard_v1) · [GnoBuilders badges](/r/samcrew/gnobuilders_badges_v2) **Libraries:** [avl](/p/samcrew/avl) · [piechart](/p/samcrew/piechart/v0) · [tablesort](/p/samcrew/tablesort/v0) · [urlfilter](/p/samcrew/urlfilter/v0)`}, {"contact", `[samourai.world](https://www.samourai.world) · [GitHub](https://github.com/samouraiworld) · [Samouraï TV](https://www.youtube.com/channel/UC0gExSSzyU8gTqUjoYerI8A) · [Peer Dev](https://www.youtube.com/@peerdevlearning) · [X](https://www.twitter.com/samouraicoop) · [Telegram](https://t.me/samouraicoop) · hello@samourai.coop`}, } } const ( yt = "https://www.youtube.com/watch?v=" ghsw = "https://github.com/samouraiworld/" ) func defaultGalleries() []struct{ name, spec string } { return []struct{ name, spec string }{ {"gno", ` gno | Gno: The Power To Build What Matters | gno.land · brand film | ` + yt + `M920PO3yrHk gno | Alignment Retreat, Turin | gno.land core team · retreat report | ` + yt + `3czMK3s30KQ gno | Inside Onbloc | gno.land · team portrait · Nov 2024 | ` + yt + `7umxHYDJ4Bk `}, {"products", ` product | Memba | Experimental · organizations OS | https://memba.samourai.app product | Zenao | Beta · ticketing & communities | https://zenao.io product | gnolove | Live · who builds gno.land | https://gnolove.world product | Visio | Live · video meetings, no account | https://visio.samourai.app product | Greffon | Beta · self-host in one click | https://greffon.io product | Reportz | Live · the commons, from GitHub | https://reportz.dev product | Coop Nation | Live · the cooperative manifesto | https://coopnation.fr product | AlarmFriends | Soon · friends wake you up | https://alarmfriends.samourai.app `}, {"memba", ` memba | Multisig Wallet | For organizations · Adena & gnokey | ` + memba + `/multisig | Create or import a multisig, propose sends and contract calls, collect signatures from Adena or gnokey, then broadcast. memba | DAO Management | Create, vote, execute · GovDAO | ` + memba + `/dao | Browse GovDAO and community DAOs, vote, execute, create your own and talk in DAO channels. memba | Validator Monitoring | Reliability scores · hacker view | ` + memba + `/validators | Every validator scored for reliability, plus a live consensus hacker view. memba | Alerting | GovDAO & validators · 3 channels | ` + memba + `/alerts | GovDAO and validator alerts on Discord, Slack and Telegram. memba | On-chain App Store | Registry · submissions · curation | ` + memba + `/apps | The ecosystem's apps, with an on-chain registry, submissions and curation. memba | Directory | DAOs · realms · packages · users | ` + memba + `/directory | Search DAOs, realms, packages, tokens and users, with source and render side by side. memba | Dev Reports | gnolove scoreboard · weekly reports | ` + memba + `/gnolove | The gnolove contributor scoreboard, weekly reports and team analytics. memba | Token Launcher | GRC20 · browse & launch | ` + memba + `/tokens | Browse GRC20 tokens and launch your own. memba | Social Feed | On-chain posts · open moderation | ` + memba + `/feed | An on-chain social feed with a public moderation log. memba | Quests & Badges | GnoBuilders · 85 quests · 8 ranks | ` + memba + `/quests | GnoBuilders: 85 quests, 8 ranks, a leaderboard and on-chain badges. game | Space Invaders | Daily or free play · no wallet | ` + memba + `/game/space-invaders | Marching waves, combos, a mystery UFO and destructible bunkers. Daily or free play, keyboard or touch. game | Block Party | Daily 4×4 merge · seeded by a block | ` + memba + `/game | A daily tile-merge puzzle. Everyone gets the same board, seeded from a real gno.land block. Streaks and a daily leaderboard. game | BARRICADE | 90-second lane defence · 8 waves | ` + memba + `/game/barricade | Hold the barricade against The Order for 8 waves up to the Broadcast Tower, then an endless siege. Runs can be certified on chain. `}, {"oss", ` oss | memba | Multisig & DAO governance for gno.land | ` + ghsw + `memba oss | gnolove | The gno.land contributor scoreboard | ` + ghsw + `gnolove oss | zenao | Event ticketing & communities | ` + ghsw + `zenao oss | gnodaokit | A framework for DAOs in Gno | ` + ghsw + `gnodaokit oss | gnomonitoring | Monitoring for gno validators | ` + ghsw + `gnomonitoring oss | katana | GovDAO validator-ejection daemon | ` + ghsw + `katana oss | gno-validator-tools | Validators & sentries as code | ` + ghsw + `gno-validator-tools oss | gov-research-wiki | The governance research lab | ` + ghsw + `gov-research-wiki oss | peerdev | The Peer Dev channel, in the open | ` + ghsw + `peerdev oss | validator-diagnostics | The validator fire-drill portal | ` + ghsw + `validator-diagnostics oss | gno-onboarding-bot | Validator onboarding, on Discord | ` + ghsw + `gno-onboarding-bot oss | gno-govdao-activity-parser | GovDAO voter activity & test env | ` + ghsw + `gno-govdao-activity-parser `}, {"events", ` festival | Paris P2P Festival | Co-founded by zôÖma · Ground Control | ` + yt + `PQ9I73tJobU festival | Hyper Hacktive Festival | 3 editions · mk2 Quai de Seine | https://hyperhacktive.io event | Web3 Summit | Web3 Foundation · Berlin 2024-25 | ` + yt + `PRG0cnV7YG8 event | dWeb Camp | Internet Archive · California 2022 | ` + yt + `zx7Dduv0Hd4 event | Funding the Commons | San Francisco · Buenos Aires | ` + yt + `bOZrUha9aQ8 `}, {"reports", ` report | Contribution Report 2025 | Samouraï Coop · Welcome to 2026 | ` + yt + `aG4M37FX4II report | Contribution Report 2024 | Samouraï Coop · Welcome to 2025 | ` + yt + `37L4vULszLQ report | Contribution Report 2023 | Samouraï Coop · Welcome to 2024 | ` + yt + `tj0imGVJHQc `}, {"peerdev", ` peerdev | Setup your local environment | Lesson 1 | ` + yt + `C8RuA_uU3Fs peerdev | Build and test your first dApp | Lesson 2 | ` + yt + `nzo6QgxQHgw peerdev | Register your User Space | Lesson 3 | ` + yt + `R5t1GmBRUVk peerdev | Transaction types | call · run · addpkg · send | ` + yt + `-Z3M1XVxWU8 peerdev | Coins and GRC20 | Lesson 5 | ` + yt + `4twoUyjo4rA peerdev | AVL tree vs map | Lesson 6 | ` + yt + `Qh73MqxxKTM peerdev | Deploying Gno packages | Lesson 7 | ` + yt + `B7bSreCwIls peerdev | Interactive documentation | Lesson 8 | ` + yt + `zpGxMQo3V-o peerdev | Effective programming in Gno | Lesson 9 | ` + yt + `UNJSuMdjTFA peerdev | Interrealm specification | Lesson 10 | ` + yt + `JJju7-GeRbA peerdev | DAOs and modular governance | Lesson 11 | ` + yt + `SphPgsjKQyQ peerdev | Governance DAO (GovDAO) | Lesson 12 | ` + yt + `jM-6ezyn_v4 peerdev | Index your transactions | tx-indexer | ` + yt + `8ld0nsV37SA peerdev | Monitor your validator | gnomonitoring | ` + yt + `ujwbKSFS_wg peerdev | Extend your IDE | VS Code · Neovim · Emacs · JetBrains | ` + yt + `Ssk1m-YGnyo peerdev | Secure your wallet | Lesson 16 | ` + yt + `q2jlN8KXBMA peerdev | Gno vs Solidity | Lesson 17 | ` + yt + `Oi77hvJCVbU `}, {"webtrip", ` webtrip | #1 Creating a New Era with Gno | Manfred Touron · gno.land | ` + yt + `Hv2QSIjz_U8 webtrip | #2 Building Trust in Web3 | Isaac Patka · Shield3 | ` + yt + `hKc9eWU2_CI webtrip | #3 Digital Art Disruptors | Distributed Gallery | ` + yt + `4nvVak97eik webtrip | #4 Decentralizing Digital Communities | Morgan Bazalgette · gno.land | ` + yt + `Hprl2WkMYCA webtrip | #5 Bridging Web2 and Web3 | Dongwon Shin · Onbloc | ` + yt + `-kb_N4Y7cm0 webtrip | #6 The Future of Staking and NFTs | Adrien Faguet · StakeLab | ` + yt + `0cOdIoQ1npI webtrip | #7 Personal Development and Blockchain | Dragos Roua | ` + yt + `aw8SJajzRdA webtrip | #8 Work-Life Balance & Product Design | Diane | ` + yt + `r_S9YbwF9UM `}, {"blog", ` blog | Gno.land in 2026: the beta mainnet | 27 Aug 2026 · gno.land | https://www.samourai.world/blog/gno-land-in-2026-what-a-beta-mainnet-actually-means/ | The chain is live and the token cannot move yet: what has shipped, what has not, and what the project has actually promised. blog | Samouraï Governance Research Lab | 5 Nov 2025 · governance | https://www.samourai.world/blog/samoura%C3%AF-governance-research-lab-une-nouvelle-%C3%A9tape-pour-la-coop%C3%A9rative/ | The coop opens a research lab on cooperative and distributed governance, after eight years of building. blog | Decentralized governance or the classic model | 7 Jan 2026 · governance | https://www.samourai.world/blog/decentralized-governance-vs-classic-model/ | Decentralized (DAO, blockchain) and classic governance compared: advantages, limits, costs and use cases. blog | Crypto (Anti-)Commons? Part 1 | 23 Jun 2026 · research | https://www.samourai.world/blog/crypto-anti-commons-controversies-surrounding-the-status-of-bitcoin-and-public-blockchains-as-dig/ | Can Bitcoin and public blockchains be called digital commons? Part 1 maps the controversy. blog | Cooperative & Web3 Glossary | 13 Jan 2026 · research | https://www.samourai.world/blog/cooperative-web3-glossary-v-0-1/ | A living glossary translating between cooperative and Web3 vocabularies, for coops meeting the DAO and Web3 builders meeting the coop. blog | Insight Report #1: Phil H. | 12 Dec 2025 · interview | https://www.samourai.world/blog/insight-report-series-external-interview-1-phil-h/ | The first of an interview series on distributed governance. blog | From Startup Nation to Coop Nation | 4 Sep 2026 · essay | https://www.samourai.world/blog/from-startup-nation-to-coop-nation/ | Signs of a public and cooperative tech awakening in France, the risks of sovereignty washing, and how to take part. blog | Reportz: the digital commons, live | 31 Aug 2026 · product | https://www.samourai.world/blog/reportz-who-builds-the-digital-commons-live-from-github/ | A contribution board built from the public GitHub API: what it counts, and what it refuses to count. blog | Greffon: self-hosted deployment | 5 Aug 2026 · product | https://www.samourai.world/blog/greffon-io-self-hosted-deployment-platform/ | A technical tour of Greffon: its control plane, workers and built-in certificate authority. `}, {"films", ` film | The Underground Sistine Chapel | Feature documentary · 2021 | ` + yt + `fsRFHmdiyGE film | Hashiriya | Documentary · 2025 | ` + yt + `OqMTmSEbqqY film | Soy Yo | Documentary · 2025 | ` + yt + `fWE2fpZuDEM film | Mañana cuesta más | Documentary · 2026 | ` + yt + `PEAR7l8dGEk film | Community Networks | Documentary · 2024 | ` + yt + `JuHVteEhNzo film | I'm Just a Lost Guy | Short film · 2025 | ` + yt + `84RgQxdI3n8 `}, } }
  6. #6crew.gno
  7. #7package home import ( "strconv" "strings" "chain/runtime" "gno.land/p/nt/avl/v0" ) // The crew roster lists everyone who built with samcrew, whether or not they // have a gno.land identity yet. // // A member belongs to a team (engineering, media, ...) and starts with a // public link, usually their GitHub profile. Once they have an on-chain // identity, the admin runs LinkProfile: the card then links to their home // realm (or /u/<address>), is marked ON CHAIN, and the member can write their // own line with SetLine. const ( maxCrew = 96 // each card costs render gas; keeps Render("") far below the query ceiling maxIDLen = 32 maxHandleLen = 32 maxRoleLen = 40 maxLineLen = 160 ) type member struct { id string // stable key, [a-z0-9._-] handle string // shown on the card role string team string // [a-z0-9._-], groups the cards url string // public link used until the member is on chain addr address home string // "/r/..." home realm, preferred link once set line string order int joined int64 } var ( crew = avl.NewTree() // id -> *member byAddr = avl.NewTree() // address string -> id, for SetLine nextOrder int ) // oneLine rejects text that would break out of its markdown or SVG slot. func oneLine(field, s string, max int) { if len(s) > max { panic(field + " too long: max " + strconv.Itoa(max) + " bytes") } if strings.ContainsAny(s, "|\n\r") { panic(field + " must be one line without '|'") } } func validURL(u string) bool { return (strings.HasPrefix(u, "https://") || strings.HasPrefix(u, "/") && !strings.HasPrefix(u, "//")) && len(u) <= 200 && !strings.ContainsAny(u, " ()<>\"|\\\n\r") } // mdEscape turns member-written text into plain text: every character that // markdown could read as a link, image, emphasis, code or HTML is escaped. func mdEscape(s string) string { var b strings.Builder for _, r := range s { if strings.ContainsRune("\\`*_[]()<>!#|~{}", r) { b.WriteByte('\\') } b.WriteRune(r) } return b.String() } func addMember(id, handle, role, team, url string) { if len(id) > maxIDLen || !validSlug(id) { panic("invalid member id: want 1-32 bytes of [a-z0-9._-]") } if crew.Has(id) { panic("already in the crew: " + id) } if crew.Size() >= maxCrew { panic("the roster is full: " + strconv.Itoa(maxCrew) + " members") } m := &member{id: id} setIdentity(m, handle, role, team, url) nextOrder++ m.order = nextOrder m.joined = runtime.ChainHeight() crew.Set(id, m) } func setIdentity(m *member, handle, role, team, url string) { if handle == "" { panic("handle required") } oneLine("handle", handle, maxHandleLen) oneLine("role", role, maxRoleLen) if len(team) > maxIDLen || !validSlug(team) { panic("invalid team: want 1-32 bytes of [a-z0-9._-]") } if !validURL(url) { panic("link must be https:// or a /path, without spaces or markdown characters") } m.handle, m.role, m.team, m.url = handle, role, team, url } func mustMember(id string) *member { v := crew.Get(id) if v == nil { panic("not in the crew: " + id) } return v.(*member) } // AddMember puts someone on the roster, in a team, linked to a public page. func AddMember(cur realm, id, handle, role, team, url string) { assertAdmin(cur) addMember(id, handle, role, team, url) } // UpdateMember changes a member's handle, role, team and public link. func UpdateMember(cur realm, id, handle, role, team, url string) { assertAdmin(cur) setIdentity(mustMember(id), handle, role, team, url) } // LinkProfile ties a member to their gno.land identity. home is their home // realm path ("/r/..."), or "" to link to /u/<addr>. From then on the member // can write their own line with SetLine. func LinkProfile(cur realm, id string, addr address, home string) { assertAdmin(cur) m := mustMember(id) if !addr.IsValid() { panic("invalid address") } if home != "" && (!strings.HasPrefix(home, "/r/") || strings.ContainsAny(home, " ()<>\"|\\\n\r")) { panic("home must be a /r/... path") } if other := byAddr.Get(addr.String()); other != nil && other.(string) != id { panic("address already linked to " + other.(string)) } if m.addr != "" { byAddr.Remove(m.addr.String()) } if m.addr != addr { m.line = "" // a line belongs to the address that wrote it } m.addr, m.home = addr, home byAddr.Set(addr.String(), id) } // ClearLine removes a member's line, for moderation. func ClearLine(cur realm, id string) { assertAdmin(cur) mustMember(id).line = "" } // RemoveMember takes someone off the roster. func RemoveMember(cur realm, id string) { assertAdmin(cur) m := mustMember(id) if m.addr != "" { byAddr.Remove(m.addr.String()) } crew.Remove(id) } // SetLine lets a crew member with a linked profile write their own line. It // renders as plain text: markdown in it is escaped. func SetLine(cur realm, line string) { caller := cur.Previous().Address() v := byAddr.Get(caller.String()) if v == nil { panic("no crew profile linked to " + caller.String()) } oneLine("line", line, maxLineLen) mustMember(v.(string)).line = line } // IsMember reports whether addr is linked to a crew profile. func IsMember(addr address) bool { return byAddr.Has(addr.String()) } // CrewSize returns the number of members on the roster. func CrewSize() int { return crew.Size() } // link is where a member's card points: home realm, then on-chain profile, // then their public link. func (m *member) link() string { switch { case m.home != "": return m.home case m.addr != "": return "/u/" + m.addr.String() } return m.url } // teamTitles names the known teams; any other team shows its raw name. var teamTitles = map[string]string{ "engineering": "Engineering crew", "media": "Media crew", "alumni": "Alumni", } // teamKinds picks the poster style of a team's cards. var teamKinds = map[string]string{ "media": "member-media", "alumni": "member-alumni", } // crewTable shows the roster team by team, teams in the order their first // member joined, members in the order they joined: one poster each, then the // lines they wrote themselves. func crewTable() string { if crew.Size() == 0 { return "_The roster is empty._" } members := make([]*member, 0, crew.Size()) crew.Iterate("", "", func(_ string, v any) bool { m := v.(*member) i := len(members) members = append(members, m) for i > 0 && members[i-1].order > m.order { members[i] = members[i-1] i-- } members[i] = m return false }) var teams []string byTeam := map[string][]*member{} for _, m := range members { if _, ok := byTeam[m.team]; !ok { teams = append(teams, m.team) } byTeam[m.team] = append(byTeam[m.team], m) } var b strings.Builder onChain := 0 var counts []string for _, team := range teams { group := byTeam[team] title := teamTitles[team] if title == "" { title = team } counts = append(counts, strconv.Itoa(len(group))+" "+strings.ToLower(strings.TrimSuffix(title, " crew"))) b.WriteString("**" + title + "**") if team == "alumni" { b.WriteString(": former crew members who built gno with us.") } b.WriteString("\n\n") kind := teamKinds[team] if kind == "" { kind = "member" } for i := 0; i < len(group); i += cardsPerRow { b.WriteString("<gno-columns>\n") for j := i; j < i+cardsPerRow; j++ { if j > i { b.WriteString("<gno-columns-sep />\n") } if j >= len(group) { continue } m := group[j] n := strconv.Itoa(j + 1) if j < 9 { n = "0" + n } label := strings.ToUpper(strings.TrimSuffix(title, " crew")) + " · " + n if m.addr != "" { label += " · ON CHAIN" onChain++ } c := card{kind: kind, title: m.handle, meta: m.role, url: m.link(), label: label} b.WriteString("[![" + mdText(m.handle) + "](" + posterURI(c) + ")](" + c.url + ")\n") } b.WriteString("</gno-columns>\n\n") } } b.WriteString("*" + strconv.Itoa(len(members)) + " samurai (" + strings.Join(counts, ", ") + "), " + strconv.Itoa(onChain) + " on chain so far. A card links to its owner's public page until they have a gno.land profile.*") var lines strings.Builder for _, m := range members { if m.line != "" { lines.WriteString("- **[" + mdText(m.handle) + "](" + m.link() + ")**: " + mdEscape(m.line) + "\n") } } if lines.Len() > 0 { b.WriteString("\n\n**In their own words.**\n\n" + lines.String()) } b.WriteString("\n*Crew members on chain write their own line with [SetLine](" + webPath + "$help&func=SetLine).*") return b.String() }
  8. #8gallery.gno
  9. #9package home import ( "encoding/base64" "strings" "gno.land/p/nt/avl/v0" ) // This file is shared, unchanged, by every Samouraï home realm. // // A gallery is a named list of cards. gnoweb's CSP only paints images from a // few hosts (YouTube thumbnails are not among them) but it allows // data:image/svg+xml, so every card is a poster drawn here, on chain, and // linked to its video or site. // // A gallery is written in one Set-like call as lines of // // kind | title | meta | url [| description] // // The optional description becomes the card's hover text. // and rendered wherever the layout says :gallery.<name>:. const ( galleryPrefix = "gallery." cardsPerRow = 3 maxCards = 48 maxTitleLen = 80 maxMetaLen = 80 maxDescLen = 200 ) type card struct { kind, title, meta, url string desc string // hover text, optional label string // overrides the kind's label when set } var galleries = avl.NewTree() // name -> []card type style struct { label, glyph, accent, shade string video bool } var styles = map[string]style{ "report": {"CONTRIBUTION REPORT", "報", "#e63946", "#3a1014", true}, "gno": {"GNO.LAND · FILMED BY SAMOURAÏ", "道", "#2ec4b6", "#0d2e2b", true}, "webtrip": {"WEB TRIP · SERIES", "旅", "#ff7aa2", "#3a1422", true}, "peerdev": {"PEER DEV · GNO TUTORIAL", "学", "#4ea8de", "#0f2536", true}, "event": {"EVENT", "祭", "#f4a261", "#3a2412", true}, "festival": {"FESTIVAL · BY SAMOURAÏ", "祭", "#f4a261", "#3a2412", false}, "film": {"FILM · DIRECTED BY THE CREW", "映", "#b388eb", "#261a36", true}, "product": {"PRODUCT", "作", "#e9c46a", "#352b10", false}, "memba": {"MEMBA · PRODUCT", "組", "#e9c46a", "#352b10", false}, "game": {"ARCADE · PLAY IN MEMBA", "遊", "#8ac926", "#1c2a0b", true}, "oss": {"OPEN SOURCE · GITHUB", "源", "#a8b3bd", "#1b2026", false}, "blog": {"BLOG · SAMOURAI.WORLD", "記", "#e8e2d8", "#2a2620", false}, "member": {"SAMOURAÏ · CREW", "侍", "#e63946", "#2a1012", false}, "member-media": {"SAMOURAÏ · MEDIA", "映", "#b388eb", "#261a36", false}, "member-alumni": {"SAMOURAÏ · ALUMNI", "侍", "#9aa0a6", "#1d1f22", false}, } func styleOf(kind string) style { if s, ok := styles[kind]; ok { return s } return style{strings.ToUpper(kind), "侍", "#e63946", "#2a1012", false} } // SetGallery replaces a whole gallery from its line format. func SetGallery(cur realm, name, spec string) { assertAdmin(cur) if !validSlug(name) { panic("invalid gallery name: " + name) } cards := parseGallery(spec) rev++ galleries.Set(name, cards) } // DeleteGallery removes a gallery. func DeleteGallery(cur realm, name string) { assertAdmin(cur) if _, removed := galleries.Remove(name); !removed { panic("no such gallery: " + name) } rev++ } func parseGallery(spec string) []card { var cards []card for _, line := range strings.Split(spec, "\n") { line = strings.TrimSpace(line) if line == "" || strings.HasPrefix(line, "#") { continue } f := strings.Split(line, "|") if len(f) != 4 && len(f) != 5 { panic("gallery line needs 4 or 5 fields (kind | title | meta | url [| description]): " + line) } c := card{ kind: strings.TrimSpace(f[0]), title: strings.TrimSpace(f[1]), meta: strings.TrimSpace(f[2]), url: strings.TrimSpace(f[3]), } if len(f) == 5 { c.desc = strings.TrimSpace(f[4]) if len(c.desc) > maxDescLen || strings.ContainsAny(c.desc, "\"\\") { panic("gallery description must be at most 200 bytes, without quotes or backslashes: " + line) } } if !validSlug(c.kind) || c.title == "" || len(c.title) > maxTitleLen || len(c.meta) > maxMetaLen { panic("bad gallery line: " + line) } if !strings.HasPrefix(c.url, "https://") && (!strings.HasPrefix(c.url, "/") || strings.HasPrefix(c.url, "//")) { panic("gallery url must be https:// or a /path: " + c.url) } if strings.ContainsAny(c.url, " ()<>\"\\") { panic("gallery url has characters markdown would break on: " + c.url) } cards = append(cards, c) } if len(cards) == 0 || len(cards) > maxCards { panic("a gallery holds 1 to 48 cards") } return cards } // renderGallery lays cards out cardsPerRow to a row, each one a poster that // links to its target. func renderGallery(name string) (string, bool) { v := galleries.Get(name) if v == nil { return "", false } cards := v.([]card) var b strings.Builder for i := 0; i < len(cards); i += cardsPerRow { b.WriteString("<gno-columns>\n") for j := i; j < i+cardsPerRow; j++ { if j > i { b.WriteString("<gno-columns-sep />\n") } if j < len(cards) { c := cards[j] b.WriteString("[![" + mdText(c.title) + "](" + posterURI(c) + ")](" + c.url + linkTitle(c.desc) + ")\n") } } b.WriteString("</gno-columns>\n\n") } return strings.TrimSuffix(b.String(), "\n\n"), true } // linkTitle renders an optional markdown link title, which gnoweb emits as // the link's hover text. func linkTitle(desc string) string { if desc == "" { return "" } return ` "` + desc + `"` } // mdText keeps a title from closing the markdown image alt text early. func mdText(s string) string { return strings.NewReplacer("[", "(", "]", ")", "\\", "/").Replace(s) } func xmlText(s string) string { return strings.NewReplacer("&", "&amp;", "<", "&lt;", ">", "&gt;", "\"", "&quot;", "'", "&#39;").Replace(s) } // wrap splits s into at most two lines of about width runes; the second line // is cut with an ellipsis when the title is longer. func wrap(s string, width int) []string { words := strings.Fields(s) var lines []string cur := "" for _, w := range words { if cur == "" { cur = w } else if runeLen(cur)+1+runeLen(w) <= width { cur += " " + w } else { lines = append(lines, cur) cur = w } } if cur != "" { lines = append(lines, cur) } if len(lines) > 2 { second := []rune(strings.Join(lines[1:], " ")) if len(second) > width { second = append(second[:width-1], '…') } lines = []string{lines[0], string(second)} } return lines } func runeLen(s string) int { return len([]rune(s)) } // clip cuts s to at most n runes, ending with an ellipsis when it had to cut. func clip(s string, n int) string { r := []rune(s) if len(r) <= n { return s } return string(append(r[:n-1], '…')) } func posterURI(c card) string { return "data:image/svg+xml;base64," + base64.StdEncoding.EncodeToString([]byte(poster(c))) } // poster draws a 320x180 card: a dark gradient, a rising sun in the kind's // accent (a play button for videos), a large kanji watermark, the kind label, // the title on up to two lines and a meta line. func poster(c card) string { st := styleOf(c.kind) if c.label != "" { st.label = c.label } var b strings.Builder b.WriteString(`<svg xmlns="http://www.w3.org/2000/svg" width="320" height="180" viewBox="0 0 320 180">`) b.WriteString(`<defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#121212"/><stop offset="1" stop-color="` + st.shade + `"/></linearGradient></defs>`) b.WriteString(`<rect width="320" height="180" rx="14" fill="url(#g)"/>`) b.WriteString(`<text x="306" y="170" text-anchor="end" font-family="serif" font-size="120" fill="#ffffff" fill-opacity=".06">` + st.glyph + `</text>`) b.WriteString(`<circle cx="268" cy="52" r="30" fill="` + st.accent + `"/>`) if st.video { b.WriteString(`<path d="M260 38 L282 52 L260 66 Z" fill="#121212"/>`) } else { b.WriteString(`<path d="M256 52 H280 M272 44 L280 52 L272 60" stroke="#121212" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"/>`) } b.WriteString(`<rect x="18" y="20" width="3" height="12" fill="` + st.accent + `"/>`) b.WriteString(`<text x="27" y="31" font-family="ui-monospace,Menlo,monospace" font-size="10" letter-spacing="1.5" fill="` + st.accent + `">` + xmlText(st.label) + `</text>`) lines := wrap(c.title, 24) y := 108 if len(lines) == 1 { y = 126 } for _, l := range lines { b.WriteString(`<text x="18" y="` + itoa(y) + `" font-family="system-ui,-apple-system,Segoe UI,sans-serif" font-weight="700" font-size="20" fill="#ffffff">` + xmlText(l) + `</text>`) y += 25 } // The meta line hugs the bottom-right corner: gnoweb draws its // external-link badge over the bottom-left one. b.WriteString(`<text x="304" y="164" text-anchor="end" font-family="ui-monospace,Menlo,monospace" font-size="11" fill="#bdbdbd">` + xmlText(clip(c.meta, 40)) + `</text>`) b.WriteString(`</svg>`) return b.String() } func itoa(n int) string { if n == 0 { return "0" } var d []byte for n > 0 { d = append([]byte{byte('0' + n%10)}, d...) n /= 10 } return string(d) }
  10. #10gnomod.toml
  11. #11module = "gno.land/r/samcrew/home" gno = "0.9" private = true
  12. #12home.gno
  13. #13// Package home is samcrew's page on gno.land, served at /u/samcrew. // // The page is assembled from SLOTS (named markdown fragments, one Set each); // the slot named "layout" is the template and every :slug: in it is filled in // a single pass. The crew roster is not a slot: see crew.gno. // // The admin starts as the samcrew namespace multisig and moves with a // two-step ProposeAdmin / AcceptAdmin, so it can later be handed to the DAO. package home import ( "strconv" "strings" "chain/runtime" "gno.land/p/nt/avl/v0" ) const ( layoutSlug = "layout" maxSlugLen = 64 ) type slot struct { body string rev int updated int64 } var ( admin = address("g136j0m08pkm2lwwde9dmlx8uee26llent9s5cpf") // samcrew namespace, 2-of-3 pendingAdmin address slots = avl.NewTree() // slug -> *slot rev int ) var reservedSlugs = []string{"chainid", "crew", "height", "hero", "realm", "rev"} func init() { h := runtime.ChainHeight() for _, s := range defaultSlots() { rev++ slots.Set(s.slug, &slot{body: s.body, rev: rev, updated: h}) } for _, g := range defaultGalleries() { galleries.Set(g.name, parseGallery(g.spec)) } for _, m := range defaultCrew() { addMember(m.id, m.handle, m.role, m.team, m.url) } for _, p := range defaultProfiles() { m := mustMember(p.id) m.addr, m.home, m.line = p.addr, p.home, p.line byAddr.Set(p.addr.String(), p.id) } } // --------------------------------------------------------------------------- // Admin func assertAdmin(cur realm) { if cur.Previous().Address() != admin { panic("restricted to admin") } } // Admin returns the current admin address. func Admin() address { return admin } // ProposeAdmin starts a handover. Nothing changes until the new address calls // AcceptAdmin, so a typo cannot lock the page. func ProposeAdmin(cur realm, next address) { assertAdmin(cur) if !next.IsValid() { panic("invalid address") } pendingAdmin = next } // AcceptAdmin completes a handover started by ProposeAdmin. func AcceptAdmin(cur realm) { caller := cur.Previous().Address() if pendingAdmin == "" || caller != pendingAdmin { panic("restricted to the proposed admin") } admin = caller pendingAdmin = "" } // --------------------------------------------------------------------------- // Slots func validSlug(slug string) bool { if len(slug) == 0 || len(slug) > maxSlugLen { return false } for i := 0; i < len(slug); i++ { c := slug[i] if !(c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '-' || c == '_' || c == '.') { return false } } return true } func assertWritableSlug(slug string) { if !validSlug(slug) { panic("invalid slug: want 1-" + strconv.Itoa(maxSlugLen) + " bytes of [a-z0-9._-], got " + strconv.Quote(slug)) } for _, r := range reservedSlugs { if slug == r { panic("reserved slug: " + slug) } } if strings.HasPrefix(slug, galleryPrefix) { panic("reserved slug: " + galleryPrefix + "* renders a gallery, use SetGallery") } } // Set creates or replaces one slot. func Set(cur realm, slug, body string) { assertAdmin(cur) assertWritableSlug(slug) rev++ slots.Set(slug, &slot{body: body, rev: rev, updated: runtime.ChainHeight()}) } // Delete removes a slot. Deleting "layout" falls back to the built-in layout. func Delete(cur realm, slug string) { assertAdmin(cur) if _, removed := slots.Remove(slug); !removed { panic("no such slot: " + slug) } rev++ } // Get returns a slot body, or "" when the slot does not exist. func Get(slug string) string { if v := slots.Get(slug); v != nil { return v.(*slot).body } return "" }
  14. #14render.gno
  15. #15package home import ( "strconv" "strings" "chain/runtime" ) const ( realmPath = "gno.land/r/samcrew/home" webPath = "/r/samcrew/home" ) const fallbackLayout = "# samcrew\n\n:intro:\n\n## The crew\n\n:crew:\n" // Render serves: // // "" the team page // "slots" the slot index // "slots/<slug>" one slot's raw markdown func Render(path string) string { switch { case path == "": return renderPage() case path == "slots": return renderIndex() case strings.HasPrefix(path, "slots/"): return renderSlot(strings.TrimPrefix(path, "slots/")) } // The path is not echoed: it comes from the visitor's URL. return "# Not found\n\nThis page does not exist.\n\n[Back home](" + webPath + ")\n" } func renderPage() string { layout := Get(layoutSlug) if layout == "" { layout = fallbackLayout } return fill(layout) } // fill replaces every :slug: in s, in one left-to-right pass. Replacement text // is never rescanned, and an unknown placeholder stays verbatim. func fill(s string) string { var b strings.Builder for { i := strings.IndexByte(s, ':') if i < 0 { b.WriteString(s) return b.String() } b.WriteString(s[:i]) s = s[i:] j := strings.IndexByte(s[1:], ':') if j < 0 { b.WriteString(s) return b.String() } if v, ok := lookup(s[1 : j+1]); ok { b.WriteString(v) s = s[j+2:] continue } b.WriteString(":") s = s[1:] } } func lookup(slug string) (string, bool) { if !validSlug(slug) { return "", false } switch slug { case "chainid": return runtime.ChainID(), true case "crew": return crewTable(), true case "hero": return heroImage(), true case "height": return strconv.FormatInt(runtime.ChainHeight(), 10), true case "realm": return realmPath, true case "rev": return strconv.Itoa(rev), true } if strings.HasPrefix(slug, galleryPrefix) { return renderGallery(strings.TrimPrefix(slug, galleryPrefix)) } if v := slots.Get(slug); v != nil { return v.(*slot).body, true } return "", false } func renderIndex() string { var b strings.Builder b.WriteString("# Slots\n\nrev " + strconv.Itoa(rev) + " · " + strconv.Itoa(slots.Size()) + " slot(s) · admin " + admin.String() + "\n\n") b.WriteString("| slot | bytes | rev | height |\n| --- | ---: | ---: | ---: |\n") slots.Iterate("", "", func(key string, value any) bool { s := value.(*slot) b.WriteString("| [" + key + "](" + webPath + ":slots/" + key + ") | " + strconv.Itoa(len(s.body)) + " | " + strconv.Itoa(s.rev) + " | " + strconv.FormatInt(s.updated, 10) + " |\n") return false }) return b.String() } func renderSlot(slug string) string { v := slots.Get(slug) if v == nil { return "# Not found\n\nNo such slot.\n\n[Slot index](" + webPath + ":slots)\n" } s := v.(*slot) body := s.body if !strings.HasSuffix(body, "\n") { body += "\n" } return "# " + slug + "\n\n" + strconv.Itoa(len(s.body)) + " bytes · rev " + strconv.Itoa(s.rev) + " · written at height " + strconv.FormatInt(s.updated, 10) + "\n\n````\n" + body + "````\n" }
  16. #16/gno.MemPackageType
  17. #17 MPUserAll

Result log

msg:0,success:true,log:,events:[]

← Back to block 271,558