1// Package coinio is the chain-facing coin plumbing that every2// value-holding realm on gno.land repeats: verified payment receipt,3// disciplined payout, and reserve-protected surplus sweeping. It was4// extracted from two audited, pearl-1-validated realms (vault and5// bounties) whose implementations of these mechanics were6// line-identical.7//8// The package is PURE and STATELESS: it holds no balances, no roles,9// and no configuration; all state stays in the consuming realm (and10// its accounting package, e.g. feeledger). It emits no events —11// consumers emit their own. Every function either succeeds or panics,12// aborting the transaction: these are payment guards, and no failure13// here has a meaningful recovery path.14//15// CAPABILITY CONTRACT: pure packages cannot declare crossing16// functions (a first `realm` parameter), so every coin-moving function17// here uses the canonical secondary-parameter shape `(_ int, rlm18// realm, ...)` — the same pattern as chain treasury packages. Pass 019// and your crossing entrypoint's own live `cur` (forwarded through20// non-crossing calls keeps it current). Each function asserts21// rlm.IsCurrent() before acting, so a stale, stored, or Previous()22// realm value fails closed (the designation-forgery guard secondary23// realm parameters require). Coins can only move FROM rlm.Address() —24// the calling realm itself — so no consumer can spend another realm's25// funds through this package.26//27// AUTHORIZATION is the consumer's responsibility: coinio decides HOW28// coins move, never WHO may move them. Gate your entrypoints before29// calling in.30//31// ORDERING CONTRACT (the one invariant coinio cannot enforce): debit32// your own accounting BEFORE calling Payout or Sweep33// (checks-effects-interactions). A panic inside coinio aborts the34// whole transaction, reverting your debit with it — that is what makes35// the debit-first order safe.36package coinio3738import (39 "chain"40 "chain/banker"41 "chain/runtime/unsafe"42)4344// Receive verifies the canonical receipt-guaranteed payment shape and45// returns the payer and amount:46//47// - the caller of the consuming realm's entrypoint is an EOA via48// MsgCall (IsUserCall) — the only shape where the chain guarantees49// the -send envelope landed at the realm address before the body50// ran (realms and maketx-run are rejected);51// - the envelope is exactly one coin of the given denom;52// - the amount is positive.53//54// Receive is a READ of the transaction's send envelope, NOT a55// consumption: a stateless pure package cannot mark an envelope spent,56// so calling Receive N times in one transaction reports the same57// envelope N times. CALL IT AT MOST ONCE PER TRANSACTION and credit58// its result at most once — a consumer that credits per call mints59// unbacked liabilities from a single -send.60//61// Call it first (and once) in any payable crossing entrypoint:62//63// func Deposit(cur realm) {64// from, amount := coinio.Receive(0, cur, "ugnot")65// // credit `from` with `amount` in your accounting66// }67func Receive(_ int, rlm realm, denom string) (from address, amount int64) {68 if !rlm.IsCurrent() {69 panic("coinio: realm capability is not current")70 }71 if denom == "" {72 panic("coinio: empty denom")73 }74 if !rlm.Previous().IsUserCall() {75 panic("coinio: payment must be a direct EOA call with -send (realms and maketx-run are rejected)")76 }77 sent := unsafe.OriginSend()78 if len(sent) != 1 || sent[0].Denom != denom {79 panic("coinio: send exactly one coin type: " + denom)80 }81 if sent[0].Amount <= 0 {82 panic("coinio: amount must be positive")83 }84 return rlm.Previous().Address(), sent[0].Amount85}8687// Payout sends amount of denom from the calling realm's own address to88// `to`. DEBIT YOUR ACCOUNTING FIRST — a panic here (or anywhere later89// in the transaction) reverts the debit together with the send.90func Payout(_ int, rlm realm, to address, denom string, amount int64) {91 if !rlm.IsCurrent() {92 panic("coinio: realm capability is not current")93 }94 var zero address95 if to == zero {96 panic("coinio: empty payout address")97 }98 if denom == "" {99 panic("coinio: empty denom")100 }101 if amount <= 0 {102 panic("coinio: amount must be positive")103 }104 bk := banker.NewBanker(banker.BankerTypeRealmSend, rlm)105 bk.SendCoins(rlm.Address(), to, chain.Coins{chain.NewCoin(denom, amount)})106}107108// Sweep sends the surplus of a single denomination — everything the109// calling realm holds above `reserve` — to `to`, and returns the swept110// amount. Pass your total liabilities as the reserve for the denom111// your accounting tracks, and 0 for foreign denominations. Panics if112// there is no positive surplus, so reserved funds are untouchable by113// construction. One denomination per call keeps the operation gas-114// bounded regardless of how many junk denoms third parties force-send.115func Sweep(_ int, rlm realm, to address, denom string, reserve int64) int64 {116 if !rlm.IsCurrent() {117 panic("coinio: realm capability is not current")118 }119 var zero address120 if to == zero {121 panic("coinio: empty sweep address")122 }123 if denom == "" {124 panic("coinio: empty denom")125 }126 if reserve < 0 {127 panic("coinio: negative reserve")128 }129 bk := banker.NewBanker(banker.BankerTypeRealmSend, rlm)130 surplus := bk.GetCoin(rlm.Address(), denom) - reserve131 if surplus <= 0 {132 panic("coinio: no surplus to sweep for " + denom)133 }134 bk.SendCoins(rlm.Address(), to, chain.Coins{chain.NewCoin(denom, surplus)})135 return surplus136}137138// HeldAt returns the amount of denom held at addr. Read-only; usable139// from views and Render without a realm capability.140func HeldAt(addr address, denom string) int64 {141 return banker.NewReadonlyBanker().GetCoin(addr, denom)142}143Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.