Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

memba_appstore_v3 source realm

Package memba\_appstore\_v3 is a curated App Store for gno.land dApps: publishers pay a flat listing fee to register ...

Overview

Package memba_appstore_v3 is a curated App Store for gno.land dApps: publishers pay a flat listing fee to register an app; a curator flips it live (or rejects it).

MONEY PATH (the only one): RegisterApp collects a flat `registrationFee` in ugnot and forwards 100% to the treasury in the SAME call — nothing is ever custodied. The safety contract mirrors memba_token_otc_v1 + the O-13 lesson:

  1. IsUserCall() guard BEFORE reading OriginSend — an ephemeral `maketx run` realm can never attach unrecoverable coins (the guard agent_registry missed).
  2. exact-coin via unsafe.OriginSend() (the coins on THIS call, not the wallet balance) — closes the overpay-trap and the wallet-balance bypass.
  3. treasury-misconfig is fail-closed: an unset treasury panics (→ tx reverts → coins refunded), never silent custody.
  4. CEI: state is written before the banker moves funds. All attacker-controlled input (screenshots, appURL scheme) is validated BEFORE OriginSend is read.
  5. NewBanker(RealmSend, cur) sends the fee from the realm's own address to the treasury — no custody, no escrow (which is why no escrow is needed to be safe).

