Realm on pearl-1
market
gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/market
realmapplicationmarketplace
Multi-domain listing/offer marketplace composing coinio + feeledger + avl/v0 + sanitize/v0; seven attack shapes rejected live.
Identity
| Import path | gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/market |
|---|---|
| Kind | realm (/r/) |
| Chain | pearl-1 |
| Namespace | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Realm address | g1y8nzwrv7ufe8c5r747vfpjm8jvayhgvddqwl2g derived, never confirmed against the realm |
Provenance
chain-attested| Deployed at height | 585,791 |
|---|---|
| Deploy transaction | 76293576775be9db30ab097944990e9c5836d00e52a84b979d3284b7707f82d9 look it up on the RPC |
| Deployer | g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3 |
| Gas used | 34,983,448 |
| Storage | 35,356 bytes, deposit 3535600ugnot |
| Files on chain | gnomod.toml market.gno |
| Deployed bytes | market.gno — 18,476 bytes |
| sha256 | 43cece28779b7d4c081c2bf6dc70c7fb5b6e80776792b8f0a6ee606696e10fd4 |
Do not take the hash above on trust. $download returns the bytes pearl-1 is actually running; this command fetches them and prints their digest, which should equal the one in the table:
curl -sS 'https://pearl.testnets.gno.land/r/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/market$download&file=market.gno' | shasum -a 256Expected: 43cece28779b7d4c081c2bf6dc70c7fb5b6e80776792b8f0a6ee606696e10fd4 — 18,476 bytes. This was checked for all 21 packages while building this site's architecture record; every one matched. Use curl: pearl's edge answers Python's default user-agent with HTTP 403.
API
chain-derived 23 exported functions.
Every function below deep-links to gnoweb's call builder, which generates a ready-to-run gnokey maketx call for it:
AcceptAdminBalanceOfBuyCancelListingClaimClaimAllCreateListingDescriptionFeeBpsFeesAccruedHeldLiabilitiesListingInfoNumListingsQuoteRenderSetFeeBpsSetFeeRecipientSurplusSweepDenomTransferAdminUsersTotalWithdrawFees
Overview
Realm market is a custodial GNOT marketplace for goods listings: sellers list (title, description, price), a buyer purchases by paying the exact price, the marketplace holds the value until the seller claims their proceeds (price minus a transparent, snapshotted protocol fee).
COMPOSITION (per the recorded DISCOVERY / REUSE ANALYSIS): balance accounting is feeledger, coin movement is coinio, free-text render safety is the ecosystem sanitizer p/nt/markdown/sanitize/v0. This realm owns only the listing state machine. Two patterns are adopted from inspected ecosystem marketplaces: non-payable entrypoints REFUSE accidental -send instead of stranding it as surplus (nsmarket/v4's assertNoSend), and admin handoff is 2-STEP (memba_appstore_v2's TransferOwnership/AcceptOwnership), closing the one-step-transfer trade-off carried by earlier realms.
LIFECYCLE (terminal states are frozen; one transition per listing):
CreateListing (anyone, no coins) : status Active; the fee bps is SNAPSHOTTED, subject to the seller's own maxFeeBps ceiling Buy (EOA + -send == price) : Active -> Sold, atomically: proceeds (price - snapshot fee) credit the seller's claimable balance, the fee accrues to the pot, the buyer is recorded CancelListing (seller only) : Active -> Cancelled (no funds are involved; listings hold no value) Claim / ClaimAll (anyone) : pays out the caller's own claimable proceeds WithdrawFees (fee recipient) : pays out the fee pot
LISTINGS ARE IMMUTABLE: there is no price update — cancel and relist (new id). Together with Buy's EXACT-envelope rule this closes the listing-manipulation race twice over: a cancelled/relisted listing fails Buy's status check, and any price change fails the envelope check — either way the buyer's coins revert with the transaction. Buyers are structurally indifferent to fee changes: they pay the listed price; the fee comes out of the seller's proceeds at the bps snapshotted when the SELLER listed (with the seller's own ceiling — the creation-time fee race is closed the same way grants closes it).
AUTHORIZATION: every identity derives from the crossing entrypoint's cur.Previous().Address(); no function takes a caller identity as a parameter. Sellers may be EOAs or realms (they claim under their own address); buyers must be EOAs (coinio.Receive is the receipt-guaranteed shape). Self-purchase is rejected.
REALM-SELLER CAVEAT (audit Y1): assertNoSend reads the ORIGIN transaction's send envelope, so a realm seller must call CreateListing/CancelListing/Claim* in a transaction whose origin carried no -send — otherwise the guard fails closed even though this realm received nothing. Not third-party triggerable (nobody can attach a send to someone else's transaction); the workaround is a separate transaction.
MONETARY INVARIANT (conservation): listings hold NO value, so with H = ugnot held at the realm address, U = claimable seller proceeds, F = the fee pot, S >= 0 out-of-band surplus:
H == U + F + S
Buy raises H by exactly price and U+F by exactly price (feeledger guarantees credited + fee == amount); Claim*/WithdrawFees debit the ledger before coinio.Payout moves the identical amount out; any panic aborts the whole transaction; this realm never issues or removes coins. Surplus is recoverable only via SweepDenom (fee recipient), which reserves Liabilities() = U + F.
APPLICATION INVARIANT: status transitions Active -> {Sold, Cancelled} exactly once; Sold if and only if a buyer is recorded; for every sold listing, proceeds + fee == price at the snapshotted bps.
Imports
chainchain/runtime/unsafegno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/coiniogno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/feeledgergno.land/p/nt/avl/v0gno.land/p/nt/markdown/sanitize/v0strconv
Constants and variables
Listing status values.
const (
StatusActive = "active"
StatusSold = "sold"
StatusCancelled = "cancelled"
)
Input bounds.
const (
MaxTitleLen = 80
MaxDescLen = 2000
MinPrice = int64(1)
)
Denom is the only asset this realm accepts.
const Denom = "ugnot"
MaxFeeBps is the hard protocol-fee cap: 1000 bps = 10%.
const MaxFeeBps = int64(1000)
Functions
AcceptAdmin
func AcceptAdmin(cur realm)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asAcceptAdmin(cross(cur), ...).
AcceptAdmin completes a staged admin handoff. Only the staged address may call it.
BalanceOf
func BalanceOf(addr address) int64
BalanceOf returns addr's claimable proceeds.
Buy
func Buy(cur realm, id int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asBuy(cross(cur), ...).
Buy purchases an active listing. The buyer must be a direct EOA caller and attach EXACTLY the listed price in ugnot — any mismatch (including a price the seller changed by cancel-and-relist) aborts and the coins revert with the transaction. Settlement is atomic: the seller's proceeds (price minus the snapshotted fee) become claimable, the fee accrues to the pot, and the buyer is recorded. Terminal.
CancelListing
func CancelListing(cur realm, id int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asCancelListing(cross(cur), ...).
CancelListing withdraws an active listing. Only the seller may cancel; no funds are involved. Terminal.
Claim
func Claim(cur realm, amount int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asClaim(cross(cur), ...).
Claim sends amount ugnot of the caller's claimable proceeds back to the caller.
ClaimAll
func ClaimAll(cur realm)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asClaimAll(cross(cur), ...).
ClaimAll sends the caller's entire claimable proceeds back to the caller. Fails if there is nothing to claim.
CreateListing
func CreateListing(cur realm, title, description string, price, maxFeeBps int64) int64
Crossing function. Callable from a transaction via
MsgCall, and from another realm asCreateListing(cross(cur), ...).
CreateListing publishes an immutable listing and returns its id. No coins are accepted (the storage deposit the caller pays is the anti-spam). The current protocol fee is snapshotted into the listing and must not exceed maxFeeBps, the ceiling the seller signed for; pass MaxFeeBps to accept any legal fee. Sellers may be EOAs or realms.
Description
func Description(id int64) string
Description returns a listing's raw description text.
FeeBps
func FeeBps() int64
FeeBps returns the fee that will be snapshotted into newly created listings (existing listings keep their own snapshot).
FeesAccrued
func FeesAccrued() int64
FeesAccrued returns the fee pot (the F term).
Held
func Held() int64
Held returns the ugnot actually held at the realm address (H).
Liabilities
func Liabilities() int64
Liabilities returns everything this realm owes: UsersTotal + FeesAccrued (listings hold no value by construction).
ListingInfo
func ListingInfo(id int64) (seller address, title string, price, feeBps int64, status string, buyer address)
ListingInfo returns a listing's fields by value: seller, title, price, snapshotted fee bps, status, and buyer (zero unless sold).
NumListings
func NumListings() int64
NumListings returns how many listings have ever been created.
Quote
func Quote(id int64) (price, fee, toSeller int64)
Quote returns what a buyer pays and what the seller would receive for a listing, at its SNAPSHOTTED fee — the same arithmetic Buy performs (pattern from nsmarket/v4: a fee discovered after signing is a fee the seller was not told about).
Render
func Render(path string) string
Render shows the market at "" and a listing detail at "<id>". Titles are charset-restricted; descriptions pass through the ecosystem sanitizer.
SetFeeBps
func SetFeeBps(cur realm, bps int64)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asSetFeeBps(cross(cur), ...).
SetFeeBps sets the protocol fee snapshotted into FUTURE listings. Existing listings keep the fee they were created under. Admin only; bounded by [0, MaxFeeBps].
SetFeeRecipient
func SetFeeRecipient(cur realm, next address)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asSetFeeRecipient(cross(cur), ...).
SetFeeRecipient re-points the fee/surplus role, including the pot accrued so far. Admin only; zero address rejected.
Surplus
func Surplus() int64
Surplus returns Held() - Liabilities() (the S term).
SweepDenom
func SweepDenom(cur realm, denom string)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asSweepDenom(cross(cur), ...).
SweepDenom sends the surplus of a single denomination to the fee recipient. For ugnot only the excess over Liabilities() moves; other denoms move wholly. Only the fee recipient may call it.
TransferAdmin
func TransferAdmin(cur realm, next address)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asTransferAdmin(cross(cur), ...).
TransferAdmin STAGES a successor admin; the handoff completes only when that address calls AcceptAdmin (2-step, so a typo cannot brick administration — pattern adopted from memba_appstore_v2). Admin only; zero address rejected. Re-staging overwrites a previous stage.
UsersTotal
func UsersTotal() int64
UsersTotal returns the sum of all claimable proceeds (the U term).
WithdrawFees
func WithdrawFees(cur realm)
Crossing function. Callable from a transaction via
MsgCall, and from another realm asWithdrawFees(cross(cur), ...).
WithdrawFees sends the accrued fee pot to the fee recipient. Only the fee recipient may call it.
Doc text is reproduced as vm/qdoc returns it. The node markdown-escapes doc comments, so a bracket or angle bracket may carry a backslash the committed source does not have. The source itself is at source and in this repository.
Dependencies
chain-attested| Imports | chain, chain/runtime/unsafe, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/coinio, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/feeledger, gno.land/p/nt/avl/v0, gno.land/p/nt/markdown/sanitize/v0, strconv |
|---|---|
| First-party dependencies | gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/coinio, gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/feeledger |
| Used by | none |
Known limitations
curatedThe manifest records no limitation for this package. That is an absence of a recorded caveat, not a proof that none exists — the deployment record below is the fuller account, and it always carries its own "what was NOT verified live" section.
Source and records
| Source file | pearl/r/market/market.gno at commit 6a510c665a53 in the project repository (not public — the digest command above is the check that needs no repository) |
|---|---|
| Matches the deployed bytes | yes — byte-identical |
| Records | catalog/primitives.md#pre-pipeline-realmspearl/DEPLOYMENT.md |