Sudoswap AMM

Sudoswap is an efficient automated market maker (AMM) that supports exchanges between NFTs (ERC-721) and fungible tokens (ETH or ERC20). It facilitates the exchange of NFTs for ETH and tokens without locating a buyer or seller. This provides you with rapid liquidity as an NFT trader and makes trading NFTs similar to trading other tokens.

Already, it has traded 23k ETH and 157k NFTs in volume, earning 620 ETH in fees for liquidity providers and 115 ETH in platform fees, with 25.5k unique wallets trading. The governance token for the SudoAMM protocol $SUDO was also announced recently.

The SudoAMM model makes low-slippage swaps between NFTs and tokens available for traders. Liquidity providers can deploy a pool that will programmatically buy or sell an NFT at a given price (similar to limit orders), ultimately giving more power over the price ranges they fund.

In contrast to OpenSea, users can choose to sell NFTs into a pool at a specified price rather than waiting for a bid. With this, users may instantaneously sell their NFTs into a pool.

The protocol charges a 0.5% fee. Trading fees, which account for most of the fees generated, are sent to pool owners, providing liquidity for trading pairs. Overall, SudoAMM’s low fees come from exempting royalties on their trades, a controversial decision that has recently sparked debates — more on this later.

The most updated trading volume numbers and the details of the token allocation are linked in the resources section at the bottom of this post.


If you want to receive reviews of the latest protocols directly in your inbox, subscribe using the button below. By subscribing, you’ll enter a chance to win an edition of this post as a collectible!


How does SudoAMM work?

The goal is to ensure that anyone can add liquidity to an NFT collection and earn trading fees. To do this, instead of an order book-based paradigm, SudoaMM uses liquidity pools - specifically, distinct liquidity pools. While separate pools increase gas expenses, the protocol has kept them low by writing gas-efficient code and innovating on the clones/proxy paradigm. New pools cost 180k gas.

Those that supply NFTs and tokens (liquidity providers) can earn trading fees on their NFTs, which was never possible before. The creator of liquidity pools can set the price at which NFTs may be purchased or sold for ETH (or other tokens). Trading within the pool is possible as long as sufficient NFTs and ETH are in the pool. SudoAMM’s liquidity pool architecture provides dynamic provisioning using customizable bonding curves: Linear and Exponential Curves.

Each time someone trades within a pool with a linear curve, the pool's pricing will be shifted by a fixed amount of ETH (delta); and each time someone trades within a pool with an exponential curve, the pool's pricing will be multiplied by a percentage amount (delta).

Creator Royalties

Before diving into the technical analysis, we need to address the elephant in the room: SudoAMM does not implement creator royalties. This decision has been discussed at length from all different perspectives; we’ve linked resources at the bottom of this review if you’re interested in reading more about it.

Rather than joining that debate in this section, we want to explain why fees have been optional in the current marketplace landscape; and why it hasn’t been technically feasible to enforce royalty fees with the current standards.

Marketplaces and Tokens

From a technical perspective, two main components make a marketplace — the marketplace contract and the assets traded in the said marketplace.

When an asset is traded, the token transfer is facilitated through the marketplace contract — this is why you usually need to send an approval transaction before you list an NFT; the approval transaction lets the marketplace transfer the NFT from your wallet when a trade is executed. Hence, all the funds and tokens exchanged are routed through the marketplace contract; the NFT does not control where funds go.

The NFT can only specify the amount and account to which royalties should be paid. The standard for specifying royalties is EIP-2981 — but again, the standard only defines a convention, which means some contracts specify them differently, increasing complexity in the marketplace contracts as they try to figure out where to route royalties.

When a sale is completed, conventionally, the marketplace contract routes part of the sale to the royalty recipient specified on the NFT contract. Still, the marketplace can skip that step and send the full purchase amount to the seller — even if the NFT specified royalty information. This is why they cannot be enforced at the protocol level. This is what Sudoswap did; they skipped the royalties step and let the sellers keep the full sale amount.


If you’ve enjoyed this post so far, consider collecting it using the button below!


Technical Analysis

The protocol is broken down into separate components that define pairs, deploy new pairs and interact with them. In this section, we will go through the key contracts that comprise the protocol. We will break them down into four separate categories:

  • Bonding curves — contracts that hold logic to calculate prices

  • Pairs — the contract that holds the tokens (also known as pools)

  • Factory — a contract used to deploy new token pairs

  • Router — a peripheral contract used to interact with token pairs

Bonding Curves

What is it?

