1package escrow23import (4 "chain"5 "chain/runtime/unsafe"6 "errors"7 "strconv"8 "strings"910 "gno.land/p/moul/kit/ui/v0"11 "gno.land/p/nt/avl/v0"12)1314// State constants for a deal's lifecycle.15const (16 StateOpen = "Open"17 StateSettled = "Settled"18 StateCanceled = "Canceled"19)2021// 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 int25 Creator address26 Counterparty address27 Terms string28 State string29 CreatorOK bool30 CounterOK bool31}3233var (34 deals = avl.NewTree() // id (string) -> *Deal35 nextID int36)3738var (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)4647// Create opens a new escrow deal between the caller (creator) and the48// given counterparty, governed by a free-form terms string. Returns the49// new deal id. Panics on invalid input (cross-realm abort).50func Create(cur realm, counterparty address, terms string) int {51 if !counterparty.IsValid() {52 panic(errBadAddr)53 }54 creator := unsafe.PreviousRealm().Address()55 if counterparty == creator {56 panic(errSelfDeal)57 }5859 id := nextID60 nextID++6162 d := &Deal{63 ID: id,64 Creator: creator,65 Counterparty: counterparty,66 Terms: terms,67 State: StateOpen,68 }69 deals.Set(strconv.Itoa(id), d)7071 chain.Emit("Created", "id", strconv.Itoa(id),72 "creator", creator.String(), "counterparty", counterparty.String())73 return id74}7576// Confirm records confirmation by whichever party is calling. Once both77// parties have confirmed, the deal transitions to Settled.78func Confirm(cur realm, id int) {79 d := mustGet(id)80 if d.State != StateOpen {81 panic(errNotOpen)82 }83 caller := unsafe.PreviousRealm().Address()8485 switch caller {86 case d.Creator:87 d.CreatorOK = true88 case d.Counterparty:89 d.CounterOK = true90 default:91 panic(errNotParty)92 }9394 chain.Emit("Confirmed", "id", strconv.Itoa(id), "party", caller.String())9596 if d.CreatorOK && d.CounterOK {97 d.State = StateSettled98 chain.Emit("Settled", "id", strconv.Itoa(id))99 }100}101102// Cancel voids an open deal. Only the creator may cancel, and only103// before the deal has settled.104func Cancel(cur realm, id int) {105 d := mustGet(id)106 if d.State != StateOpen {107 panic(errNotOpen)108 }109 caller := unsafe.PreviousRealm().Address()110 if caller != d.Creator {111 panic(errNotCreator)112 }113 d.State = StateCanceled114 chain.Emit("Canceled", "id", strconv.Itoa(id))115}116117func mustGet(id int) *Deal {118 v := deals.Get(strconv.Itoa(id))119 if v == nil {120 panic(errNotFound)121 }122 return v.(*Deal)123}124125// Render lists all escrow deals with their parties, terms, and state.126// When path is a numeric id, renders just that deal.127func Render(path string) string {128 path = strings.TrimSpace(strings.Trim(path, "/"))129 if path != "" {130 if id, err := strconv.Atoi(path); err == nil {131 v := deals.Get(strconv.Itoa(id))132 if v == nil {133 return "# Escrow\n\nDeal `" + path + "` not found.\n"134 }135 return renderOne(v.(*Deal))136 }137 }138139 var b strings.Builder140 b.WriteString("# Escrow\n\n")141 b.WriteString("2-party escrow deals (accounting only — no funds move).\n\n")142143 if deals.Size() == 0 {144 b.WriteString("_No deals yet._\n")145 return b.String()146 }147148 b.WriteString("| ID | Creator | Counterparty | State | Confirmations | Terms |\n")149 b.WriteString("|----|---------|--------------|-------|---------------|-------|\n")150 deals.Iterate("", "", func(_ string, v interface{}) bool {151 d := v.(*Deal)152 b.WriteString("| " + strconv.Itoa(d.ID) +153 " | " + ui.Addr(d.Creator) +154 " | " + ui.Addr(d.Counterparty) +155 " | " + d.State +156 " | " + confs(d) +157 " | " + ui.Cell(d.Terms) + " |\n")158 return false159 })160 return b.String()161}162163func renderOne(d *Deal) string {164 var b strings.Builder165 b.WriteString("# Escrow Deal #" + strconv.Itoa(d.ID) + "\n\n")166 b.WriteString("- **State:** " + d.State + "\n")167 b.WriteString("- **Creator:** " + d.Creator.String() + " (" + yn(d.CreatorOK) + ")\n")168 b.WriteString("- **Counterparty:** " + d.Counterparty.String() + " (" + yn(d.CounterOK) + ")\n")169 b.WriteString("- **Terms:** " + d.Terms + "\n")170 return b.String()171}172173func confs(d *Deal) string {174 n := 0175 if d.CreatorOK {176 n++177 }178 if d.CounterOK {179 n++180 }181 return strconv.Itoa(n) + "/2"182}183184func yn(b bool) string {185 if b {186 return "confirmed"187 }188 return "pending"189}190Cancel(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 int)
Confirm(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 int)
Create(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}, counterparty string, terms string) int
Render(path string) string
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
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.