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

/p/moul/x/pair/v0

Directory · 4 Files
README.md Open

gno.land/p/moul/x/pair/v0

One constant-product AMM pair (x*y=k, 30 bps to the liquidity providers), as a pure package, so that the realm holding a pair can be twenty lines of glue.

This is the shared half of the instance-per-realm pattern: gno's answer to the Ethereum factory shape, where an ecosystem is a frontend over thousands of tiny identical contracts, one per token couple.

artifact what it is
p/moul/x/pair/v0 this package, the whole behaviour, deployed once
an instance realm ~20 lines: two constants, an init that registers, one-line re-exports. See r/moul/x/pairs/aaabbb/v0
r/moul/x/pairreg/v0 the registry every instance announces itself to, and the unified frontend over all of them
tools/pairgen the factory, which runs on your machine because gno has no on-chain deploy

What gno changes about the Ethereum pattern

  • No on-chain factory. vm/add_package is permanently denied to realm code, so an instance is an addpkg transaction signed by a human, priced in gas and a storage deposit. tools/pairgen fills the template and prints the command.
  • Spawning is still permissionless. Any address may deploy under gno.land/r/<its own g1 address>/** with no registered username, so anyone can run their own instance and land in the registry.
  • No clone trick needed. Ethereum copies bytecode or delegatecalls through an EIP-1167 proxy; an import stores this package once for every instance.
  • The registry sees live state. Instances register a *pair.Pair pointer, so one vm/qrender renders every instance's real reserves, with no indexer.
  • Nothing attests the code. No realm can read another package's source, so an instance's code can only be verified off chain, by diffing it against freshly generated output. Unforgeable identity, no code attestation: the mirror image of CREATE2.

Using it from an instance

 1var p *pair.Pair
 2
 3func init(cur realm) {
 4	p = pair.New(keyA, keyB, grc20reg.MustGet(keyA), grc20reg.MustGet(keyB))
 5	pairreg.Register(cross(cur), p)
 6}
 7
 8func Swap(cur realm, keyIn string, amountIn, minOut int64) int64 {
 9	return p.Swap(0, cur, keyIn, amountIn, minOut)
10}

Every state-changing method is shaped (_ int, rlm realm, ...). That is forced, not stylistic: a pure package cannot declare a crossing function (func F(cur realm, ...) fails to compile there), and the parameter cannot be named cur either. The realm value arrives non-crossing, which is exactly what is wanted: the frame stays the instance realm, so funds move to and from the instance's address and grc20's rlm.IsCurrent() spoof check still passes. p/moul/x/framelab/v0 is the 30-line probe that pins this property.

A pure package also cannot import a realm, so this one never resolves a token by path: the instance passes *grc20.Token handles in.

Economics, and what is deliberately absent

Reserves are stored fields, never derived from BalanceOf, which removes MINIMUM_LIQUIDITY, skim/sync and the first-depositor inflation attack in one decision. The price: tokens sent directly to an instance's address are permanently stuck. The first deposit mints shares = amountA with no sqrt; later deposits mint min(A-side, B-side) with floor division, so an off-ratio deposit rounds against the depositor. No oracle, no flash swap, no routing, no protocol fee, no governance.

All amounts are int64 and the fee denominator is 1000, so every reserve is capped at MaxReserve = MaxInt64/1000. Unusable with 18-decimal tokens; six to nine decimals is the practical band.

Full design study of the economics: gno-contracts#135.

When to copy this pattern, and when not to

Uniswap V4 moved from a pair-per-contract factory to a singleton, and PR #137 is the same AMM as one realm holding N pools. An AMM is therefore the contested case, kept here as a deliberate A/B.

Reach for instance-per-realm when instances have different owners, trust and lifecycles (a user's shop, a DAO, a game table). Keep one realm with N objects when instances are fungible parts of one shared network (liquidity, an order book, a global index).


Part of 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/pair/v0 dependency graph

🧪 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.