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.

escrow.gno

4.75 Kb · 198 lines
  1package escrow
  2
  3import (
  4	"chain"
  5	"errors"
  6	"strconv"
  7	"strings"
  8
  9	"gno.land/p/nym-moultest123/kit/ui/v0"
 10	"gno.land/p/nym-moultest123/md/v0"
 11	"gno.land/p/nt/avl/v0"
 12)
 13
 14// State constants for a deal's lifecycle.
 15const (
 16	StateOpen     = "Open"
 17	StateSettled  = "Settled"
 18	StateCanceled = "Canceled"
 19)
 20
 21// Deal is a 2-party escrow record. Accounting only: no funds move,
 22// this tracks agreement and mutual confirmation between two parties.
 23type Deal struct {
 24	ID           int
 25	Creator      address
 26	Counterparty address
 27	Terms        string
 28	State        string
 29	CreatorOK    bool
 30	CounterOK    bool
 31}
 32
 33var (
 34	deals  = avl.NewTree() // id (string) -> *Deal
 35	nextID int
 36)
 37
 38var (
 39	errNotFound   = errors.New("escrow: deal not found")
 40	errNotParty   = errors.New("escrow: caller is not a party to this deal")
 41	errNotOpen    = errors.New("escrow: deal is not open")
 42	errNotCreator = errors.New("escrow: only the creator can cancel")
 43	errBadAddr    = errors.New("escrow: invalid counterparty address")
 44	errSelfDeal   = errors.New("escrow: counterparty must differ from creator")
 45)
 46
 47// Create opens a new escrow deal between the caller (creator) and the
 48// given counterparty, governed by a free-form terms string. Returns the
 49// new deal id. Panics on invalid input (cross-realm abort).
 50func Create(cur realm, counterparty address, terms string) int {
 51	if !cur.IsCurrent() {
 52		panic("escrow: spoofed realm")
 53	}
 54	if !counterparty.IsValid() {
 55		panic(errBadAddr)
 56	}
 57	creator := cur.Previous().Address()
 58	if counterparty == creator {
 59		panic(errSelfDeal)
 60	}
 61
 62	id := nextID
 63	nextID++
 64
 65	d := &Deal{
 66		ID:           id,
 67		Creator:      creator,
 68		Counterparty: counterparty,
 69		Terms:        terms,
 70		State:        StateOpen,
 71	}
 72	deals.Set(strconv.Itoa(id), d)
 73
 74	chain.Emit("Created", "id", strconv.Itoa(id),
 75		"creator", creator.String(), "counterparty", counterparty.String())
 76	return id
 77}
 78
 79// Confirm records confirmation by whichever party is calling. Once both
 80// parties have confirmed, the deal transitions to Settled.
 81func Confirm(cur realm, id int) {
 82	if !cur.IsCurrent() {
 83		panic("escrow: spoofed realm")
 84	}
 85	d := mustGet(id)
 86	if d.State != StateOpen {
 87		panic(errNotOpen)
 88	}
 89	caller := cur.Previous().Address()
 90
 91	switch caller {
 92	case d.Creator:
 93		d.CreatorOK = true
 94	case d.Counterparty:
 95		d.CounterOK = true
 96	default:
 97		panic(errNotParty)
 98	}
 99
100	chain.Emit("Confirmed", "id", strconv.Itoa(id), "party", caller.String())
101
102	if d.CreatorOK && d.CounterOK {
103		d.State = StateSettled
104		chain.Emit("Settled", "id", strconv.Itoa(id))
105	}
106}
107
108// Cancel voids an open deal. Only the creator may cancel, and only
109// before the deal has settled.
110func Cancel(cur realm, id int) {
111	if !cur.IsCurrent() {
112		panic("escrow: spoofed realm")
113	}
114	d := mustGet(id)
115	if d.State != StateOpen {
116		panic(errNotOpen)
117	}
118	caller := cur.Previous().Address()
119	if caller != d.Creator {
120		panic(errNotCreator)
121	}
122	d.State = StateCanceled
123	chain.Emit("Canceled", "id", strconv.Itoa(id))
124}
125
126func mustGet(id int) *Deal {
127	v := deals.Get(strconv.Itoa(id))
128	if v == nil {
129		panic(errNotFound)
130	}
131	return v.(*Deal)
132}
133
134// Render lists all escrow deals with their parties, terms, and state.
135// When path is a numeric id, renders just that deal.
136func Render(path string) string {
137	path = strings.TrimSpace(strings.Trim(path, "/"))
138	if path != "" {
139		if id, err := strconv.Atoi(path); err == nil {
140			v := deals.Get(strconv.Itoa(id))
141			if v == nil {
142				return "# Escrow\n\nDeal " + md.InlineCode(path) + " not found.\n"
143			}
144			return renderOne(v.(*Deal))
145		}
146	}
147
148	var b strings.Builder
149	b.WriteString("# Escrow\n\n")
150	b.WriteString("2-party escrow deals (accounting only — no funds move).\n\n")
151
152	if deals.Size() == 0 {
153		b.WriteString("_No deals yet._\n")
154		return b.String()
155	}
156
157	b.WriteString("| ID | Creator | Counterparty | State | Confirmations | Terms |\n")
158	b.WriteString("|----|---------|--------------|-------|---------------|-------|\n")
159	deals.Iterate("", "", func(_ string, v interface{}) bool {
160		d := v.(*Deal)
161		b.WriteString("| " + strconv.Itoa(d.ID) +
162			" | " + ui.Addr(d.Creator) +
163			" | " + ui.Addr(d.Counterparty) +
164			" | " + d.State +
165			" | " + confs(d) +
166			" | " + ui.Cell(d.Terms) + " |\n")
167		return false
168	})
169	return b.String()
170}
171
172func renderOne(d *Deal) string {
173	var b strings.Builder
174	b.WriteString("# Escrow Deal #" + strconv.Itoa(d.ID) + "\n\n")
175	b.WriteString("- **State:** " + d.State + "\n")
176	b.WriteString("- **Creator:** " + d.Creator.String() + " (" + yn(d.CreatorOK) + ")\n")
177	b.WriteString("- **Counterparty:** " + d.Counterparty.String() + " (" + yn(d.CounterOK) + ")\n")
178	b.WriteString("- **Terms:** " + ui.Inline(d.Terms) + "\n")
179	return b.String()
180}
181
182func confs(d *Deal) string {
183	n := 0
184	if d.CreatorOK {
185		n++
186	}
187	if d.CounterOK {
188		n++
189	}
190	return strconv.Itoa(n) + "/2"
191}
192
193func yn(b bool) string {
194	if b {
195		return "confirmed"
196	}
197	return "pending"
198}