/p/moul/x/pair/v0
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_packageis permanently denied to realm code, so an instance is anaddpkgtransaction signed by a human, priced in gas and a storage deposit.tools/pairgenfills 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.Pairpointer, so onevm/qrenderrenders 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:

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