deep dive 1: sudoAMM vs the other AMMs they told u not to worry about

Trigger warning: numbers

This blog post goes over sudoAMM vs other AMMs, with most of the focus being sudoAMM vs the constant product (i.e. x*y=k) invariant used by protocols like Bancor and Uniswap.

(Note: This blog post is premised upon the assumption that most NFT traders prefer trading whole numbers of NFTs on existing liquidity platforms and marketplaces. Apologies to fractional NFT enjoyooors, that discussion must be saved for another time.)

demystifying x*y=k

To explain sudoAMM, we need to differentiate it from the constant product invariant. So to start, we'll give an overview of the constant product invariant as applied to NFTs.

The constant product invariant is a popular choice when trading fungible tokens. One reason is that it provides support over the entire range of potential prices, from 0 to infinity. As a result, LPs do not need to rebalance their liquidity, even as prices go sharply up or down. This can make it seem like an unbiased choice when it comes to pricing.

However, the constant product invariant is biased in a different way–it is biased to never run out of either reserves in token A or token B. In other words, the AMM pool can never be 100% drained of either token A or B. Traders will just get increasingly unfavorable rates as they try to swap for the remnants of whatever meager tokens are left on one side.

When trading fungible tokens, this "bias" is can be almost invisible. Most traders understand the notion of slippage relative to pool depth. And the fact that fungible tokens can be traded in fractional amounts means that arbitrage with external markets (if they exist) will keep the AMM price in line with the "true" market price.

Existing NFT liquidity platforms like NFTX and NFT20 seek to enable both price discovery and liquidity for NFT collections by utilizing the constant product invariant. However, when trading NFTs on a constant product curve, the bias to never run out of reserves becomes more visible. Users generally want to trade whole NFTs, as we've seen with popular external marketplaces like OpenSea also deal with whole NFTs. This adds additional mental overhead for traders who may be used to a different slippage paradigm, and it prevents straightforward arbitrage between other markets.

Essentially, platforms like NFTX and NFT20 are discretizing the x*y=k curve.

If we assume that traders must buy or sell a whole number of NFTs, then for any given pool, we can calculate the prices that the pool will buy or sell NFTs at, until they run out of either NFTs or ETH.

For simplicity sake, let's assume there is an NFT collection currently valued at 1 ETH, and we are making a constant product pool. Say we deposit 10 ETH and 10 NFTs. Then, depending on how many NFTs are left in the pool, the price to buy/sell an NFT looks like:

                                               
  ETH Price to Buy 1 NFT ETH Price to Sell 1 NFT Slippage % between Buy/Sell
 1 NFTs in Pool 50.000 N/A
 2 NFTs in Pool50.000 16.667 66.667
 3 NFTs in Pool16.667 8.333 37.500
 4 NFTs in Pool8.333 5.000 26.667
 5 NFTs in Pool5.000 3.333 20.833
 6 NFTs in Pool3.333 2.381 17.143
 7 NFTs in Pool2.381 1.786 14.583
 8 NFTs in Pool1.786 1.389 12.698
 9 NFTs in Pool1.389 1.111 11.250
 10 NFTs in Pool1.111 0.909 10.101
 11 NFTs in Pool0.909 0.758 9.167
 12 NFTs in Pool0.758 0.641 8.392
 13 NFTs in Pool0.641 0.549 7.738
 14 NFTs in Pool0.549 0.476 7.179
 15 NFTs in Pool0.476 0.417 6.696
 16 NFTs in Pool0.417 0.368 6.275
 17 NFTs in Pool0.368 0.327 5.903
 18 NFTs in Pool0.327 0.292 5.573
 19 NFTs in Pool0.292 0.263 5.278
 20 NFTs in Pool0.263 ... ...

The lines to focus on are the rows above and below 10 NFTs in Pool, as that's where the pool has buy/sell prices close to 1 ETH. Using the above table, we can see the behavior mentioned earlier. As the number of NFTs left in the pool decreases and approaches 0, the price to buy 1 NFT increases until we reach a price of infinity for the last NFT. Similarly, as the number of NFTs in the pool increases, the price to buy 1 NFT decreases.

There are two metrics I'll focus on when it comes to looking at AMMs for NFTs, one for traders, and one for LPs.

