v0 source pure
v0 - Unaudited: This is an initial version that has not yet been formally audited. A fully audited version will be pu...
View source
v0 - Unaudited This is an initial version of this package that has not yet been formally audited. A fully audited version will be published as a subsequent release. Use in production at your own risk.
treasury - Coin and GRC20 treasury management
Treasury management for coin and GRC20 token transfers in Gno realms. A Treasury holds a set of Bankers, each responsible for sending a specific asset type, and records the payment history per banker.
1. Concepts
- Treasury: container that registers one or more
Bankers and exposes a unifiedSend/History/BalancesAPI. Also provides aRenderrouter for gnoweb pages. - Banker: handler for a single asset type. Built-ins are
CoinsBanker(native chain coins) andGRC20Banker(any number of GRC20 tokens, resolved through a user-suppliedTokenListerFunc). - Payment: opaque value produced by a banker-specific helper (
NewCoinsPayment,NewGRC20Payment). EachPaymentis bound to aBankerID(), which is how the treasury routes it.
2. Usage
1import (
2 "chain"
3 "chain/banker"
4 "chain/runtime"
5
6 "gno.land/p/demo/tokens/grc20"
7 "gno.land/p/nt/treasury/v0"
8)
9
10var (
11 tokens = map[string]*grc20.Token{}
12 tr *treasury.Treasury
13)
14
15func init() {
16 owner := runtime.CurrentRealm().Address() // this realm holds and sends the funds
17
18 // Coins banker owned by this realm.
19 coinsBanker, err := treasury.NewCoinsBankerWithOwner(
20 owner,
21 banker.NewBanker(banker.BankerTypeRealmSend),
22 )
23 if err != nil {
24 panic(err)
25 }
26
27 // GRC20 banker that resolves tokens through a lister.
28 grc20Banker, err := treasury.NewGRC20BankerWithOwner(owner, func() map[string]*grc20.Token {
29 return tokens
30 })
31 if err != nil {
32 panic(err)
33 }
34
35 tr, err = treasury.New(
36 []treasury.Banker{coinsBanker, grc20Banker},
37 runtime.CurrentRealm().PkgPath(),
38 )
39 if err != nil {
40 panic(err)
41 }
42}
43
44// SendUgnot transfers ugnot from the realm to `to`.
45func SendUgnot(cur realm, to address, amount int64) {
46 p := treasury.NewCoinsPayment(chain.Coins{{Denom: "ugnot", Amount: amount}}, to)
47 if err := tr.Send(0, cur, p); err != nil {
48 panic(err)
49 }
50}
51
52// Render exposes the treasury under the realm's render path.
53func Render(path string) string {
54 return tr.Render(path)
55}
3. API
3.1 Treasury
1// Builds a treasury with the provided bankers (at least one required, IDs must be unique).
2// pkgPath is the realm's package path, used as the base for the Render router.
3func New(bankers []Banker, pkgPath string) (*Treasury, error)
4
5func (t *Treasury) Send(_ int, rlm realm, p Payment) error
6func (t *Treasury) History(bankerID string, pageNumber, pageSize int) ([]Payment, error)
7func (t *Treasury) Balances(bankerID string) ([]Balance, error)
8func (t *Treasury) Address(bankerID string) (string, error)
9func (t *Treasury) HasBanker(bankerID string) bool
10func (t *Treasury) ListBankerIDs() []string
11
12// Render entry points (a mux router is initialized by `New`).
13func (t *Treasury) Render(path string) string
14func (t *Treasury) RenderLanding(path string) string
15func (t *Treasury) RenderBanker(bankerID, path string) string
16func (t *Treasury) RenderBankerHistory(bankerID, path string) string
Render routes:
""— landing page, lists each banker.{banker}— banker details (address, balances, last N payments).{banker}/history— paginated payment history.
The history_size query parameter on {banker} controls the preview size (default 5, 0 hides the preview).
3.2 Banker and Payment interfaces
1type Banker interface {
2 ID() string // unique banker ID used for routing
3 Send(int, realm, Payment) error // thread the caller's cur; pass 0 as the first arg
4 Balances() []Balance
5 Address() string // address used to receive payments
6}
7
8type Payment interface {
9 BankerID() string // routes the payment to a banker
10 String() string
11}
12
13type Balance struct {
14 Denom string
15 Amount int64
16}
17
18// Capability guard: any entry point that accepts a Banker from an external
19// caller MUST verify it before invoking its methods. Validates dynamic type
20// only (embedding-based wrappers are rejected), not captured state.
21func IsCanonicalBanker(b Banker) bool
3.3 CoinsBanker
Banker for native chain coins. Owns an address and an inner chain/banker.Banker (must be the canonical one returned by banker.NewBanker — fake implementations are rejected).
1func NewCoinsBankerWithOwner(owner address, banker_ banker.Banker) (*CoinsBanker, error)
2
3func NewCoinsPayment(coins chain.Coins, toAddress address) Payment
CoinsBanker.ID() returns "Coins".
3.4 GRC20Banker
Banker for GRC20 tokens. Tokens are resolved at send time through a TokenListerFunc, so the set of supported tokens can change without rebuilding the banker.
1type TokenListerFunc func() map[string]*grc20.Token
2
3func NewGRC20BankerWithOwner(owner address, lister TokenListerFunc) (*GRC20Banker, error)
4
5func NewGRC20Payment(tokenKey string, amount int64, toAddress address) Payment
GRC20Banker.ID() returns "GRC20". tokenKey must be a key in the map returned by the lister.
3.5 Errors
1ErrNoBankerProvided // New called with empty bankers slice
2ErrDuplicateBanker // two bankers share the same ID
3ErrBankerNotFound // Send/History/... called with an unknown banker ID
4ErrSendPaymentFailed // wraps the underlying banker error
5ErrCurrentRealmIsNotOwner // banker called from a realm other than its owner
6ErrNoOwnerProvided
7ErrInvalidPaymentType // payment routed to the wrong banker type
8ErrNonCanonicalBanker // CoinsBanker built from a non-canonical std banker
9ErrNonCanonicalBankerImpl // New given a Banker of a non-canonical type
10ErrSpoofedRealm // Send called with a non-current rlm
11ErrNoListerProvided
12ErrGRC20TokenNotFound
4. Security
The Banker capability model rests on three rules:
- Construct your own bankers. Never accept a pre-built
Banker(including a*WithOwnervalue) from an external realm. A hostileBalances/Addresscan report data tied to an attacker address.NewcallsIsCanonicalBankeron each banker and rejects foreign types withErrNonCanonicalBankerImpl. IsCanonicalBankerchecks dynamic TYPE only, not captured state. Embedding-based wrappers (type Evil struct { *CoinsBanker }) are rejected because type assertions are nominal. Any public entry point that takes aBankerfrom a caller must call it before invoking the banker's methods.- Owner must match the acting realm.
Sendassertsrlm.IsCurrent()(elseErrSpoofedRealm) and the banker rejects a caller that is not its owner (ErrCurrentRealmIsNotOwner). Set the owner to the realm that will actually send.
v0 - Unaudited: This is an initial version that has not yet been formally audited. A fully audited version will be published as a subsequent release. Use in production at your own risk.
Package treasury provides treasury management for handling coin and GRC20 token transfers in Gno realms.
1
4
var ErrNoListerProvided, ErrGRC20TokenNotFound
var ErrNoBankerProvided, ErrDuplicateBanker, ErrBankerNotFound, ErrSendPaymentFailed, ErrNonCanonicalBankerImpl
1var (
2 ErrNoBankerProvided = errors.New("no banker provided")
3 ErrDuplicateBanker = errors.New("duplicate banker")
4 ErrBankerNotFound = errors.New("banker not found")
5 ErrSendPaymentFailed = errors.New("failed to send payment")
6 ErrNonCanonicalBankerImpl = errors.New("non-canonical Banker impl: only *CoinsBanker / *GRC20Banker accepted")
7)var ErrCurrentRealmIsNotOwner, ErrNoOwnerProvided, ErrInvalidPaymentType, ErrSpoofedRealm
Common Banker errors.
var ErrNonCanonicalBanker
6
func IsCanonicalBanker
IsCanonicalBanker reports whether b is one of treasury's canonical concrete Banker impls. Use this at any public entry point in /p/ or /r/ that accepts a Banker from an external caller before invoking its methods.
Foreign types — including embedding-based wrappers like `type Evil struct { *CoinsBanker }` — are rejected because type assertions are nominal: *Evil is not *CoinsBanker, regardless of method promotion.
To add a new canonical type: extend the switch below AND add a regression test (under filetests/ in this package) that an embedded-impl bypass is rejected.
Mirrors the precedent of chain/banker.IsCanonical and p/jaekwon/allowancesender's canonical-impl check.
func NewCoinsBankerWithOwner
NewCoinsBankerWithOwner creates a new CoinsBanker with the given address.
banker_ must be the canonical Banker produced by banker.NewBanker; hand-rolled Banker implementations (no-op fakes, decorators) are rejected via banker.IsCanonical. Without this check, a callee receiving a *CoinsBanker constructed from a fake banker would not be able to tell that Send is a no-op (no real coins move). The pkgAddr-vs-owner mismatch is still surfaced lazily by the inner banker's own SendCoins check.
func NewGRC20BankerWithOwner
NewGRC20BankerWithOwner creates a new GRC20Banker with the given address.
func NewCoinsPayment
NewCoinsPayment creates a new coinsPayment.
func NewGRC20Payment
NewGRC20Payment creates a new grc20Payment.
func New
New creates a new Treasury instance with the given bankers.
pkgPath should be the package path of the owning realm. It is captured on the Treasury and used to build the "See full history" link in RenderBanker. Pass `cur.PkgPath()` from the owning realm's init (or another live-cur context). Passing "" disables the link.
The path is stored as plain data — no IsCurrent guard inside this /p/ constructor. The caller is the trust boundary; it must derive the value from a real `cur realm` rather than accept it from untrusted input. The captured path is consumed only for render output (Class-2 designation-forgery shape, accepted as display-only — see docs/resources/gno-security.md).
Each banker must be one of treasury's canonical concrete impls (*CoinsBanker, *GRC20Banker) per IsCanonicalBanker. Foreign-realm impls (including embedded wrappers around canonical types) are rejected: a malicious Send impl would receive a capability token via its rlm parameter when treasury.Send dispatches into it.
The allowlist validates type only, not captured state. Treasury operators must construct their own bankers; never accept a pre-built *Banker value from an external realm.
7
type Balance
structBalance represents the balance of an asset held by a Banker.
type Banker
interfaceBanker is an interface that allows for banking operations.
SECURITY: Send takes (int, realm, Payment), so handing a Banker value to untrusted code yields a capability token to whatever Send impl that code dispatches into. The set of canonical impls is closed (*CoinsBanker, *GRC20Banker); any public function that accepts a Banker as a parameter from external callers MUST verify it via IsCanonicalBanker and reject otherwise. treasury.New enforces this for its own intake; future Banker-accepting APIs must do the same. An unexported-marker "seal" does NOT defend against this — see p/test/seal/filetests/z_seal_iface_embedding_filetest.gno.
Note that IsCanonicalBanker validates dynamic TYPE only, not captured STATE: a canonical *CoinsBanker constructed via NewCoinsBankerWithOwner with a hostile owner argument passes the allowlist but its read methods (Balances, Address) report data tied to that hostile address. Treasury operators must construct their own bankers and NEVER accept pre-built *Banker values from external realms.
type CoinsBanker
structCoinsBanker is a Banker that sends banker.Coins.
Methods on CoinsBanker
func Address
method on CoinsBankerAddress implements Banker.
func Balances
method on CoinsBankerBalances implements Banker.
func ID
method on CoinsBankerID implements Banker.
func Send
method on CoinsBankerSend implements Banker.
rlm must be the caller's own captured cur (i.e. the cur of the immediate crossing-function caller). Sending with rlm = cur.Previous() or any other realm value is rejected: rlm.IsCurrent() asserts pointer identity against the topmost crossing frame. Combined with the rlm.Address() == cb.owner check, this restricts Send to the owning realm acting in its own frame.
type GRC20Banker
structGRC20Banker is a Banker that sends GRC20 tokens listed using a getter set during initialization.
Methods on GRC20Banker
func Address
method on GRC20BankerAddress implements Banker.
func Balances
method on GRC20BankerBalances implements Banker.
func ID
method on GRC20BankerID implements Banker.
func Send
method on GRC20BankerSend implements Banker.
rlm must be the caller's own captured cur (i.e. the cur of the immediate crossing-function caller). Sending with rlm = cur.Previous() or any other realm value is rejected: rlm.IsCurrent() asserts pointer identity against the topmost crossing frame. Combined with the rlm.Address() == gb.owner check, this restricts Send to the owning realm acting in its own frame.
type Payment
interfacePayment is an interface that allows getting details about a payment.
type TokenListerFunc
funcTokenListerFunc is a function type that returns a map of GRC20 tokens.
type Treasury
structTreasury is the main structure that holds all bankers and their payment history. It also provides a router for rendering the treasury pages.
Methods on Treasury
func Address
method on TreasuryAddress returns the address of the banker with the given ID.
func Balances
method on TreasuryBalances returns the balances of the banker with the given ID.
func HasBanker
method on TreasuryHasBanker checks if a banker with the given ID is registered.
func History
method on TreasuryHistory returns the payment history sent by the banker with the given ID. Payments are paginated, with the most recent payments first.
func ListBankerIDs
method on TreasuryListBankerIDs returns a list of all registered banker IDs.
func Render
method on TreasuryRender renders content based on the given path.
func RenderBanker
method on TreasuryRenderBanker renders the details of a specific banker.
func RenderBankerHistory
method on TreasuryRenderBankerHistory renders the payment history of a specific banker.
func RenderLanding
method on TreasuryRenderLanding renders the landing page of the treasury.
func Send
method on TreasurySend sends a payment using the corresponding banker. rlm is threaded to the banker's Send for IsCurrent + owner validation.
15
- chain stdlib
- chain/banker stdlib
- errors stdlib
- gno.land/p/aeddi/panictoerr package
- gno.land/p/demo/tokens/grc20 package
- gno.land/p/moul/md package
- gno.land/p/moul/mdtable package
- gno.land/p/nt/bptree/v0 package
- gno.land/p/nt/bptree/v0/list package
- gno.land/p/nt/bptree/v0/pager package
- gno.land/p/nt/mux/v0 package
- gno.land/p/nt/ufmt/v0 package
- net/url stdlib
- strconv stdlib
- strings stdlib