v3 over v2: a `rejected` state + RejectApp/EditListing lifecycle, ≤6 screenshots, an on-chain appURL scheme allowlist, FlagApp extended to pending listings (the public Unverified tab's safety valve), composite-key status/publisher indexes with O(1) per-status counters (so status/publisher reads are bounded, not full scans), and a sealed SeedListing migration primitive (FinalizeSeed closes the backdoor).

TREASURY: stored LOCALLY (admin-settable, 2-step handoff, defaults to the samcrew multisig) rather than read from memba_market_config — keeps the money path locally unit-testable. Keep it in sync with memba_market_config.GetTreasury().

Constants 5

const DefaultRegistrationFee, MaxRegistrationFee, MaxNameLen, MaxTaglineLen, MaxDescrLen, MaxCategoryLen, MaxURLLen, MaxPkgPathLen, MaxCIDLen, MaxReasonLen, MaxScreenshots, MaxResubmits, FlagHideThreshold

 1const (
 2	// DefaultRegistrationFee is the launch listing fee: 1 GNOT (1_000_000 ugnot).
 3	DefaultRegistrationFee = int64(1_000_000)
 4	// MaxRegistrationFee caps a fat-finger / compromised-proposal fee at 100 GNOT.
 5	MaxRegistrationFee = int64(100_000_000)
 6
 7	MaxNameLen     = 80
 8	MaxTaglineLen  = 140
 9	MaxDescrLen    = 2000
10	MaxCategoryLen = 40
11	MaxURLLen      = 400
12	MaxPkgPathLen  = 200
13	MaxCIDLen      = 100
14	MaxReasonLen   = 500
15	// MaxScreenshots bounds the per-listing screenshot gallery.
16	MaxScreenshots = 6
17	// MaxResubmits bounds how many times a publisher can edit/resubmit a listing, so a
18	// reject→edit→pending loop can't grief the curator queue indefinitely.
19	MaxResubmits = 5
20	// FlagHideThreshold auto-hides a listing (live OR pending) from the public lists once
21	// this many distinct addresses have flagged it.
22	FlagHideThreshold = 5
23)
source

const MaxClearBatch

1const MaxClearBatch = 200
source

MaxClearBatch bounds how many per-address flag marks one ClearFlags call removes, so a mega-brigade (thousands of sybil flags) cannot gas-lock the reset — the curator just calls ClearFlags repeatedly until the count reaches zero.

const RenderMaxRows

1const RenderMaxRows = 100
source

RenderMaxRows bounds the Render() output so a large catalog can never make the realm render-DoS (mirrors the feed's live-only bounded render).

Functions 32

func AcceptOwnership

crossing Action
1func AcceptOwnership(cur realm)
source

AcceptOwnership completes the handoff. Only the staged pendingOwner may call it. The former owner's curator grant is revoked atomically; the successor can curate immediately and may explicitly re-appoint the former owner later.

func AddCurator

crossing Action
1func AddCurator(cur realm, addr address)
source

AddCurator grants the curate role (approve pending listings). Owner only.

func AppCount

Action
1func AppCount() int
source

AppCount returns the total number of registered listings (any status).

func ApproveApp

crossing Action
1func ApproveApp(cur realm, pkgPath string)
source

ApproveApp flips a pending (or previously-live) listing live. Curator-only.

func ClearFlags

crossing Action
1func ClearFlags(cur realm, pkgPath string)
source

ClearFlags resets a listing's community-flag state after curator review. Curator-only. Without it a flag-hidden listing stays hidden FOREVER: FlagCount never decrements and survives every status transition, so FlagHideThreshold (5) sybil addresses could permanently disappear any live app. Clearing also deletes the per-address dedupe marks — the community can re-flag if the concern is real, and every clear is an emitted event, so a curator whitewashing a bad listing is publicly visible on-chain.

func DelistApp

crossing Action
1func DelistApp(cur realm, pkgPath string)
source

DelistApp removes a listing from public view. The publisher or a curator may do it.

func EditListing

crossing Action
1func EditListing(
2	cur realm,
3	pkgPath, name, tagline, descr, category, iconCID, screenshotsCSV, appURL string,
4)
source

EditListing lets the publisher update a listing that is NOT live/delisted — i.e. a pending or rejected one — and resets it to `pending` for (re-)review. Editing a live listing is structurally forbidden so a Verified badge can never be bait-and-switched. Bounded by MaxResubmits so a reject→edit loop can't grief the queue.

func FinalizeSeed

crossing Action
1func FinalizeSeed(cur realm)
source

FinalizeSeed permanently seals SeedListing (one-way latch). Owner-only.

func FlagApp

crossing Action
1func FlagApp(cur realm, pkgPath string)
source

FlagApp lets any user flag a publicly-listed (live OR pending) listing once. At FlagHideThreshold distinct flags the listing drops from the public lists (isVisible), giving the public Unverified/pending tab a community safety valve; a curator can then Delist/Reject.

func GetListingJSON

Action
1func GetListingJSON(pkgPath string) string
source

GetListingJSON returns a single listing (any status) with its full detail (descr + screenshots), or the JSON literal `null` if the package path is not registered.

func GetStatsJSON

Action
1func GetStatsJSON() string
source

GetStatsJSON returns per-status counts (served from O(1) counters) for the store header.

func IsCurator

Action
1func IsCurator(a string) bool
source

IsCurator reports whether an address may approve/reject listings (the curator-dashboard gate).

func ListByPublisherJSON

Action
1func ListByPublisherJSON(publisher string, offset, limit int) string
source

ListByPublisherJSON returns a bounded window of a publisher's listings across ALL statuses (the My-Submissions view) — no flag filter, since a publisher sees their own flagged/rejected.

func ListByStatusJSON

Action
1func ListByStatusJSON(status string, offset, limit int) string
source

ListByStatusJSON returns a bounded window of listings in `status`. Public tabs (live, pending) exclude flag-hidden listings; rejected/delisted are returned raw (used by curator/owner views). An unknown status returns an empty array — the client passes a fixed enum, never free text.

func ListLiveJSON

Action
1func ListLiveJSON(offset, limit int) string
source

ListLiveJSON returns a bounded JSON array of the visible (live, un-flag-hidden) listings — the Verified tab. Bounded by scanning only the "live" index slice.

func Pause

crossing Action
1func Pause(cur realm, state bool)
source

Pause is the kill switch: while paused, RegisterApp aborts (reads stay available). Owner only.

func RegisterApp

crossing Action
1func RegisterApp(
2	cur realm,
3	pkgPath, name, tagline, descr, category, iconCID, screenshotsCSV, appURL string,
4) uint64
source

RegisterApp lists a new app. The caller pays EXACTLY registrationFee ugnot with the call; the whole fee is forwarded to the treasury (no custody). The listing starts `pending`.

func RejectApp

crossing Action
1func RejectApp(cur realm, pkgPath, reason string)
source

RejectApp declines a pending submission, recording a reason and granting a one-time free resubmit credit. Curator-only; only a pending app can be rejected.

func RemoveCurator

crossing Action
1func RemoveCurator(cur realm, addr address)
source

RemoveCurator revokes the curate role. Owner only.

func Render

1func Render(path string) string
source

Render shows the live catalog (bounded) — a read-only trust surface for gnoweb. The frontend reads structured data via the getters, not this markdown.

func RestoreApp

crossing Action
1func RestoreApp(cur realm, pkgPath string)
source

RestoreApp brings a delisted app back to `pending` (re-curation required). Curator-only.

func SeedListing

crossing Action
1func SeedListing(
2	cur realm,
3	id uint64,
4	pkgPath, name, tagline, descr, category, iconCID, screenshotsCSV, appURL, publisherStr, status string,
5	flagCount int,
6	createdAt int64,
7)
source

SeedListing imports a listing verbatim (Id, CreatedAt, FlagCount, Status, Publisher) during a v2→v3 migration. Owner-only, NON-payable (never reads OriginSend / moves funds), dedupe-guarded. After the migration the owner calls FinalizeSeed, permanently sealing this entrypoint — without that latch it would be a standing backdoor to forge fee-free listings with arbitrary publisher.

func SetRegistrationFee

crossing Action
1func SetRegistrationFee(cur realm, fee int64)
source

SetRegistrationFee sets the flat listing fee in ugnot (0..MaxRegistrationFee). Zero is allowed (a fee waiver). Owner only.

func SetTreasury

crossing Action
1func SetTreasury(cur realm, addr address)
source

SetTreasury repoints the fee recipient. Must be non-empty (an empty treasury would fail-close RegisterApp). Owner only. Keep this in sync with memba_market_config.GetTreasury().

func TransferOwnership

crossing Action
1func TransferOwnership(cur realm, newOwner address)
source

TransferOwnership stages a new owner; it takes effect only after AcceptOwnership is called BY that address (2-step, so a typo can't brick admin). A later call replaces the staged address; CancelOwnershipTransfer withdraws it.

Types 2

type GovernanceListing

struct
1type GovernanceListing struct {
2	Exists                      bool
3	ContentHash, Status, Reason string
4	Credit                      bool
5	Flags                       int
6	ClearKeys                   string
7}
source

Scalars only: no persistent Listing pointer or screenshot slice escapes.

type Listing

struct
 1type Listing struct {
 2	Id                 uint64
 3	PkgPath            string
 4	Name               string
 5	Tagline            string
 6	Descr              string
 7	Category           string
 8	IconCID            string
 9	ScreenshotCIDs     []string
10	AppURL             string
11	Publisher          address
12	Status             string
13	RejectReason       string
14	PaidResubmitCredit bool
15	ResubmitCount      int
16	FlagCount          int
17	CreatedAt          int64
18}
source

Listing is one app. PkgPath (the realm/package path) is the unique key.

Imports 9

Source Files 6