# `gno.land/p/moul/x/grc20wrap/v0` **Build a new GRC20 on top of one you do not control.** `Vault` escrows an existing token and issues its own against it; `Basket` does the same over several at once. A `Policy` decides the exchange rate in both directions and whether the wrapped token may move, which is the whole personality of a wrapper. ```go import ( "gno.land/p/moul/x/grc20wrap/v0" "gno.land/p/nt/grc20/v0" ) // In your realm, over any *grc20.Token you can reach (an import, or a // gno.land/r/nt/grc20reg/v0 lookup): v := grc20wrap.NewVault(under, grc20wrap.Pool{}, "Pooled FOO", "pFOO", 4, 0, cur) // The holder approves YOUR realm's address on the underlying first, through // the underlying realm's own entry point. Then: shares, err := v.Wrap(0, cur, holder, 1000) // escrow 1000, mint shares out, err := v.Unwrap(0, cur, holder, shares) // burn shares, release escrow err = v.Donate(0, cur, patron, 500) // no mint: every share is worth more ``` ## The five policies | policy | wrapped token behaves like | |---|---| | `OneToOne` | a 1:1 custody receipt | | `Ratio{Num, Den}` | the same token re-denominated (`{1000, 1}` adds three decimals) | | `Soulbound{Base}` | a badge: wrap and unwrap freely, never transferable | | `Pool` | a share of the escrow; `Donate` pays every holder at once | | `Fee{Base, WrapBPS, UnwrapBPS}` | a haircut left behind, which over `Pool` pays whoever stays | They compose: `Fee{Base: Pool{}, UnwrapBPS: 100}` is a pool with a 1% exit fee. Writing your own means three methods and a name; embed `OneToOne` and override only what differs. ## Basket: one token backed by two ```go b := grc20wrap.NewBasket( []*grc20.Token{red, blue}, []int64{1, 2}, "Purple", "PURPLE", 4, 0, cur, ) err := b.Fuse(0, cur, holder, 100) // escrow 100 RED + 200 BLUE, mint 100 PURPLE err = b.Defuse(0, cur, holder, 40) // burn 40, hand back 40 RED + 80 BLUE ``` Proportions are fixed, and the meta-token is only ever minted against the real thing, so it cannot drift from its backing or be arbitraged. It has no price oracle and no rebalancing, because both would mean valuing the legs. ## Three things worth knowing before using it **Custody is an allowance, never a privilege.** Escrow sits at the host realm's address and moves through `grc20.RealmTeller`, which grc20 binds eagerly to that address. A wrapper can only take what a holder approved for it, and can only ever spend its own realm's balance. **Errors are returned before anything moves, panics after.** Wrapping is two ledger writes on two different tokens and gno has no rollback short of a panic, so each method validates first and returns an error while the world is still untouched, then panics if a write fails past the point of no return. Aborting the transaction is the only atomicity available. **A policy veto binds users, not realms.** `Vault.Move` is the only path a signing account has, because `MsgCall` cannot build the `realm` argument grc20's tellers require. A realm holding the wrapped token can always call `grc20.RealmTeller` and move its own balance. `Soulbound` means "no user can pass it on", not "it can never move". **Live demo:** [`r/moul/x/grc20wrapdemo`](https://github.com/moul/gno-contracts/tree/main/r/moul/x/grc20wrapdemo/v0) · render it at [`/r/moul/x/grc20wrapdemo/v0`](https://gno.land/r/moul/x/grc20wrapdemo/v0). --- Part of **[moul/gno-contracts](https://github.com/moul/gno-contracts)** — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage. **Dependency graph:** ![gno.land/p/moul/x/grc20wrap/v0 dependency graph](https://raw.githubusercontent.com/moul/gno-contracts/main/_assets/gno.land/p/moul/x/grc20wrap/v0/deps.png) > 🧪 **Highly experimental — potentially vibe-coded.** Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: [DISCLAIMER](https://github.com/moul/gno-contracts/blob/main/DISCLAIMER.md).