A bonding curve is a mathematical curve that defines the relationship between a given asset's price (NFT) and supply. Bonding curves are represented as smart contracts. Currently, the protocol supports two types of curves Linear and Exponential.

How does it accomplish this?

The protocol implements the two bonding curves as contracts that more or less work as libraries. They take inputs from the pairs and return the new prices and other relevant information. The pairs then update their state to reflect new prices.

Each bonding curve implements the ICurve interface, which has four functions: validateDelta, validateSpotPrice, getBuyInfo, and getSellInfo.

For linear curves, function validateDelta and validateSpotPrice accept any delta value. However, in exponential curves, the delta must be greater than 1 for validateDelta, and the new spot price has to be larger than or equal to the minimum price of 1 gwei in validateSpotPrice. Both functions are called when initializing a new pool and the protocol; that way, the protocol can assume the values are correct and use them without verifying them.

Function getBuyInfo first verifies whether or not the user is buying one or more NFTs. Then, for linear curves, getBuyInfo performs an additive calculation that updates the price for each item bought with an additive operation. In exponential curves, getBuyInfo it performs a multiplicative operation to adjust the price for each item bought. With this, both curves update a new buy spot price upwards to avoid arbitraging LPs. The total cost of the items is then added to find the inputValue and the necessary protocol fees are applied.

Like getBuyInfo functions, getSellInfo functions also perform a check for one or more NFTs. Linear curves first compare the spot price to the total price decrease of all NFTs sold in the swap. If the spotPrice < totalPriceDecrease, the new spot price is set to 0 to calculate the number of items that sell into the linear curve (until it reaches 0, the result is rounded up) and to avoid selling for a negative value. Otherwise, the new spot price would be the difference between the spot price and the total price change (delta * the number of items).

For exponential curves, getSellInfo first computes the inverse value of delta (1/delta). Then, it multiplies the spot price by the inverse delta (or divide by delta) to find the new spot price for each item sold, which is then put into a uint128 if the resulting value is less than 1 gwei. The total revenue of the items is then added up to find the outputValue and the necessary protocol fees are applied.

By summing up the protocolFee, starting inputValue or outputValue, and trade fee (for trade pools only), functions getBuyInfo and getSellInfo return the final inputValue or outputValue, newSpotPrice, newDelta, and protocolFee.

Pairs

What is it?

The protocol’s base contract is LSSVMPair.sol, which can hold NFTs, tokens, or both and holds the core swap logic from NFTs to tokens. Each pool is a unique LSSVMPair contract owned by the account that created the pool and has unique settings such as the bonding curve type, pool type, delta, etc. After pool initialization, these pairings' assets and price quotations are tracked.

How does it accomplish this?

Pairs are either LSSVMPairEnumerable or LSSVMPairMissingEnumerable depending if the NFT/Token pair supports the enumerable ERC721 extension. SudoAMM implements an ID set through LSSVMPairMissingEnumerable to allow easy access to NFT IDs in the pool if enumerable is not supported. Depending on the owner’s preferred token type, NFTs could be paired with either an ETH or ERC20.

Based on what the pair contains, an LSSVM pair can either be one of three types:

  1. TOKEN - contains deposited tokens that will be swapped out for users’ NFTs. The pairs provide pricing for how much they will pay for any NFT in the collection.

  2. NFT - contains NFTs that will be swapped out for users’ tokens. These pairs produce a quote for how much it will sell for any NFT in its catalog.

  3. TRADE - contains NFTs and Tokens. Here, you can buy NFTs with tokens and sell NFTs for tokens (functionality of both TOKEN and NFT pair). The spread between buying and selling would be given to the pair owner as fees.

The initialize function is invoked during pair deployment by the factory contract (more in the next section) to set the initial custom parameters of the new pool. The type of NFT collection, bonding curve, and pool type cannot be changed after the pool is deployed. With the Ownable library, pairs are only initialized once and are verified by ensuring the current owner is address(0). A set pool fee of less than 90% is permitted solely for TRADE pools (< 90% because the maximum protocol fee is 10%), while TOKEN and NFT pools have their pool fees set to 0.

Pairs compute the number of tokens or NFTs to send or receive by calling the assigned bonding curve contract (linear or exponential). Remember, the bonding curves are view-only contracts pairs utilize to establish the next trade’s pricing. Knowing this, the pair’s role is to continuously update the state of the pool’s bonding curve and carry out all input/output validations such as verifying the pool type and ensuring users are swapping more than 0 NFTs, and execute output checks that call the bonding curves for pricing information within its swap functions.

