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

amm/v0 package

Overview

Package amm is a minimal constant-product automated market maker for GRC20 token pairs, in a single file.

It holds many pools in one realm. A pool is created implicitly by its first liquidity deposit and is addressed by the two tokens' grc20reg keys, in either order. Swaps follow x*y=k with a 30 bps fee that stays in the pool and therefore accrues to the liquidity providers.

What it is not

Not an oracle: the reserve ratio is a spot price that any trader can move within a single transaction. Nothing should price off this realm. Not audited. Not a router: one hop, one pool, no path finding. Not a place to park value.

Design notes worth knowing before calling it

Reserves are stored fields and are NEVER derived from BalanceOf. That is the single decision that removes Uniswap V2's MINIMUM_LIQUIDITY burn, its skim/sync pair, and the first-depositor share-inflation attack in one go: sending tokens straight to this realm's address changes no reserve, no price and no share value. The price of that is blunt and worth stating: tokens sent directly to the realm address are PERMANENTLY STUCK.

The first deposit mints shares equal to the token-A amount rather than sqrt(A*B). The geometric mean is cosmetic, every later operation uses only ratios, and dropping it removes an integer square root over a 128-bit product. Later deposits mint min(A-side, B-side) with floor division on both, so an off-ratio deposit always rounds against the depositor.

v1 vs v0: LP shares are a real GRC20 token

v0 tracks LP positions in a private avl ledger: smallest possible, but a position can only be held by the address that opened it and is invisible to every other contract. v1 mints one GRC20 token per pool instead, holds its PrivateLedger, and registers it with grc20reg. A position is then an ordinary fungible token: transferable, approvable, usable as collateral, and readable by any realm that knows the registry key.

The cost is honest and measurable. Pool creation now also mints a token and writes a registry entry. Positions gain an allowance surface, and with it the classic GRC20 approve race. And because MsgCall cannot pass a realm argument, this realm has to re-export Transfer/Approve/TransferFrom/ Allowance wrappers over the LP token for signing users, four functions that v0 does not need at all. Another REALM can skip them and move its own LP balance through grc20reg directly.

All amounts are int64, as GRC20 mandates, and there is no 256-bit type in reach. Pricing therefore runs through a 128-bit mulDiv (math/bits), and every reserve is capped at maxReserve so that the plain int64 parts of the formula cannot overflow. See the comment on maxReserve for the arithmetic, and note the consequence: this AMM is unusable with 18-decimal tokens. Six to nine decimals is the practical band.

Design study and full analysis: https://github.com/moul/gno-contracts/issues/135

Functions

AddLiquidity

func AddLiquidity(cur realm, keyA, keyB string, maxA, maxB int64) int64

AddLiquidity deposits up to maxA of keyA and maxB of keyB and mints LP shares to the caller. It creates the pool if this is its first deposit, in which case the caller's amounts set the starting price and the whole of maxA and maxB is taken.

On an existing pool the deposit is trimmed to the current reserve ratio: only one side is consumed in full and the remainder of the richer side is left untouched, so pass the amounts you are willing to spend, not the amounts you insist on spending.

The caller must first Approve this realm's address on BOTH tokens for at least the amounts that will be taken. Returns the shares minted.

Params

Command

# WARNING: This command is running in an INSECURE mode.
# It is strongly recommended to use a hardware device for signing
# and avoid trusting any computer connected to the internet,
# as your private keys could be exposed.

gnokey maketx call -pkgpath "gno.land/r/moul/x/amm/v0" -func "AddLiquidity" -args $'' -args $'' -args $'' -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -chainid "gnoland-1" -remote "https://rpc.gno.land" ADDRESSgnokey query -remote "https://rpc.gno.land" auth/accounts/ADDRESS
gnokey maketx call -pkgpath "gno.land/r/moul/x/amm/v0" -func "AddLiquidity" -args $'' -args $'' -args $'' -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -broadcast=false ADDRESS > call.tx
gnokey sign -tx-path call.tx -chainid "gnoland-1" -account-number ACCOUNTNUMBER -account-sequence SEQUENCENUMBER ADDRESS
gnokey broadcast -remote "https://rpc.gno.land" call.tx
  

AllowanceLP

func AllowanceLP(keyA, keyB string, owner, spender address) int64

AllowanceLP reports how much of owner's LP position spender may move.

Params

Command

gnokey query vm/qeval -remote "https://rpc.gno.land" -data "gno.land/r/moul/x/amm/v0.AllowanceLP(,,,)"

Result

AmountOut

