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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

render.gno

6.11 Kb · 169 lines
  1package markets
  2
  3import (
  4	"chain/runtime"
  5	"chain/runtime/unsafe"
  6	"strconv"
  7	"strings"
  8	"time"
  9)
 10
 11// appURL is the exchange interface; the realm page links to it for trading.
 12const appURL = "https://six-seven-67-phi.vercel.app"
 13
 14// pagePath is this realm's gnoweb path ("/r/<namespace>/markets"), captured at
 15// deployment so links between its pages work on any chain and namespace.
 16var pagePath = strings.TrimPrefix(unsafe.CurrentRealm().PkgPath(), "gno.land")
 17
 18// historyRows is how many recent quotes a market page lists.
 19const historyRows = 10
 20
 21// Render draws the realm's public page on gnoweb: the market list at the root
 22// and one page per market at ":<id>". It is informational; the app reads JSON.
 23func Render(path string) string {
 24	if path == "" {
 25		return renderIndex()
 26	}
 27	r := records[path]
 28	if r == nil {
 29		return "# Market not found\n\nThere is no market `" + escapeCode(path) + "` in this realm.\n\n[All markets](" + pagePath + ")\n"
 30	}
 31	return renderMarket(r)
 32}
 33
 34func renderIndex() string {
 35	var out strings.Builder
 36	out.WriteString("# 67\n\n")
 37	out.WriteString("Questions about the world, priced in GNOT. A share in the outcome that happens pays 1 GNOT.\n\n")
 38	out.WriteString("[Trade on 67 with the Adena wallet](" + appURL + "/#/markets)\n\n")
 39	if len(marketIDs) == 0 {
 40		out.WriteString("No markets are listed yet.\n\n")
 41	} else {
 42		out.WriteString("| Question | Yes, GNOT | No, GNOT | Status |\n")
 43		out.WriteString("| :--- | ---: | ---: | :--- |\n")
 44		for _, id := range marketIDs {
 45			m := records[id].market
 46			out.WriteString("| [" + escapeText(m.Question) + "](" + pagePath + ":" + id + ") | ")
 47			out.WriteString(priceText(m.YesPricePpm) + " | " + priceText(scale-m.YesPricePpm) + " | " + status(m) + " |\n")
 48		}
 49		out.WriteString("\n")
 50	}
 51	if runtime.ChainID() != "gnoland-1" {
 52		out.WriteString("> [!NOTE] Testnet\n")
 53		out.WriteString("> This is " + runtime.ChainID() + ", a test network: test GNOT has no value.\n\n")
 54	}
 55	out.WriteString("Questions, rules and results come from Polymarket. 67 is independent and not affiliated with Polymarket.\n\n")
 56	out.WriteString("## How a market settles\n\n")
 57	out.WriteString("The question and its rules are fixed in this realm when a market is listed. ")
 58	out.WriteString("Every purchase is fully collateralized: the realm refuses a sale it could not pay out, and nobody can withdraw the collateral. ")
 59	out.WriteString("When Polymarket's result is final, the market's oracle records it here, and holders of the winning side claim 1 GNOT per share.\n\n")
 60	out.WriteString("[Functions](" + pagePath + "$help) and [source](" + pagePath + "$source) of this realm.\n")
 61	return out.String()
 62}
 63
 64func renderMarket(r *marketRecord) string {
 65	m := r.market
 66	var out strings.Builder
 67	out.WriteString("# " + escapeText(m.Question) + "\n\n")
 68
 69	out.WriteString("<gno-columns>\n")
 70	out.WriteString("**Yes**\n\n" + priceText(m.YesPricePpm) + " GNOT\n\n<gno-columns-sep>\n\n")
 71	out.WriteString("**No**\n\n" + priceText(scale-m.YesPricePpm) + " GNOT\n\n<gno-columns-sep>\n\n")
 72	out.WriteString("**Status**\n\n" + status(m) + "\n")
 73	out.WriteString("</gno-columns>\n\n")
 74
 75	if m.Finalized {
 76		out.WriteString("## Result\n\n")
 77		out.WriteString(m.Winner + " won. The oracle recorded it on " + dayText(m.FinalizedAt) + ". ")
 78		out.WriteString("Holders of " + m.Winner + " claim 1 GNOT per share in [the app](" + appURL + "/#/portfolio); " + priceText(m.PaidUgnot) + " GNOT has been paid so far.\n\n")
 79		out.WriteString("Evidence hash `" + m.EvidenceHash + "`\n\n")
 80	} else {
 81		out.WriteString("[Buy Yes or No on 67](" + appURL + "/#/market/" + m.ID + ")\n\n")
 82	}
 83
 84	out.WriteString("## Rules\n\n")
 85	for _, line := range strings.Split(m.Rules, "\n") {
 86		line = strings.TrimSpace(line)
 87		if line != "" {
 88			out.WriteString(escapeText(line) + "\n\n")
 89		}
 90	}
 91
 92	out.WriteString("## Source\n\n")
 93	out.WriteString("- Polymarket condition `" + m.ConditionID + "`\n")
 94	out.WriteString("- Rules hash `" + m.RulesHash + "`, the SHA-256 of the question and the rules above\n")
 95	out.WriteString("- Oracle, fixed for this market: " + string(m.Oracle) + "\n\n")
 96
 97	out.WriteString("## Record\n\n")
 98	out.WriteString("- " + countText(m.Trades, "trade", "trades") + ", " + priceText(m.VolumeUgnot) + " GNOT bought\n")
 99	out.WriteString("- Collateral " + priceText(m.EscrowUgnot) + " GNOT\n")
100	out.WriteString("- Listed on " + dayText(m.CreatedAt) + "\n\n")
101
102	out.WriteString("## Quotes\n\n")
103	out.WriteString("| Published, UTC | Yes, GNOT | No, GNOT |\n")
104	out.WriteString("| :--- | ---: | ---: |\n")
105	from := len(r.history) - historyRows
106	if from < 0 {
107		from = 0
108	}
109	for i := len(r.history) - 1; i >= from; i-- {
110		point := r.history[i]
111		out.WriteString("| " + timeText(point.At) + " | " + priceText(point.YesPricePpm) + " | " + priceText(scale-point.YesPricePpm) + " |\n")
112	}
113	out.WriteString("\n[All markets](" + pagePath + ")\n")
114	return out.String()
115}
116
117func status(m Market) string {
118	if m.Finalized {
119		return m.Winner + " won"
120	}
121	if time.Now().Unix() >= m.ClosesAt {
122		return "Awaiting the result"
123	}
124	return "Open until " + dayText(m.ClosesAt)
125}
126
127// priceText formats millionths like the app: at least two decimals, no
128// trailing zeros beyond them (420000 is "0.42", 425000 is "0.425").
129func priceText(value int64) string {
130	text := ppmPrice(value)
131	for strings.HasSuffix(text, "0") && len(text)-strings.Index(text, ".") > 3 {
132		text = text[:len(text)-1]
133	}
134	return text
135}
136
137func dayText(unix int64) string { return time.Unix(unix, 0).UTC().Format("2 January 2006") }
138func timeText(unix int64) string {
139	return time.Unix(unix, 0).UTC().Format("2 January 2006, 15:04")
140}
141
142func countText(n int64, one, many string) string {
143	if n == 1 {
144		return "1 " + one
145	}
146	return strconv.FormatInt(n, 10) + " " + many
147}
148
149// escapeText keeps question and rules text literal in Markdown, tables included.
150func escapeText(s string) string {
151	var out strings.Builder
152	for _, c := range s {
153		switch c {
154		case '\\', '`', '*', '_', '[', ']', '<', '>', '|', '#', '~', '!':
155			out.WriteByte('\\')
156		}
157		out.WriteRune(c)
158	}
159	return out.String()
160}
161
162// escapeCode keeps an untrusted path inside one inline code span.
163func escapeCode(s string) string {
164	s = strings.ReplaceAll(s, "`", "'")
165	if len(s) > 64 {
166		s = s[:64]
167	}
168	return s
169}