After pair deployment, the owner may adjust certain variables to alter the pair’s pricing and liquidity. The spot price, delta, and trade fee can be modified for TRADE pairs. For ETH pairs, the owner can withdraw their NFTs, ETH, or ERC20.

Factory

What is it?

The factory is a contract that deploys new pools and holds protocol-level configuration. The factory contract name is LSSVMPairFactory. Protocol pairs are deployed as clones; clones are smart contracts that hold storage but delegate logic to a separate smart contract.

How does it accomplish this?

Deploying Pairs:

The factory contract deploys pair clones through two functions,createPairETH and createPairERC20, each for the type of pair.

Each function validates the bonding curve type; validation is the same for both pairs. Since bonding curves are smart contracts, each type has a unique address. The factory stores a map of addresses to boolean values to represent if the bonding curve is allowed.

After validation, a new pair clone is deployed. The factory uses a custom version of the minimal proxy pattern. The standardized version of minimal proxies (EIP-1167) does not support immutable variables. Immutable variables and constants are held in the contract's code and not in storage — hence they’re much cheaper to store and access. The modified version allows proxies/clones to hold immutable variables by appending a few initialization parameters to the code; this optimization was pioneered by wighawag and called it clone-with-immutable-args; they explained:

The immutable arguments are stored in the code region of the created proxy contract, and whenever the proxy is called, it reads the arguments into memory, and then appends them to the calldata of the delegate call to the implementation contract. The implementation contract can thus read the arguments straight from calldata.

By doing so, the gas cost of creating parametrizable clones is reduced, since there's no need to store the parameters in storage, which you need to do with EIP-1167. The cost of using such clones is also reduced, since storage loads are replaced with calldata reading, which is far cheaper.

The parameters stored in the code section are:

  • ILSSVMPairFactoryLike factory

  • ICurve bondingCurve

  • IERC721 nft

  • uint8 poolType

  • ERC20 token — only for ERC20 pairs

In addition to more gas-efficient reads, the novel clone pattern saves five store operations for ERC20 pairs and four for ETH pairs.

After the pair is deployed, an initialization call is required to store and validate the initial parameters in the pair. The factory initializes the clones by calling initialize and passing in the msg.sender as the first parameter to be set as the pair's owner, as well as the asset-recipient, delta, fee, and spot price.

After the new pair is deployed and initialized, assets are transferred to the pair contract. For ERC-20 pairs, there are two additional parameters for transferring assets: ERC20 token and uint256 initialTokenBalance. These parameters specify the token and the amount that must be transferred to the pair contract at creation. For ETH pairs, a token doesn’t need to be specified since ETH is the base currency, and the amount used to initialize the pool is the value sent with the transaction, accessed through msg.value. Afterward, the NFTs specified for the pool are transferred from the owner to the pool. All assets are transferred using solmate’s SafeTransferLib.sol.

After the assets are transferred, a NewPair event is emitted to signify a new pair has been deployed.

Protocol Configuration and Ownership:

The factory contract has an owner; the current owner is set to the 0xmons gnosis safe multisig with a 3/5 threshold. The owner has the following capabilities:

  • Updating protocol fees with a maximum of 10%

  • Updating the protocol fee recipient (currently set to the 0xmons multisig)

  • Withdrawing ETH and ERC20 fees from the factory; fees are sent to the protocol fee recipient

  • Add or remove bonding curves

  • Add or remove allowed routers

  • Set allowed contracts that a pair can call arbitrarily

The owner functionality is implemented using OpenZeppelin’s Ownable.sol; one critique of this pattern is that only one transaction is necessary to transfer ownership. A preferred pattern by some requires two — one to initialize the ownership transfer and the second to accept the ownership. With the said pattern, you always ensure that the new owner can submit transactions and it doesn’t lose ownership by accident; this was discussed at length on this Twitter thread.

Router

What is it?

The Router contract consolidates token approvals into a single contract and facilitates swaps. The router contract name is LSSVMRouter.

Swaps work similarly to other DEXs; an input and output amount is specified, as well as a swap route and a deadline. The router addresses two types of swaps, Normal and Robust. A normal swap checks slippage (difference between desired input and output amounts) at the end of trade and reverts if exceeded. A robust swap checks slippage between each swap route and skips the swap if the slippage is exceeded.

The practical difference between both swap types is that sometimes robust swaps will be partially executed where normal swaps fully revert; completing part of the swap order provides a better user experience when trading in volatile pricing environments. More details on robust swaps are below.

How does it accomplish this?

