← Back to Realms

Realm

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

Overview

Realm Path
gno.land/r/moul/x/daily/escrow/v0
Exported Functions
4
State Entries
12
Source Files
2
Total Package Entries
22

Exported Functions

4 exported functions

State

12 state entries

Source Code

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

Raw Package Data

Raw JSON data