func AmountOut(amountIn, reserveIn, reserveOut int64) int64

AmountOut is the pricing function, fee included, as a pure function of the two reserves. Exported so a caller can quote off-chain against reserves it already holds, and so the arithmetic is testable on its own.

Params

Command

gnokey query vm/qeval -remote "https://rpc.gno.land" -data "gno.land/r/moul/x/amm/v0.AmountOut(,,)"

Result

ApproveLP

func ApproveLP(cur realm, keyA, keyB string, spender address, amount int64)

ApproveLP lets spender move up to amount of the caller's LP position.

Same approve race as any GRC20: an allowance changed from a non-zero value can be spent at both the old and the new one if the holder is unlucky with ordering. Set it to 0 first when lowering it.

Params

Command

# WARNING: This command is running in an INSECURE mode.
# It is strongly recommended to use a hardware device for signing
# and avoid trusting any computer connected to the internet,
# as your private keys could be exposed.

gnokey maketx call -pkgpath "gno.land/r/moul/x/amm/v0" -func "ApproveLP" -args $'' -args $'' -args $'' -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -chainid "gnoland-1" -remote "https://rpc.gno.land" ADDRESSgnokey query -remote "https://rpc.gno.land" auth/accounts/ADDRESS
gnokey maketx call -pkgpath "gno.land/r/moul/x/amm/v0" -func "ApproveLP" -args $'' -args $'' -args $'' -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -broadcast=false ADDRESS > call.tx
gnokey sign -tx-path call.tx -chainid "gnoland-1" -account-number ACCOUNTNUMBER -account-sequence SEQUENCENUMBER ADDRESS
gnokey broadcast -remote "https://rpc.gno.land" call.tx
  

LPToken

func LPToken(keyA, keyB string) string

LPToken returns the grc20reg key of the pool's LP token. Hand it to any realm that should read or move these positions without importing this one.

Params

Command

gnokey query vm/qeval -remote "https://rpc.gno.land" -data "gno.land/r/moul/x/amm/v0.LPToken(,)"

Result

PoolCount

func PoolCount() int

PoolCount returns how many pools exist.

Command

gnokey query vm/qeval -remote "https://rpc.gno.land" -data "gno.land/r/moul/x/amm/v0.PoolCount()"

Result

Quote

func Quote(keyIn, keyOut string, amountIn int64) int64

Quote prices amountIn of keyIn against the pool's live reserves. It is the number Swap would return right now, which is not a promise about the next block.

Params

Command

gnokey query vm/qeval -remote "https://rpc.gno.land" -data "gno.land/r/moul/x/amm/v0.Quote(,,)"

Result

RemoveLiquidity

func RemoveLiquidity(cur realm, keyA, keyB string, shares int64) (int64, int64)

RemoveLiquidity burns shares held by the caller and returns the proportional amounts of both tokens, in the caller's (keyA, keyB) argument order.

Burning the pool's entire share supply pays out the whole reserve, so the last provider out leaves nothing unclaimable behind and the pool can be reseeded at a fresh price.

Params

Command

# WARNING: This command is running in an INSECURE mode.
# It is strongly recommended to use a hardware device for signing
# and avoid trusting any computer connected to the internet,
# as your private keys could be exposed.

gnokey maketx call -pkgpath "gno.land/r/moul/x/amm/v0" -func "RemoveLiquidity" -args $'' -args $'' -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -chainid "gnoland-1" -remote "https://rpc.gno.land" ADDRESSgnokey query -remote "https://rpc.gno.land" auth/accounts/ADDRESS
gnokey maketx call -pkgpath "gno.land/r/moul/x/amm/v0" -func "RemoveLiquidity" -args $'' -args $'' -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -broadcast=false ADDRESS > call.tx
gnokey sign -tx-path call.tx -chainid "gnoland-1" -account-number ACCOUNTNUMBER -account-sequence SEQUENCENUMBER ADDRESS
gnokey broadcast -remote "https://rpc.gno.land" call.tx
  

Render

func Render(path string) string

Render lists every pool, or one pool's detail when path is a "keyA~keyB" pool id.

Param

Command

gnokey query vm/qeval -remote "https://rpc.gno.land" -data "gno.land/r/moul/x/amm/v0.Render()"

Result

Reserves

func Reserves(keyA, keyB string) (int64, int64)

Reserves returns the two reserves in the caller's argument order.

Params

Command

gnokey query vm/qeval -remote "https://rpc.gno.land" -data "gno.land/r/moul/x/amm/v0.Reserves(,)"

Result

SharesOf

