1package escrow23import (4 "chain"5 "chain/runtime/unsafe"6 "errors"7 "strconv"8 "strings"910 "gno.land/p/nt/avl/v0"11)1213// State constants for a deal's lifecycle.14const (15 StateOpen = "Open"16 StateSettled = "Settled"17 StateCanceled = "Canceled"18)1920// 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 int24 Creator address25 Counterparty address26 Terms string27 State string28 CreatorOK bool29 CounterOK bool30}3132var (33 deals = avl.NewTree() // id (string) -> *Deal34 nextID int35)3637var (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)4546// Create opens a new escrow deal between the caller (creator) and the47// given counterparty, governed by a free-form terms string. Returns the48// 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 }5758 id := nextID59 nextID++6061 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)6970 chain.Emit("Created", "id", strconv.Itoa(id),71 "creator", creator.String(), "counterparty", counterparty.String())72 return id73}7475// Confirm records confirmation by whichever party is calling. Once both76// 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()8384 switch caller {85 case d.Creator:86 d.CreatorOK = true87 case d.Counterparty:88 d.CounterOK = true89 default:90 panic(errNotParty)91 }9293 chain.Emit("Confirmed", "id", strconv.Itoa(id), "party", caller.String())9495 if d.CreatorOK && d.CounterOK {96 d.State = StateSettled97 chain.Emit("Settled", "id", strconv.Itoa(id))98 }99}100101// Cancel voids an open deal. Only the creator may cancel, and only102// 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 = StateCanceled113 chain.Emit("Canceled", "id", strconv.Itoa(id))114}115116func mustGet(id int) *Deal {117 v := deals.Get(strconv.Itoa(id))118 if v == nil {119 panic(errNotFound)120 }121 return v.(*Deal)122}123124// 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 }137138 var b strings.Builder139 b.WriteString("# Escrow\n\n")140 b.WriteString("2-party escrow deals (accounting only — no funds move).\n\n")141142 if deals.Size() == 0 {143 b.WriteString("_No deals yet._\n")144 return b.String()145 }146147 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 false158 })159 return b.String()160}161162func renderOne(d *Deal) string {163 var b strings.Builder164 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}171172func confs(d *Deal) string {173 n := 0174 if d.CreatorOK {175 n++176 }177 if d.CounterOK {178 n++179 }180 return strconv.Itoa(n) + "/2"181}182183func yn(b bool) string {184 if b {185 return "confirmed"186 }187 return "pending"188}189190func short(a address) string {191 s := a.String()192 if len(s) <= 12 {193 return s194 }195 return s[:8] + "…" + s[len(s)-4:]196}197198func escapePipe(s string) string {199 return strings.ReplaceAll(s, "|", "\\|")200}201Cancel(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.