PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

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

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
v0
Namespace
moul / x / pair
Files
4 (README)(gnomod.toml)
Exported functions
n/a — not supported for pure packages by the node (vm/qfuncs)
Module
gno.land/p/moul/x/pair/v0
gno
0.9

Files (4)

  • README.mdmarkdown
  • gnomod.tomltoml
  • pair.gnogno
  • pair_test.gnogno
README.mdPreviewRaw
# `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

```go
var p *pair.Pair

func init(cur realm) {
	p = pair.New(keyA, keyB, grc20reg.MustGet(keyA), grc20reg.MustGet(keyB))
	pairreg.Register(cross(cur), p)
}

func Swap(cur realm, keyIn string, amountIn, minOut int64) int64 {
	return p.Swap(0, cur, keyIn, amountIn, minOut)
}
```

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](https://github.com/moul/gno-contracts/issues/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](https://github.com/moul/gno-contracts/pull/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).

<!-- BEGIN GNOCONTRACTS FOOTER (generated by `make readmes`; do not edit below) -->

---

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/pair/v0 dependency graph](https://raw.githubusercontent.com/moul/gno-contracts/main/_assets/gno.land/p/moul/x/pair/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).

<!-- END GNOCONTRACTS FOOTER -->

Functions

not supported for pure packages by the node (vm/qfuncs)

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.