func SharesOf(keyA, keyB string, owner address) int64

SharesOf returns owner's LP shares, i.e. their LP token balance.

Params

Command

gnokey query vm/qeval -remote "https://rpc.gno.land" -data "gno.land/r/moul/x/amm/v0.SharesOf(,,)"

Result

Swap

func Swap(cur realm, keyIn, keyOut string, amountIn, minOut int64) int64

Swap sells amountIn of keyIn for keyOut and aborts unless at least minOut comes back. The caller must first Approve this realm's address on keyIn.

minOut is the only protection against being sandwiched or against the pool moving between quoting and execution. Pass a real bound; passing 0 means accepting any price at all.

Params

Command

# WARNING: This command is running in an INSECURE mode.
# It is strongly recommended to use a hardware device for signing
# and avoid trusting any computer connected to the internet,
# as your private keys could be exposed.

gnokey maketx call -pkgpath "gno.land/r/moul/x/amm/v0" -func "Swap" -args $'' -args $'' -args $'' -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -chainid "gnoland-1" -remote "https://rpc.gno.land" ADDRESSgnokey query -remote "https://rpc.gno.land" auth/accounts/ADDRESS
gnokey maketx call -pkgpath "gno.land/r/moul/x/amm/v0" -func "Swap" -args $'' -args $'' -args $'' -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -broadcast=false ADDRESS > call.tx
gnokey sign -tx-path call.tx -chainid "gnoland-1" -account-number ACCOUNTNUMBER -account-sequence SEQUENCENUMBER ADDRESS
gnokey broadcast -remote "https://rpc.gno.land" call.tx
  

TotalShares

func TotalShares(keyA, keyB string) int64

TotalShares returns the pool's LP token total supply.

Params

Command

gnokey query vm/qeval -remote "https://rpc.gno.land" -data "gno.land/r/moul/x/amm/v0.TotalShares(,)"

Result

TransferFromLP

func TransferFromLP(cur realm, keyA, keyB string, from, to address, amount int64)

TransferFromLP spends an allowance the owner granted to the caller.

Params

Command

# WARNING: This command is running in an INSECURE mode.
# It is strongly recommended to use a hardware device for signing
# and avoid trusting any computer connected to the internet,
# as your private keys could be exposed.

gnokey maketx call -pkgpath "gno.land/r/moul/x/amm/v0" -func "TransferFromLP" -args $'' -args $'' -args $'' -args $'' -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -chainid "gnoland-1" -remote "https://rpc.gno.land" ADDRESSgnokey query -remote "https://rpc.gno.land" auth/accounts/ADDRESS
gnokey maketx call -pkgpath "gno.land/r/moul/x/amm/v0" -func "TransferFromLP" -args $'' -args $'' -args $'' -args $'' -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -broadcast=false ADDRESS > call.tx
gnokey sign -tx-path call.tx -chainid "gnoland-1" -account-number ACCOUNTNUMBER -account-sequence SEQUENCENUMBER ADDRESS
gnokey broadcast -remote "https://rpc.gno.land" call.tx
  

TransferLP

func TransferLP(cur realm, keyA, keyB string, to address, amount int64)

TransferLP moves amount of the caller's LP position to `to`.

The LP token lives in this realm, so a signing user cannot reach it through the token's own entry points the way they would for any other GRC20: there are none. These four wrappers are that entry point. A REALM holding LP does not need them and can go through grc20reg instead:

Example
1grc20reg.Transfer(0, cur, amm.LPToken(keyA, keyB), to, n)

Params

Command

# WARNING: This command is running in an INSECURE mode.
# It is strongly recommended to use a hardware device for signing
# and avoid trusting any computer connected to the internet,
# as your private keys could be exposed.

gnokey maketx call -pkgpath "gno.land/r/moul/x/amm/v0" -func "TransferLP" -args $'' -args $'' -args $'' -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -chainid "gnoland-1" -remote "https://rpc.gno.land" ADDRESSgnokey query -remote "https://rpc.gno.land" auth/accounts/ADDRESS
gnokey maketx call -pkgpath "gno.land/r/moul/x/amm/v0" -func "TransferLP" -args $'' -args $'' -args $'' -args $'' -gas-fee 1000000ugnot -gas-wanted 1_000_000_000 -send "" -broadcast=false ADDRESS > call.tx
gnokey sign -tx-path call.tx -chainid "gnoland-1" -account-number ACCOUNTNUMBER -account-sequence SEQUENCENUMBER ADDRESS
gnokey broadcast -remote "https://rpc.gno.land" call.tx