For traders who are buying or selling more than 1 NFT, one natural question is, "What is the average rate I am getting when buying or selling multiple NFTs?" This is important to know when e.g. determining how to size a buy or sell.

Assuming that the pool currently holds 10 NFTs (and thus has an instantaneous price of 1 ETH), then we can calculate the average price for buying or selling multiple items:

               
  For 1 NFT For 2 NFTs For 3 NFTs For 4 NFTs For 5 NFTs
 Avg Buying Price1.111 1.250 1.429 1.667 2.000
 Avg Selling Price0.909 0.833 0.769 0.714 0.667

As more items are bought or sold, the average rate becomes more unfavorable.

In the table above, we can see that LPs must reserve some of their NFT supply to sell at very high rates (e.g. 50 ETH, 16.667 ETH, etc.), as well as reserve some of their ETH supply to buy at very low rates (e.g. 0.292 ETH, 0.263 ETH, etc.)

Liquidity cannot be everywhere at once. By providing liquidity at these extreme prices, LPs are necessarily sacrificing potential liquidity at tighter pricing closer to the current price. The larger the range, the more capital is idle (and thus not earning fees) at any given time. Thus, one question that LPs may ask is, "How much of my liquidity is being used to LP within some multiplier X (above and below) of the current price?"

For example, LPs may only care about how much of their funds are LP'ing in the range [0.5 ETH, 2.0 ETH ], for (1/2) ETH and (1*2) ETH. (Here, X is 2).

Again, assuming the same pool conditions and pricing as before, we can calculate the percentage of NFTs that are bought at sold within a multiplier X of the current price of 1 ETH:

               
  X = 1.25 X = 1.5 X = 1.75 X = 2 X = 3 X = 4
 % Liquidity Used15% 25% 30% 40% 60% 80%

As we increase the price range (i.e. X increases), we can see that more and more liquidity gets used. 15% of total liquidity is used to provide buys/sells within 1.25X of the current price, 25% of total liqudiity is used to provide buys/sells within 1.5X of the current price, and so on.

At this point, someone might ask about the changing the initial pool parameters. A pool with starting liquidity of 10 ETH and 10 NFTs is not very deep. What if we used, say, 100 ETH and 100 NFTs instead?

Adding more ETH and NFTs would improve the average price for buying or selling multiple NFTs. The deeper a pool's liquidity, the lower the price impact for buys/sells.

However, adding more ETH and NFTs would not improve the % of total liquidity used in the pool for a certain price range. No matter how much more ETH and NFTs we put in, the constant product invariant will always only allocate around 25% of our liquidity at the price range 1.5X above and below the current price. For example, at 100 ETH and 100 NFTs, only around 25 ETH worth of liquidity will be used to provide buys/sells in the range [0.666 ETH, 1.5 ETH] for (1/1.5) ETH and (1*1.5) ETH.

This is again a consequence of the constant product invariant trying to spread liquidity evenly over the entire price range of 0 to infinity.

Now, hopefully you have an intuition for how constant product curves work in the discretized case.

How does sudoAMM compare?

enter sudoAMM

sudoAMM seeks to improve rates for traders and improve capital efficiency for LPs. It does this by decoupling pricing calculations from the token reserves. Unlike the constant product invariant where the pricing gets worse as fewer NFTs are left in the pool (and vice versa), sudoAMM puts LPs in control by letting them specify exactly how they want their pool's pricing to change.

It achieves this by abstracting out the pricing function. There is no invariant that has to be maintained before or after each swap. Instead, LPs can theoretically specify any arbitrary function to be computed at execution time to determine how they want to price their pool.

Presently, sudoAMM has two very simple pricing functions live: a linear curve, and an exponential curve. Each time someone trades with a pool with a linear curve, the pool's pricing will be shifted by a fixed amount. Each time someone trades with a pool with an exponential curve, the pool's pricing will be multiplied by a fixed amount.

Even with such simple behavior, however, we can construct pools that offer similar or better pricing to the constant product invariant we used above.

