PathrockNetwork Gno Explorer
HomeBlocksTransactionsRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/r/moul/x/daily/dutchauction/v0

Realm
Open in gnoweb ↗

Overview

Kind
Realm (renderable)
Name
v0
Namespace
moul / x / daily / dutchauction
Files
3 (README)(gnomod.toml)
Exported functions
6
Module
gno.land/r/moul/x/daily/dutchauction/v0
gno
0.9

Files (3)

  • README.mdmarkdown
  • gnomod.tomltoml
  • dutchauction.gnogno
dutchauction.gnogno
1// Package dutchauction ports the classic Solidity Dutch-auction pattern to2// gno.land: a seller lists an item at a high starting price that decays3// linearly, block by block, down to a floor price. The first buyer to call4// Buy pays whatever the price is at that moment — real ugnot, escrowed by5// the chain with the transaction and forwarded to the seller on settlement.6// Overpayment is refunded automatically. State-mutating functions are7// crossing functions (`cur realm`) per the gno 0.9 interrealm convention.8package dutchauction910import (11	"errors"12	"strconv"13	"strings"1415	"chain"16	"chain/banker"17	"chain/runtime"18	"chain/runtime/unsafe"1920	"gno.land/p/nt/avl/v0"21)2223const denom = "ugnot"2425type status int2627const (28	statusActive status = iota29	statusSold30	statusCancelled31)3233func (s status) String() string {34	switch s {35	case statusActive:36		return "active"37	case statusSold:38		return "sold"39	case statusCancelled:40		return "cancelled"41	default:42		return "unknown"43	}44}4546// auction is one listing: price falls linearly from StartPrice to47// FloorPrice over Duration blocks starting at StartHeight, then holds flat48// at FloorPrice until bought or cancelled.49type auction struct {50	ID          string51	Item        string52	Seller      address53	StartPrice  int6454	FloorPrice  int6455	StartHeight int6456	Duration    int6457	Status      status58	Buyer       address59	SoldPrice   int6460}6162var (63	auctions    avl.Tree // id string -> *auction64	nextID      uint6465	totalListed uint6466	totalSold   uint6467	totalVolume int6468)6970var (71	errEmptyItem    = errors.New("dutchauction: item description required")72	errBadPrice     = errors.New("dutchauction: startPrice must be greater than floorPrice, floorPrice must be >= 0")73	errBadDuration  = errors.New("dutchauction: durationBlocks must be positive")74	errNotFound     = errors.New("dutchauction: auction not found")75	errNotActive    = errors.New("dutchauction: auction is not active")76	errNotSeller    = errors.New("dutchauction: caller is not the seller")77	errNotUserCall  = errors.New("dutchauction: buy must be a direct user transaction")78	errUnderpaid    = errors.New("dutchauction: payment below current price")79	errSpoofedRealm = errors.New("dutchauction: spoofed realm")80)8182// caller authenticates the crossing frame and returns the immediate caller.83func caller(cur realm) address {84	if !cur.IsCurrent() {85		panic(errSpoofedRealm)86	}87	return cur.Previous().Address()88}8990// List creates a new Dutch auction for item, starting at startPrice ugnot91// and falling linearly to floorPrice over durationBlocks. Returns the new92// auction's ID. Crossing function.93func List(cur realm, item string, startPrice, floorPrice, durationBlocks int64) string {94	seller := caller(cur)9596	item = strings.TrimSpace(item)97	if item == "" {98		panic(errEmptyItem)99	}100	if floorPrice < 0 || startPrice <= floorPrice {101		panic(errBadPrice)102	}103	if durationBlocks <= 0 {104		panic(errBadDuration)105	}106107	nextID++108	id := strconv.FormatUint(nextID, 10)109	auctions.Set(id, &auction{110		ID:          id,111		Item:        item,112		Seller:      seller,113		StartPrice:  startPrice,114		FloorPrice:  floorPrice,115		StartHeight: runtime.ChainHeight(),116		Duration:    durationBlocks,117		Status:      statusActive,118	})119	totalListed++120	return id121}122123// Buy purchases auction id at its current price. The caller must send at124// least that many ugnot with the transaction; any excess is refunded125// immediately. Only a direct EOA transaction (MsgCall) may buy, so the126// payment envelope can't be spoofed via an ephemeral MsgRun realm. Returns127// the price actually paid. Crossing function.128func Buy(cur realm, id string) int64 {129	if !cur.Previous().IsUserCall() {130		panic(errNotUserCall)131	}132	buyer := cur.Previous().Address()133134	a := getAuction(id)135	if a.Status != statusActive {136		panic(errNotActive)137	}138139	price := priceAt(a, runtime.ChainHeight())140	sent := unsafe.OriginSend()141	paid := sent.AmountOf(denom)142	if paid < price {143		panic(errUnderpaid)144	}145146	a.Status = statusSold147	a.Buyer = buyer148	a.SoldPrice = price149	totalSold++150	totalVolume += price151152	bnk := banker.NewBanker(banker.BankerTypeOriginSend, cur)153	pkgAddr := cur.Address()154	if paid > price {155		bnk.SendCoins(pkgAddr, buyer, chain.NewCoins(chain.NewCoin(denom, paid-price)))156	}157	bnk.SendCoins(pkgAddr, a.Seller, chain.NewCoins(chain.NewCoin(denom, price)))158159	return price160}161162// Cancel withdraws an active auction. Only the seller may cancel, and only163// before it's bought. Crossing function.164func Cancel(cur realm, id string) {165	who := caller(cur)166167	a := getAuction(id)168	if a.Status != statusActive {169		panic(errNotActive)170	}171	if who != a.Seller {172		panic(errNotSeller)173	}174	a.Status = statusCancelled175}176177// CurrentPrice returns id's price at the current chain height.178func CurrentPrice(id string) int64 {179	return priceAt(getAuction(id), runtime.ChainHeight())180}181182// AuctionInfo returns a snapshot of auction id.183func AuctionInfo(id string) (item string, seller address, status string, currentPrice int64, floorPrice int64, startPrice int64) {184	a := getAuction(id)185	return a.Item, a.Seller, a.Status.String(), priceAt(a, runtime.ChainHeight()), a.FloorPrice, a.StartPrice186}187188// priceAt computes the linear-decay price of a at the given height. Once189// settled (sold or cancelled), the price is frozen: SoldPrice for a sale,190// zero for a cancellation.191func priceAt(a *auction, height int64) int64 {192	if a.Status != statusActive {193		return a.SoldPrice194	}195	elapsed := height - a.StartHeight196	if elapsed <= 0 {197		return a.StartPrice198	}199	if elapsed >= a.Duration {200		return a.FloorPrice201	}202	drop := a.StartPrice - a.FloorPrice203	return a.StartPrice - drop*elapsed/a.Duration204}205206func getAuction(id string) *auction {207	v := auctions.Get(id)208	if v == nil {209		panic(errNotFound)210	}211	return v.(*auction)212}213214// Render produces the gnoweb Markdown view: a table of every auction,215// newest first, with live prices. Not a crossing function.216func Render(path string) string {217	var b strings.Builder218	b.WriteString("# Dutch Auction\n\n")219	b.WriteString("A price that only ever falls. List an item high; it decays block by ")220	b.WriteString("block toward a floor; the first `Buy` call wins it at whatever the ")221	b.WriteString("price is at that instant. Real ugnot, escrowed by the chain and paid ")222	b.WriteString("straight to the seller — overpayment is refunded automatically.\n\n")223224	b.WriteString("- **Total listed:** " + strconv.FormatUint(totalListed, 10) + "\n")225	b.WriteString("- **Total sold:** " + strconv.FormatUint(totalSold, 10) + "\n")226	b.WriteString("- **Total volume:** " + strconv.FormatInt(totalVolume, 10) + " ugnot\n")227	b.WriteString("- **Current height:** " + strconv.FormatInt(runtime.ChainHeight(), 10) + "\n\n")228229	if auctions.Size() == 0 {230		b.WriteString("_No auctions yet — call `List` to open one._\n")231		return b.String()232	}233234	b.WriteString("## Auctions\n\n")235	b.WriteString("| ID | Item | Status | Price now | Floor | Start | Seller |\n")236	b.WriteString("| ---: | --- | --- | ---: | ---: | ---: | --- |\n")237238	height := runtime.ChainHeight()239	ids := make([]uint64, 0, auctions.Size())240	auctions.Iterate("", "", func(key string, value any) bool {241		n, _ := strconv.ParseUint(key, 10, 64)242		ids = append(ids, n)243		return false244	})245	for i := len(ids) - 1; i >= 0; i-- {246		id := strconv.FormatUint(ids[i], 10)247		v := auctions.Get(id)248		a := v.(*auction)249		b.WriteString("| " + a.ID + " | " + a.Item + " | " + a.Status.String() +250			" | " + strconv.FormatInt(priceAt(a, height), 10) +251			" | " + strconv.FormatInt(a.FloorPrice, 10) +252			" | " + strconv.FormatInt(a.StartPrice, 10) +253			" | `" + a.Seller.String() + "` |\n")254	}255256	return b.String()257}258

Functions

  • AuctionInfo(id string) (item string, seller string, status string, currentPrice int64, floorPrice int64, startPrice int64)

  • Buy(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, id string) int64

  • Cancel(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, id string)

  • CurrentPrice(id string) int64

  • List(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, item string, startPrice int64, floorPrice int64, durationBlocks int64) string

  • Render(path string) string

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.

Rendered

RenderedRawgnoweb ↗

vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.