Token to NFT Swaps:

Swapping ETH or ERC20 tokens for NFTs occurs within four functions:

  • swapETHForAnyNFTs

  • swapETHForSpecificNFTs

  • swapERC20ForAnyNFTs

  • swapERC20ForSpecificNFTs

Functions with the keyword “specific” permit users to specify which NFT IDs they want from each pair. Functions that contain the keyword “any” transfer an NFT ID determined by the pool.

Each of these functions checks if the swap meets the deadline set by the user through the checkDeadline modifier. If the swap is proposed at/after the assigned deadline, the swap will revert. If the swap is proposed before the deadline, these functions delegate swapping to their internal functions.

Internal functions _swapETHForAnyNFTs and _swapETHForSpecificNFTs query the pair's bonding curve and swap among the pairs specified. When querying, the pairs cannot directly “pull” ETH; so for ETH to NFT swaps, the router needs to calculate the price per swap with the function getBuyNFTQuote from LSSVMPair.sol to total the exact ETH amount it will send to the pool. This ultimately saves gas by eliminating the need to return any excess ETH.

Similarly, _swapERC20ForAnyNFTs and _swapERC20ForSpecificNFTs perform the same checks but don’t need to query the pair’s bonding curve with getBuyNFTQuotebecause when the Pair requires token transfers, it invokes the Router contract (it doesn’t transfer directly from the user’s wallet). The router contract checks if the caller is a Pair clone through the LSSVMPairCloner with the isPair function. If the pair is a clone, the router transfers tokens from the user’s wallet to the pair.

NFT to NFT Swaps:

NFT to NFT swaps occur within four functions:

  • swapNFTsForAnyNFTsThroughETH

  • swapNFTsForSpecificNFTsThroughETH

  • swapNFTsForAnyNFTsThroughERC20

  • swapNFTsForSpecificNFTsThroughERC20

Like Token to NFTs Swaps, NFT to NFT swaps also perform deadline checks. However, instead of calling one internal function, it calls two. The first internal function swaps an NFT to a token. Doing so ensures that it does an aggregate slippage check by setting the minOutput, which represents the minimum acceptable total excess ETH received, of the swap to 0 and returns the tokens to the LSSVMRouter. Afterward, a second internal function is called to swap tokens to NFTs and ultimately returns the total amount of ERC20 or ETH tokens received.

Robust Swaps:

For circumstances in which the price of your transaction might fluctuate rapidly between submission and execution, the LSSVMRouter resorts to robust swaps. The functions for robust swaps are:

  • robustSwapETHForAnyNFTs

  • robustSwapETHForSpecificNFTs

  • robustSwapERC20ForAnyNFTs

  • robustSwapERC20ForSpecificNFTs

A maximum per-swap cost is established to prevent an intermediate swap from executing if the specified price range is exceeded. Thus, users may request several swaps and have as many of them carried out as feasible. These robust functions are much like normal swaps, but with an added component of looping through swaps to ensure it meets the set maxCost standard with no errors before attempting.

The robustSwapNFTsForToken operation is alike the preceding robust swaps, except that it places value on the per-swap minimum output rather than the per-swap maximum cost. After checking for errors and verifying if it is at least equal to our minOutput, it proceeds to call the swapNFTsForToken function to perform the NFTs for Token swap.

Similar to NFT to NFT swaps, functions robustSwapETHForSpecificNFTsAndNFTsToToken and robustSwapERC20ForSpecificNFTsAndNFTsToToken also, perform two (robust) swaps in one transaction. The first swap involves buying specific NFTs with ETH or ERC20 tokens, and the second swap sells the NFTs for tokens in one transaction. All parameters are stored in a struct called RobustPairNFTsFoTokenAndTokenforNFTsTrade to prevent the “stack too deep” error.

Conclusion

Sudoswap is a novel and ground-breaking protocol for the NFT ecosystem. NFT traders now have access to instant liquidity, which previously seemed difficult with NFTs. Bringing DeFi and NFT communities together improves the overall NFT market’s efficiency and liquidity, which onboards more users into the space.

Sources


Protocol Review is excited to continue publishing accessible reviews for decentralized protocols. We believe in using our deep technical knowledge to distill protocols and increase their mainstream understanding.

To stay up to date with the latest reviews, subscribe to our Mirror publication and follow us on Twitter. If you have suggestions for protocols you’d like to see reviewed or are interested in writing for the PR, fill out this form.

Special thanks to Will Phan for the collaboration on this review.

Subscribe to Protocol Review
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.