ZAMM (zenAtomic Market Maker) is a singleton smart‑contract implementing a gas‑minimised Uniswap V2–style constant‑product market. It wraps liquidity shares and custom tokens in the ERC‑6909 multi‑token standard and introduces transient storage credit so routers can chain swaps without redundant external transfers.
Each pool obeys x·y = k
. Pools are identified by poolId = keccak256(PoolKey)
where PoolKey
includes:
{
id0, id1 // ERC‑6909 token IDs (0 = ERC‑20 / native ETH)
token0, token1 // contract addresses (0 = native ETH)
swapFee // basis‑points fee, ≤ 10000
}
Ordering rules guarantee a single canonical ID for any unordered pair.
Add‑/remove‑liquidity mints/burns a fungible ERC‑6909 token whose tokenContract == address(this)
and id == poolId
. Supply tracks totalLiquidity
and supports transfer()
/approve()
natively.
During a transaction ZAMM can credit balances to the caller in transient storage (EIP‑1153 style). This acts like an in‑contract msg.sender → address(this)
transfer costing ≈ 2 gas.
deposit(token,id,amount)
credits manually._safeTransfer(...,to)
where to == address(this)
credits automatically._useTransientBalance()
spends credit inside swaps/liquidity adds.recoverTransientBalance()
.This mechanism lets routers build multihop paths without touching external balances more than once — dramatically reducing gas.
The constructor sets the deployer as feeToSetter
. Use Fee Control functions to update feeTo
and feeToSetter
. No factory contract is required: pools are instantiated lazily on first addLiquidity()
or makeLiquid()
.
make()
function make(address maker, uint256 supply, string uri) returns (uint256 coinId)
Deploy a new ERC‑6909 token (not paired) and mint supply
to maker
. The coinId
is keccak256(selector, msg.sender, block.timestamp)
.
makeLiquid()
function makeLiquid(address maker,address liqTo,uint256 mkrAmt,
uint256 liqAmt,uint256 swapFee,string uri)
payable returns (uint256 coinId, uint256 poolId, uint256 liquidity)
Same as make()
, plus immediately creates an ETH ↔ Token pool and seeds initial liquidity with msg.value ⬌ liqAmt
. Useful for token launches.
addLiquidity()
Add assets to an existing pool, receiving LP tokens. Supports transient credit and native ETH. Optimal ratios are calculated on‑chain.
removeLiquidity()
Burn LP tokens for underlying reserves. amount0Min/amount1Min
guard against slippage.
swapExactIn()
swapExactIn(PoolKey key, uint256 amountIn,
uint256 amountOutMin, bool zeroForOne,
address to, uint256 deadline) payable → uint256 amountOut
zeroForOne=true
sells token0 ⇒ token1.msg.value
.amountOut < amountOutMin
.swapExactOut()
swapExactOut(PoolKey key, uint256 amountOut,
uint256 amountInMax, bool zeroForOne,
address to, uint256 deadline) payable → uint256 amountIn
Mirror of swapExactIn
; refunding excess ETH when provided.
swap()
(low‑level)swap(PoolKey key, uint256 amount0Out, uint256 amount1Out,
address to, bytes data)
Flash‑style primitive patterned after Uniswap V2. When to == address(this)
the output is credited to transient storage, enabling efficient multihop sequences inside a router or multicall
. If data
is non‑empty, IZAMMCallee(to).zammCall
will be invoked.
Function | Purpose |
---|---|
deposit(token,id,amount) | Pre‑credit balance. Use before calling swaps in the same tx. |
recoverTransientBalance(token,id,to) | Withdraw leftover credit at end of tx. |
multicall(bytes[]) | Sequential internal delegatecall ; preserves transient credit across calls. |
Tip: If you build a router, structure calls as:
deposit()
for input tokens onceswap()
(or swapExactIn
) with to=this
swapX
with external to
recoverTransientBalance
The fallback function implements a compressed calldata codec (Solady LibZip). Routers may send densely‑packed calls directly to ZAMM
. For readability during development, prefer multicall
.
setFeeTo(address)
sets the protocol‑fee recipient. setFeeToSetter(address)
transfers admin rights. Only the current feeToSetter
(initialized to deployer) may call either function.
lock
modifier).make()
/makeLiquid()
IDs depend on block.timestamp
; do not rely on deterministic ordering across forks.deadline
to mitigate price racing.unchecked
where overflow is impossible by design; review upstream math if extending.// pseudo router
zamm.swapExactIn{value:1 ether}(
PoolKey(0, DAI_ID, address(0), DAI, 30), // 0.30 % fee
1 ether,
1800e18, // minimum DAI out
true,
user,
block.timestamp + 5 minutes
);
bytes[] calls = new bytes[](3);
// 1) credit WBTC once
calls[0] = abi.encodeWithSelector(ZAMM.deposit.selector, WBTC, 0, 1 btc);
// 2) WBTC→ETH, output kept as credit
calls[1] = abi.encodeWithSelector(ZAMM.swap.selector,
keyWBTC_ETH, 0, ethOut, address(this), "");
// 3) ETH→USDC to user
calls[2] = abi.encodeWithSelector(ZAMM.swapExactIn.selector,
keyETH_USDC, ethOut, minUsdc, true, user, deadline);
zamm.multicall(calls);
(coinId, poolId, lp) = zamm.makeLiquid{value:100 ether}(
msg.sender, msg.sender, 1_000_000 * 10**18, // mint & send tokens
100_000 * 10**18, // paired token amount
50, // 0.5 % fee
"ipfs://…"
);
Function (public) | Key Args | Returns |
---|---|---|
make | maker,supply,uri | coinId |
makeLiquid | maker,liqTo,mkrAmt,liqAmt,swapFee,uri | coinId,poolId,liquidity |
addLiquidity | PoolKey,amount0Desired,amount1Desired,… | amount0,amount1,liquidity |
removeLiquidity | PoolKey,liquidity,… | amount0,amount1 |
swapExactIn | PoolKey,amountIn,… | amountOut |
swapExactOut | PoolKey,amountOut,… | amountIn |
swap | PoolKey,amount0Out,amount1Out,to,data | — |
deposit | token,id,amount | — |
recoverTransientBalance | token,id,to | amount |
multicall | bytes[] data | bytes[] results |
setFeeTo / setFeeToSetter | address | — |
Code released under MIT. Documentation © 2025 z0r0z. No warranty.