Transaction
B918B1C70BDF76…BC29DD0F9755
Block 265,708 · index 0 · indexed
Summary
- Hash
- B918B1C70BDF76153C8FC55E314D7844429447F105DB0275BD59BC29DD0F9755
- Block
- 265,708
- Size
- 8414 bytes
- Gas used
- 15,957,597 / 30,000,000
- Fee
- 60000 ugnot
- Status
- success
Messages
- Attached funds
- 5000000 ugnot
Arguments · 11
- #1memba_market_config
- #2admin.gno
- #3// Admin operations — DAO-gated, with a 2-step admin handoff and a hard fee ceiling. // There is intentionally NO Pause() here (see config.gno SAFETY note): pausing the // fee spine would brick every engine at once. Per-engine Pause() is the kill switch. package memba_market_config import ( "strconv" "chain" ) func itoa(n int64) string { return strconv.FormatInt(n, 10) } func assertAdmin() { if caller() != admin { panic("admin only") } } // SetFeeBPS sets a lane's protocol fee in basis points, clamped to [0, MaxFeeBPS]. // A value above the 5% ceiling is rejected so a misconfiguration can never overcharge. func SetFeeBPS(cur realm, lane string, bps int64) { assertAdmin() if lane == "" { panic("empty lane") } if bps < 0 || bps > MaxFeeBPS { panic("feeBPS out of range") } feeByLane.Set(lane, bps) chain.Emit("FeeBPSChanged", "lane", lane, "bps", itoa(bps)) } // SetTreasury repoints where every lane's protocol fee flows. func SetTreasury(cur realm, addr address) { assertAdmin() if addr == "" { panic("empty treasury") } treasury = addr chain.Emit("TreasuryChanged", "treasury", addr.String()) } // TransferAdmin proposes a new admin (the memba_dao executor). The 2-step handoff // requires the new admin to AcceptAdmin, so a transfer to a wrong or uncontrolled // address cannot lock the realm. func TransferAdmin(cur realm, newAdmin address) { assertAdmin() if newAdmin == "" { panic("empty admin") } if !newAdmin.IsValid() { panic("invalid address: " + newAdmin.String()) } if newAdmin == admin { panic("newAdmin is the current admin") } pendingAdmin = newAdmin chain.Emit("AdminTransferProposed", "pending", newAdmin.String()) } // CancelAdminTransfer withdraws a staged handoff. Admin only. func CancelAdminTransfer(cur realm) { assertAdmin() if pendingAdmin == "" { panic("no pending admin") } cancelled := pendingAdmin pendingAdmin = "" chain.Emit("AdminTransferCancelled", "pending", cancelled.String()) } // AcceptAdmin completes the handoff. Only the pending admin can call it. func AcceptAdmin(cur realm) { if pendingAdmin == "" { panic("no pending admin") } if caller() != pendingAdmin { panic("not pending admin") } admin = pendingAdmin pendingAdmin = "" chain.Emit("AdminAccepted", "admin", admin.String()) } // GetPendingAdmin returns the staged successor, or the empty address. func GetPendingAdmin() address { return pendingAdmin }
- #4config.gno
- #5// Package memba_market_config is the DAO-owned fee spine for the Memba marketplace. // It is the single source of truth for the per-lane protocol fee (basis points) and // the treasury that receives it. Every trade engine (NFT, services, token-OTC, agents) // reads GetFeeBPS(lane) + GetTreasury() at settlement, so the DAO sets the rate ONCE // and it applies everywhere — that shared read is what makes the lanes one marketplace. // // SAFETY (panel finding C1): the read getters are PURE and NON-FAILING — they never // panic, and there is intentionally NO Pause(). A per-trade cross-realm read that // could fail or halt would be a single point of failure able to brick every engine at // once; instead an engine always gets a usable, bounded value here and clamps locally, // while each engine keeps its OWN Pause() as the real kill switch. The fee is bounded // to [0, MaxFeeBPS] on write, so a reader can trust the bound without re-checking. package memba_market_config import ( "chain" "chain/runtime/unsafe" "gno.land/p/samcrew/avl" ) // No authority address is compiled in. The publishing transaction's signer (on // gnoland-1 the samcrew namespace multisig, the stamped creator at enable time) // is seeded as admin AND treasury at package load; the DAO takes over through the // two-step TransferAdmin/AcceptAdmin and repoints fees with SetTreasury. // // Under `gno test` every package is initialized with an empty origin caller, and // this realm is read cross-realm by every engine's test suite, so an empty seed // would leave the engines' fee-spine tests nothing to assert against. In that // one case the seed becomes unseededAuthority: a sub-address of this realm that // no code ever mints, so no on-chain caller can ever be it, while a test can // impersonate it with testing.NewUserRealm. On chain the origin caller at load // is never empty, so this branch is unreachable there. const ( // MaxFeeBPS is the 5% hard ceiling. SetFeeBPS rejects anything above it, so a // fat-finger or a compromised proposal can never make a lane overcharge. MaxFeeBPS = int64(500) // DefaultFeeBPS is returned for an unset/unknown lane so engines always get a // usable value (2.0%). DefaultFeeBPS = int64(200) ) var ( admin address pendingAdmin address treasury address feeByLane = avl.NewTree() // lane string -> int64 bps ) // unseededAuthority is the test-VM stand-in for a publisher; see the note above. var unseededAuthority = chain.DerivePkgSubAddr("gno.land/r/samcrew/memba_market_config", "unseeded") // seedAuthority makes the publisher admin and treasury. It runs once at load. func seedAuthority(publisher address) { if publisher == "" { publisher = unseededAuthority } admin = publisher pendingAdmin = "" treasury = publisher } func init() { seedAuthority(unsafe.OriginCaller()) // Seed the launch lanes. "agent" is intentionally unset (TBD) and resolves to // DefaultFeeBPS until the DAO sets it when that lane is built. feeByLane.Set("nft", int64(200)) // 2.0% feeByLane.Set("service", int64(200)) // 2.0% release fee (the 5% cancel fee is escrow-internal → freelancer) feeByLane.Set("token", int64(50)) // 0.5% OTC — competitive vs DEX ~0.3% } func caller() address { return unsafe.PreviousRealm().Address() } // ── Read getters — PURE, NON-FAILING (never panic, no Pause) ────────────────── // GetFeeBPS returns the protocol fee in basis points for a lane. An unset or unknown // lane returns DefaultFeeBPS. The result is always within [0, MaxFeeBPS] (enforced on // write). Never panics — an engine must always be able to settle. func GetFeeBPS(lane string) int { v, ok := feeByLane.Get(lane) if !ok { return int(DefaultFeeBPS) } return int(v.(int64)) } // GetTreasury returns the address that receives the protocol fee on every lane. func GetTreasury() address { return treasury } // GetLaneConfig returns (feeBPS, treasury) in one read for an engine's settlement path. func GetLaneConfig(lane string) (int, address) { return GetFeeBPS(lane), treasury } // GetAdmin returns the current admin (the multisig, or the memba_dao executor after // the 2-step handoff). func GetAdmin() address { return admin }
- #6gnomod.toml
- #7module = "gno.land/r/samcrew/memba_market_config" gno = "0.9"
- #8render.gno
- #9// Render — public read-only view + audit surface for the fee config. package memba_market_config import ( "strings" "gno.land/p/nt/ufmt/v0" ) func Render(path string) string { var sb strings.Builder sb.WriteString("# Memba Marketplace — Fee Config\n\n") sb.WriteString("DAO-owned per-lane protocol fee + treasury. Engines read this at settlement.\n\n") sb.WriteString(ufmt.Sprintf("**Admin:** %s\n", admin.String())) if pendingAdmin != "" { sb.WriteString(ufmt.Sprintf("**Pending admin:** %s\n", pendingAdmin.String())) } sb.WriteString(ufmt.Sprintf("**Treasury:** %s\n", treasury.String())) sb.WriteString(ufmt.Sprintf("**Fee ceiling:** %d bps (5%%)\n\n", MaxFeeBPS)) sb.WriteString("| Lane | Fee (bps) |\n|------|-----------|\n") feeByLane.Iterate("", "", func(key string, val interface{}) bool { sb.WriteString(ufmt.Sprintf("| %s | %d |\n", key, val.(int64))) return false }) sb.WriteString(ufmt.Sprintf("\n*Unset lanes default to %d bps.*\n", DefaultFeeBPS)) return sb.String() }
- #10/gno.MemPackageType
- #11 MPUserAll
Result log
msg:0,success:true,log:,events:[]