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

README.md

6.76 Kb · 182 lines

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.