Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

v0 source pure

v0 - Unaudited: This is an initial version that has not yet been formally audited. A fully audited version will be pu...

Readme 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 unified Send/History/Balances API. Also provides a Render router for gnoweb pages.
  • Banker: handler for a single asset type. Built-ins are CoinsBanker (native chain coins) and GRC20Banker (any number of GRC20 tokens, resolved through a user-supplied TokenListerFunc).
  • Payment: opaque value produced by a banker-specific helper (NewCoinsPayment, NewGRC20Payment). Each Payment is bound to a BankerID(), 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 *WithOwner value) from an external realm. A hostile Balances/Address can report data tied to an attacker address. New calls IsCanonicalBanker on each banker and rejects foreign types with ErrNonCanonicalBankerImpl.
  • IsCanonicalBanker checks 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 a Banker from a caller must call it before invoking the banker's methods.
  • Owner must match the acting realm. Send asserts rlm.IsCurrent() (else ErrSpoofedRealm) and the banker rejects a caller that is not its owner (ErrCurrentRealmIsNotOwner). Set the owner to the realm that will actually send.

Overview

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.

Constants 1

Variables 4

Functions 6

func IsCanonicalBanker

1func IsCanonicalBanker(b Banker) bool
source

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

1func NewCoinsBankerWithOwner(owner address, banker_ banker.Banker) (*CoinsBanker, error)
source

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

1func NewGRC20BankerWithOwner(owner address, lister TokenListerFunc) (*GRC20Banker, error)
source

NewGRC20BankerWithOwner creates a new GRC20Banker with the given address.

func NewCoinsPayment

1func NewCoinsPayment(coins chain.Coins, toAddress address) Payment
source

NewCoinsPayment creates a new coinsPayment.

func NewGRC20Payment

1func NewGRC20Payment(tokenKey string, amount int64, toAddress address) Payment
source

NewGRC20Payment creates a new grc20Payment.

func New

1func New(bankers []Banker, pkgPath string) (*Treasury, error)
source

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.

Types 7

type Balance

struct
1type Balance struct {
2	Denom  string // The denomination of the asset
3	Amount int64  // The amount of the asset
4}
source

Balance represents the balance of an asset held by a Banker.

type Banker

interface
1type Banker interface {
2	ID() string                     // Get the ID of the banker.
3	Send(int, realm, Payment) error // Send a payment to a recipient.
4	Balances() []Balance            // Get the balances of the banker.
5	Address() string                // Get the address of the banker to receive payments.
6}
source

Banker 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

struct
1type CoinsBanker struct {
2	owner  address       // The address of this coins banker owner.
3	banker banker.Banker // The underlying std banker, must be a BankerTypeRealmSend.
4}
source

CoinsBanker is a Banker that sends banker.Coins.

Methods on CoinsBanker

func Address

method on CoinsBanker
1func (cb *CoinsBanker) Address() string
source

Address implements Banker.

func Balances

method on CoinsBanker
1func (cb *CoinsBanker) Balances() []Balance
source

Balances implements Banker.

func ID

method on CoinsBanker
1func (CoinsBanker) ID() string
source

ID implements Banker.

func Send

method on CoinsBanker
1func (cb *CoinsBanker) Send(_ int, rlm realm, p Payment) error
source

Send 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

struct
1type GRC20Banker struct {
2	owner  address         // The address of this GRC20 banker owner.
3	lister TokenListerFunc // Allows to list tokens from methods that require it.
4}
source

GRC20Banker is a Banker that sends GRC20 tokens listed using a getter set during initialization.

Methods on GRC20Banker

func Address

method on GRC20Banker
1func (gb *GRC20Banker) Address() string
source

Address implements Banker.

func Balances

method on GRC20Banker
1func (gb *GRC20Banker) Balances() []Balance
source

Balances implements Banker.

func ID

method on GRC20Banker
1func (GRC20Banker) ID() string
source

ID implements Banker.

func Send

method on GRC20Banker
1func (gb *GRC20Banker) Send(_ int, rlm realm, p Payment) error
source

Send 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

interface
1type Payment interface {
2	BankerID() string // Get the ID of the banker that can process this payment.
3	String() string   // Get a string representation of the payment.
4}
source

Payment is an interface that allows getting details about a payment.

type TokenListerFunc

func
1type TokenListerFunc func() map[string]*grc20.Token
source

TokenListerFunc is a function type that returns a map of GRC20 tokens.

type Treasury

struct
1type Treasury struct {
2	bankers   *bptree.BPTree // string -> *bankerRecord
3	router    *mux.Router
4	realmPath string // owning realm's PkgPath, captured at New() (used for render links)
5}
source

Treasury 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 Treasury
1func (t *Treasury) Address(bankerID string) (string, error)
source

Address returns the address of the banker with the given ID.

func Balances

method on Treasury
1func (t *Treasury) Balances(bankerID string) ([]Balance, error)
source

Balances returns the balances of the banker with the given ID.

func HasBanker

method on Treasury
1func (t *Treasury) HasBanker(bankerID string) bool
source

HasBanker checks if a banker with the given ID is registered.

func History

method on Treasury
1func (t *Treasury) History(
2	bankerID string,
3	pageNumber int,
4	pageSize int,
5) ([]Payment, error)
source

History 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 Treasury
1func (t *Treasury) ListBankerIDs() []string
source

ListBankerIDs returns a list of all registered banker IDs.

func Render

method on Treasury
1func (t *Treasury) Render(path string) string
source

Render renders content based on the given path.

func RenderBanker

method on Treasury
1func (t *Treasury) RenderBanker(bankerID string, path string) string
source

RenderBanker renders the details of a specific banker.

func RenderBankerHistory

method on Treasury
1func (t *Treasury) RenderBankerHistory(bankerID string, path string) string
source

RenderBankerHistory renders the payment history of a specific banker.

func RenderLanding

method on Treasury
1func (t *Treasury) RenderLanding(path string) string
source

RenderLanding renders the landing page of the treasury.

func Send

method on Treasury
1func (t *Treasury) Send(_ int, rlm realm, p Payment) error
source

Send sends a payment using the corresponding banker. rlm is threaded to the banker's Send for IsCurrent + owner validation.

Imports 15

Source Files 8