Let's calculate the same tables as before, but this time with a sudoAMM pool. Assume we have a pool with 10 ETH and 10 NFTs, this time with an exponential curve parameterized with a multiplier of 1.1 (i.e. each buy or sell will multiply or divide the price by 1.1).

                                               
  ETH Price to Buy 1 NFT ETH Price to Sell 1 NFT Slippage % between Buy/Sell
 1 NFTs in Pool2.358 2.144 4.762
 2 NFTs in Pool2.144 1.949 4.762
 3 NFTs in Pool1.949 1.772 4.762
 4 NFTs in Pool1.772 1.611 4.762
 5 NFTs in Pool1.611 1.464 4.762
 6 NFTs in Pool1.464 1.331 4.762
 7 NFTs in Pool1.331 1.210 4.762
 8 NFTs in Pool1.210 1.100 4.762
 9 NFTs in Pool1.100 1.000 4.762
 10 NFTs in Pool1.000 0.909 4.762
 11 NFTs in Pool0.909 0.826 4.762
 12 NFTs in Pool0.826 0.751 4.762
 13 NFTs in Pool0.751 0.683 4.762
 14 NFTs in Pool0.683 0.621 4.762
 15 NFTs in Pool0.621 0.564 4.762
 16 NFTs in Pool0.564 0.513 4.762
 17 NFTs in Pool0.513 0.467 4.762
 18 NFTs in Pool0.467 0.424 4.762
 19 NFTs in Pool0.424 0.386 4.762
 20 NFTs in Pool0.386 0.350 4.762

Again, we can start from the middle row with 10 NFTs in Pool. As we go up or down, we can see the price to buy/sell 1 NFT also goes up or down, but more gradually compared to the constant product invariant. In exchange for this tighter pricing, the sudoAMM pool is actually willing to sell its last NFT for 2.358 ETH, rather than pricing it at infinity.

Another property of the exponential curve is that due to the multiplicative nature of pricing between buys/sells, the slippage % is actually constant no matter what the current price is.

Next, we can calculate the average price for buying/selling multiple NFTs:

               
  For 1 NFT For 2 NFTs For 3 NFTs For 4 NFTs For 5 NFTs
 Avg Buying Price1.000 1.050 1.103 1.160 1.221
 Avg Selling Price0.909 0.868 0.829 0.792 0.758

As a consequence of our tighter price range, we get better average prices for both buys and sells: an average price of 1.221 ETH for buying 5 NFTs (vs 2 ETH), and an average price of 0.758 ETH for selling 5 NFTs (vs 0.667 ETH).

For LPs, this means higher % of total liquidity used:

               
  X = 1.25 X = 1.5 X = 1.75 X = 2 X = 3 X = 4
 % Liquidity Used30% 50% 60% 80% 100% 100%

At this point, comparisons to Uniswap v3 (and, more generally, concentrated liquidity) are unavoidable.

If all sudoAMM does is provide tighter price ranges (of which better average price and higher % liquidity used are merely a consequence), then why not continue to use a fungible intermediary for the NFT, and provide liquidity on Uniswap v3 instead of a constant product curve?

The answer is two-fold: sudoAMM offers improvements in both efficiency and flexibility.

First, efficiency. Any protocol that first uses Uni v3 (or any other fungible token AMM) for price discovery pays an additional gas overhead when it first converts the NFT into tokens, before doing the pricing calculation. In sudoAMM, native NFTs are paired against ETH. No extra conversion overhead is needed.

Second, flexibility. As market conditions change, LPs may want to adjust their pool's parameters. sudoAMM offers LPs many options. At any point, they can:

sudoAMM also provides native support for DCA-style strategies via single-sided liquidity pools. Like Uni v3, LPs can deposit one side of a pair. For example, just NFTs to swap for ETH (as the NFT price rises) or just ETH to swap for NFTs (as the NFT price falls). Unlike Uni v3, these pools actually only allow for swaps in the desired direction, meaning there is no need to "buy back" in the unfavorable direction once price changes.

Many of these options are also available on Uni v3, but they require the use of another service like Gelato or Duality, whereas sudoAMM provides native support. Others, like DCA'ing out at a specified price interval are complicated to express in Uni v3 and would require the creation of multiple Uni v3 LP positions, where sudoAMM would require only one LP position.

in summary

sudoAMM is a an AMM designed specifically for NFTs. By allowing LPs to set how their pool pricing changes, we can unlock concentrated liquidity similar to Uni v3, but with many additional native benefits. All the code used to generate the above tables can be found here.

Mainnet testing for the contracts has gone well, and we'll be increasing UI access to more community members soon.

I hope you found this blog post helpful. Please join us on the next deep dive as we talk decentralization and eating the global NFT marketplace.