Exploring Sudoswap Smart Contracts

turupawn

Ahmed Castro

Posted on December 22, 2022

Exploring Sudoswap Smart Contracts

Sudoswap is a highly innovative protocol that combines familiar ideas in a unique way. It allows for the buying and selling of NFTs like any other marketplace, but it does so using liquidity pools, similar to those found on Uniswap. This makes Sudoswap a valuable platform to explore for those interested in the NFT market. In this video, I will explain the general functioning of Sudoswap and also share a bit of my work and experience with this protocol.

Before we start

For this tutorial you will need NodeJs that I recommend downloading it from Linux via NVM, and also you will need Metamask or another compatible wallet with Goerli funds that you can get from a faucet.

Autofloor?

Sudoswap allows for a lot of creativity in its use. As an example, I will show you how a token launch (primary market) can be conducted in a liquidity pool on Sudoswap. This eliminates the need for an external minting page. The token will also have Autofloor functions, which automatically transfer a percentage of the funds in the launch pool to another contract to provide holders with a guaranteed, immediate sale option if they wish to exit the market.

// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.16;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract ICurve{}

abstract contract LSSVMPair
{
    enum PoolType {
        TOKEN,
        NFT,
        TRADE
    }
}

contract LSSVMPairETH {
    function changeSpotPrice(uint128 newSpotPrice) external {
    }
}

contract LSSVMPairFactory {
    function createPairETH(
        IERC721 _nft,
        ICurve _bondingCurve,
        address payable _assetRecipient,
        LSSVMPair.PoolType _poolType,
        uint128 _delta,
        uint96 _fee,
        uint128 _spotPrice,
        uint256[] calldata _initialNFTIDs
    ) external payable returns (LSSVMPairETH pair) {
    }
}

contract MyNFT is ERC721Enumerable, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter public tokenCounter;
    uint256 public token_count;

    LSSVMPairFactory public factory;
    LSSVMPairETH public nftPair;
    LSSVMPairETH public tokenPair;

    uint public MAX_AMOUNT = 10000;

    uint128 public mintPrice;
    uint128 public autoFloorPrice;
    uint public autoFloorFeePercentage;
    address public autoFloorNFTReciever;
    uint public  FEE_DECIMAL = 2;

    address public a1;
    address public a2;
    address public a3;

    string public baseTokenURI = "https://api.io/";

    // Constructor and init

    constructor() ERC721("My NFT", "MNFT") {
        mintPrice = 0.04 ether;
        autoFloorPrice = 0.02 ether;
        autoFloorFeePercentage = 5000; // 50%
        autoFloorNFTReciever = 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045;
        a1 = 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045;
        a2 = 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045;
        a3 = 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045;
    }

    function initializeSale(address factoryAddress) public {
        factory = LSSVMPairFactory(factoryAddress);
        uint[] memory initialNFTIDs = new uint[](10);
        address payable recipient = payable(this);

        while(tokenCounter.current()<10)
        {
            initialNFTIDs[tokenCounter.current()] = tokenCounter.current();
            _mint(address(this),tokenCounter.current());
            _approve(factoryAddress, tokenCounter.current());
            tokenCounter.increment();
        }

        nftPair = factory.createPairETH(
            IERC721(address(this)),
            ICurve(0x5B6aC51d9B1CeDE0068a1B26533CAce807f883Ee), // Lineal
            recipient,  // This contract
            LSSVMPair.PoolType.NFT,// NFT, TOKEN, TRADE
            0e18,       // Delta
            0,          // Fee
            mintPrice,  // Initial price
            initialNFTIDs
        );

        uint[] memory emptyArray = new uint[](0);
        tokenPair = factory.createPairETH(
            IERC721(address(this)),
            ICurve(0x5B6aC51d9B1CeDE0068a1B26533CAce807f883Ee), // Lineal
            payable(autoFloorNFTReciever), // This contract
            LSSVMPair.PoolType.TOKEN,// NFT, TOKEN, TRADE
            0e18,       // Delta
            0,          // Fee
            autoFloorPrice, // Initial price
            emptyArray
        );
    }

    // View functions

    function getNFTPairAddress() public view returns(address) {
        return address(nftPair);
    }

    function getTokenPairAddress() public view returns(address) {
        return address(tokenPair);
    }

    // Override functions

    function _baseURI() internal view virtual override returns (string memory) {
        return baseTokenURI;
    }

    function _afterTokenTransfer(address from, address to, uint256 tokenId) internal override(ERC721)
    {
        require(to != address(nftPair), "Can't send to mint pair");
        if(from != address(0))
        {
            if(from == address(nftPair) && uint(tokenCounter.current()) < MAX_AMOUNT)
            {
                _mint(address(nftPair), tokenCounter.current());
                tokenCounter.increment();
            }
        }
        super._afterTokenTransfer(from, to, tokenId);
    }

    function _safeTransfer(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) internal override {
        data;
        _transfer(from, to, tokenId);
    }

    // Owner functions

    function setAutoFloorFeePercentage(uint value) public onlyOwner
    {
        autoFloorFeePercentage = value;
    }

    function setMintPrice(uint128 value) public onlyOwner
    {
        mintPrice = value;
        nftPair.changeSpotPrice(value);
    }

    function setAutoFloorPrice(uint128 value) public onlyOwner
    {
        autoFloorPrice = value;
        tokenPair.changeSpotPrice(value);
    }

    function setAddresses(address[] memory _a) public onlyOwner {
        a1 = _a[0];
        a2 = _a[1];
        a3 = _a[2];
    }

    function withdrawTeam(uint256 amount) public payable onlyOwner {
        uint256 percent = amount / 100;
        bool sent;
        bytes memory data;
        (sent, data) = payable(a1).call{value: percent * 40}("");
        require(sent, "Failed to send Ether");
        (sent, data) = payable(a2).call{value: percent * 30}("");
        require(sent, "Failed to send Ether");
        (sent, data) = payable(a3).call{value: percent * 30}("");
        require(sent, "Failed to send Ether");
    }

    function setBaseURI(string memory baseURI) public onlyOwner {
        baseTokenURI = baseURI;
    }

    // Fallback functions

    fallback() external payable {
    }

    receive() external payable {
        uint256 transferAmount = (msg.value) * (autoFloorFeePercentage) / (10**(FEE_DECIMAL + 2));
        (bool sent, bytes memory data) = address(tokenPair).call{value: transferAmount}("");
        data;
        require(sent, "Failed to send Ether");
    }
}
Enter fullscreen mode Exit fullscreen mode

Run the tests

Additionally, I prepared a pair of unit tests for those who want deepen into this topic.

git clone https://github.com/FilosofiaCodigo/SudoswapToken
cd SudoswapToken
npm install
Enter fullscreen mode Exit fullscreen mode

Then, create a .env file with the same format as .env.example. And put your RPC URL.

.env

RPC_URL=RPCURLHERE
Enter fullscreen mode Exit fullscreen mode

Finally run the tests.

npx test
Enter fullscreen mode Exit fullscreen mode

Thanks for watching this video!

Follows us on dev.to and in Youtube for everything related to Blockchain development.

đź’– đź’Ş đź™… đźš©
turupawn
Ahmed Castro

Posted on December 22, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related