# NFT AMM Project [TOC]  ## Feature Requirement **1. Liquidity Management** - Measure user's liquidity position, eg: generate one NFT for each user's pool, calculate "liquidity" just like Uniswap V3 does to measure user's deposits and real-time value of the pool. Therefor, third-parties can build further features and derivatives on top of those liquidity positions, just like they do with cToken, LP tokens. - Handle multiple users in the same pool to avoid liquidity fragments, eg: manage assets in one pool of multiple users with the same strategy, like in Uniswap V3, assets of USDC/USDT pool from multiple users with the same fee will goes to the same place(the same contract). - Adopt [EIP-3525](https://eips.ethereum.org/EIPS/eip-3525) if possible, facilitate NFT partial transfer. **2. Multiple Router** - Support ERC20 router, support USX in the future. - Multiple router between ERC20 and ETH.  - Support yield assets, like iToken, cToken. - Off-chain router service to match multiple swap requirements, including ERC20/ETH swap, - Case 1: multiple hybrid ERC20/ETH ==> NFT - Case 2: NFT => multiple hybrid ERC20/ETH - Case 3: NFT => ETH => ERC20 => NFT - Case 4: NFT => hybrid ERC20 => NFT - ... **3. Off-chain Bounding Curve** - More sophisticated pricing strategy, On-chain VS Off-chain - hybrid model where we can move more of the pricing calculations off-chain - Oracle based bounding curve like Dodoex. **4. TWAP(Time-weighted Average Price)** - Support build-in TWAP like Uniswap and Curve. **5. Boost** - Support build-in feature to boost user swap and reward, for example: users who hold specified NFTs(with hierarchical authorities) can boost with less swap fees and better rewards. **6. EIP Compatible** - Support EIP721/EIP1155 compatible NFTs. **7. Referral** - Support referral with incentives, eg: portion of protocl fee allocation, or dedicated rewards. **8. Royalty** - Support EIP2981 and royalties on swap. ## References - [caviar NFT AMM](https://docs.caviar.sh/): automated market maker (AMM) protocol for trading NFTs, supporting liquidity pools and custom pools. - [about NFT, Trading & Lending | Snow](https://hackmd.io/@0xSn0w/Hkgl40QJj): Summary of NFT protocols - [Arrakis Finance](https://www.arrakis.finance/): Uniswap V3 NFT position vault - [Fractional](https://fractional.art/):NFT fragment protocol - [NFTX](https://nftx.io/): NFT tokenization protocol - [linoswap](https://www.linoswap.io/): A Rarity-Friendly NFT AMM, support liquidity mining - [Sudoswap](https://sudoswap.xyz/#/): Common NFT AMM - [Collection](https://collection.xyz/): Staking protocol based on sudoswap's liquidity - [Collectionstaker](https://etherscan.deth.net/address/0x8720c1f4a4cc616c4821352b985898d4f7ac6159) - [Collectionswap](https://etherscan.deth.net/address/0x226620C03C2f2dBBBd90E2Eca4754D8a41Fd3DEB#code) - [RewardPoolEth](https://etherscan.deth.net/token/0xc743cd9082ab5d857df1a91dbe909f945bdaf7c7) - [Uniswap V3](https://uniswap.org/blog/uniswap-v3): Liquidity position management via NFT --- ## Proof-Of-Concept ### Prototype ![](https://hackmd.io/_uploads/rk5I0BT9j.jpg) ### AMM #### Actions > ***Create*** / ***Swap(Buy&Sell)*** / ***AddLiquidity*** / ***Removeliquidity*** #### Data * Collections * collection: Ape * Pools * Pool-A * id * balance: ETH / NFT * price: spot / buy / sell * Pool-B * collection: Rabbit ### Mining #### Actions > ***Deposit*** / ***Withdraw*** / ***Claim*** #### Data * Staking Pool * total staked value * reward rate * My * staked value * earned ### Wallet * balance: collections / position / ETH --- ## Functions & Methods https://github.com/dforce-network/Amy/blob/dev/docs/index.md https://github.com/dforce-network/Amy/blob/dev/test/positionManagement.goerli.test.ts https://github.com/dforce-network/Amy/blob/dev/test/liquidityMining.test.ts ### Contracts on Goerli(12/01/2023) | Contract Name | Contract Address | Link | |:-------------------:|:------------------------------------------ |:-------------------------------------------------------------------------------------- | | MerkleProofVerifier | 0xb7503743638c09de0e40217b45f8753fe3d50a44 | [link](https://goerli.etherscan.io/address/0xb7503743638c09de0e40217b45f8753fe3d50a44) | | MockERC20 | 0x77aE086183B4B5Be3e14831518F0E3C6CcCBfa7b | [link](https://goerli.etherscan.io/address/0x77aE086183B4B5Be3e14831518F0E3C6CcCBfa7b) | | MockERC721_1 | 0x44389049101cfCf0C269178c429C70FfFcee8836 | [link](https://goerli.etherscan.io/address/0x44389049101cfCf0C269178c429C70FfFcee8836) | | MockERC721_2 | 0x21D9d55dfFf99f2ED86c93C203f0EeA5aDBB23ab | [link](https://goerli.etherscan.io/address/0x21D9d55dfFf99f2ED86c93C203f0EeA5aDBB23ab) | | PositionManagement | 0x1faf03fAf59b8922Be4462f4DfcfEf5F0615D8e1 | [link](https://goerli.etherscan.io/address/0x1faf03fAf59b8922Be4462f4DfcfEf5F0615D8e1) | | RankingStrategy | 0x7cc361C83c0444dd589834CC5bDcF0d9144C3119 | [link](https://goerli.etherscan.io/address/0x7cc361C83c0444dd589834CC5bDcF0d9144C3119) | | StakingPool.active | 0xeeA6Ff8e5aE37D2F8e940A0361c970205F360f75 | [link](https://goerli.etherscan.io/address/0xeeA6Ff8e5aE37D2F8e940A0361c970205F360f75) | | StakingPool | 0x25D6D97a4b7b8505Ee4a3942BE8095D819bfe77F | [link](https://goerli.etherscan.io/address/0x25D6D97a4b7b8505Ee4a3942BE8095D819bfe77F) | | Pair_Router(sudo) | 0x25b4EfC43c9dCAe134233CD577fFca7CfAd6748F | [link](https://goerli.etherscan.io/address/0x25b4EfC43c9dCAe134233CD577fFca7CfAd6748F) | | exponentialCurve | 0x0D807bd5fF2C4eF298755bE30E22926b33244B0c | [link](https://goerli.etherscan.io/address/0x0D807bd5fF2C4eF298755bE30E22926b33244B0c) | | linearCurve | 0xaC6dcFF6E13132f075e36cA3a7F403236f869438 | [link](https://goerli.etherscan.io/address/0xaC6dcFF6E13132f075e36cA3a7F403236f869438) | | xykCurve | 0x02363a2F1B2c2C5815cb6893Aa27861BE0c4F760 | [link](https://goerli.etherscan.io/address/0x0D807bd5fF2C4eF298755bE30E22926b33244B0c) | | ERC721Enumberator | 0x2A02802290f98aa18A028f748B60055d91c533eC | [link](https://goerli.etherscan.io/address/0x2A02802290f98aa18A028f748B60055d91c533eC) | ### Contract ABI All ABI are at [here](https://github.com/dforce-network/Amy/tree/dev/abi) ### Liquidity(in the contract: PositionManagement) - Create Pool ```solidity function createPairWithETH(contract IERC721 nft, contract ICurve bondingCurve, uint128 delta, uint96 fee, uint128 spotPrice, uint256[] initialNFTIDs, uint256 rank, bytes32[] rankProof) external payable returns (contract ILSSVMPairETH newPair, uint256 tokenId) ``` _To create a new pair and deposit initial nft items and ETH into it, a new token ID willd be minted with coressponding value_ ##### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | nft | contract IERC721 | The address of the nft | | bondingCurve | contract ICurve | The address of the bonding curve for the pair | | delta | uint128 | The delta value for the bonding curve | | fee | uint96 | The fee for the pair | | spotPrice | uint128 | The spot price for the bonding curve | | initialNFTIDs | uint256[] | The initial nft items to deposit | | rank | uint256 | The rank of the nfts | | rankProof | bytes32[] | The merkle rank proof data | ##### Return Values | Name | Type | Description | | ---- | ---- | ----------- | | newPair | contract ILSSVMPairETH | The newly created pair | | tokenId | uint256 | The newly minted token ID | - Add Liquidity 1. mintWithETH ```solidity function mintWithETH(uint256 slot, uint256[] nftIds, uint256 rank, bytes32[] rankProof) external payable returns (uint256 tokenId) ``` _To add nft items and ETH into target pair, a new token ID willd be minted with coressponding value_ ##### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | slot | uint256 | the slot of the target pair, can be queried by the pair address | | nftIds | uint256[] | The nft items to be added into target pair | | rank | uint256 | The rank of nft items | | rankProof | bytes32[] | The merkle rank proof data | ##### How to get parameters `slot:` Choose NFT at first, then use `PositionManagement.getAllNftPairs(nftAddress)` to get all pairs, then choose pair, use `PositionManagement.pairIds(pairAddress)` to get slot. `nftIds:` Query in the database to get. ##### Return Values | Name | Type | Description | | ---- | ---- | ----------- | | tokenId | uint256 | The newly minted token ID | 2. addLiquidityWithETH ```solidity function addLiquidityWithETH(uint256 tokenId, uint256[] nftIds, uint256 rank, bytes32[] rankProof) public payable ``` _To add nft items and ETH into target pair, coressponding value will be mint for the token ID_ ##### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | tokenId | uint256 | The token ID of which the value will be minted | | nftIds | uint256[] | The nft items to be added into target pair | | rank | uint256 | The rank of nft items | | rankProof | bytes32[] | The merkle rank proof data | ##### How to get parameters `tokenId:` Use `PositionManagement.balanceOf(userAddress)` to get the `amount` of all owned tokens, then use `PositionManagement.tokenOfOwnerByIndex(userAddress, amount_index)` to get `tokenId`. (To list pair to choose, use `PositionManagement.slotOf(tokenId)` to get `slot`, then use `(PositionManagement.pairIdToPairKey(slot)).nft`to get nft address) - Remove Liquidity ```solidity function removeLiquidityWithETH(uint256 tokenId, uint256[] nftIds) external ``` _To remove certain nft items from pair, corresponding value will be burned, eth value will also be tranfered to `msg.sender`_ ##### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | tokenId | uint256 | The ERC3525 token ID, value will be burned from it. | | nftIds | uint256[] | The nft IDs to be removed from pair | ### Swap Using Router(in the contract: Pair_Router) #### PairSwapAny ```solidity struct PairSwapAny { address pair; uint256 numItems; } ``` #### PairSwapSpecific ```solidity struct PairSwapSpecific { address pair; uint256[] nftIds; } ``` * SwapTokenToNFT 1. swapETHForAnyNFTs ```solidity function swapETHForAnyNFTs(struct ILSSVMRouter.PairSwapAny[] swapList, address payable ethRecipient, address nftRecipient, uint256 deadline) external payable returns (uint256 remainingValue) ``` Swaps ETH into NFTs using multiple pairs. ##### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | swapList | struct ILSSVMRouter.PairSwapAny[] | The list of pairs to trade with and the number of NFTs to buy from each. | | ethRecipient | address payable | The address that will receive the unspent ETH input | | nftRecipient | address | The address that will receive the NFT output | | deadline | uint256 | The Unix timestamp (in seconds) at/after which the swap will revert | ##### Return Values | Name | Type | Description | | ---- | ---- | ----------- | | remainingValue | uint256 | The unspent ETH amount | 2. swapETHForSpecificNFTs ```solidity function swapETHForSpecificNFTs(struct ILSSVMRouter.PairSwapSpecific[] swapList, address payable ethRecipient, address nftRecipient, uint256 deadline) external payable returns (uint256 remainingValue) ``` Swaps ETH into specific NFTs using multiple pairs. ##### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | swapList | struct ILSSVMRouter.PairSwapSpecific[] | The list of pairs to trade with and the IDs of the NFTs to buy from each. | | ethRecipient | address payable | The address that will receive the unspent ETH input | | nftRecipient | address | The address that will receive the NFT output | | deadline | uint256 | The Unix timestamp (in seconds) at/after which the swap will revert | ##### Return Values | Name | Type | Description | | ---- | ---- | ----------- | | remainingValue | uint256 | The unspent ETH amount | - SwapNFTToToken ```solidity function swapNFTsForToken(struct ILSSVMRouter.PairSwapSpecific[] swapList, uint256 minOutput, address tokenRecipient, uint256 deadline) external returns (uint256 outputAmount) ``` Swaps NFTs into ETH/ERC20 using multiple pairs. ##### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | swapList | struct ILSSVMRouter.PairSwapSpecific[] | The list of pairs to trade with and the IDs of the NFTs to sell to each. | | minOutput | uint256 | The minimum acceptable total tokens received | | tokenRecipient | address | The address that will receive the token output | | deadline | uint256 | The Unix timestamp (in seconds) at/after which the swap will revert | ##### Return Values | Name | Type | Description | | ---- | ---- | ----------- | | outputAmount | uint256 | The total tokens received | ### Swap Using Pair directly - swapTokenForAnyNFTs ```solidity function swapTokenForAnyNFTs(uint256 numNFTs, uint256 maxExpectedTokenInput, address nftRecipient, bool isRouter, address routerCaller) external payable returns (uint256 inputAmount) ``` Sends token to the pair in exchange for any `numNFTs` NFTs _To compute the amount of token to send, call bondingCurve.getBuyInfo. This swap function is meant for users who are ID agnostic_ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | numNFTs | uint256 | The number of NFTs to purchase | | maxExpectedTokenInput | uint256 | The maximum acceptable cost from the sender. If the actual amount is greater than this value, the transaction will be reverted. | | nftRecipient | address | The recipient of the NFTs | | isRouter | bool | True if calling from LSSVMRouter, false otherwise. Not used for ETH pairs. | | routerCaller | address | If isRouter is true, ERC20 tokens will be transferred from this address. Not used for ETH pairs. | #### Return Values | Name | Type | Description | | ---- | ---- | ----------- | | inputAmount | uint256 | The amount of token used for purchase | - swapTokenForSpecificNFTs ```solidity function swapTokenForSpecificNFTs(uint256[] nftIds, uint256 maxExpectedTokenInput, address nftRecipient, bool isRouter, address routerCaller) external payable returns (uint256 inputAmount) ``` Sends token to the pair in exchange for a specific set of NFTs _To compute the amount of token to send, call bondingCurve.getBuyInfo This swap is meant for users who want specific IDs. Also higher chance of reverting if some of the specified IDs leave the pool before the swap goes through._ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | nftIds | uint256[] | The list of IDs of the NFTs to purchase | | maxExpectedTokenInput | uint256 | The maximum acceptable cost from the sender. If the actual amount is greater than this value, the transaction will be reverted. | | nftRecipient | address | The recipient of the NFTs | | isRouter | bool | True if calling from LSSVMRouter, false otherwise. Not used for ETH pairs. | | routerCaller | address | If isRouter is true, ERC20 tokens will be transferred from this address. Not used for ETH pairs. | #### Return Values | Name | Type | Description | | ---- | ---- | ----------- | | inputAmount | uint256 | The amount of token used for purchase | - swapNFTsForToken ```solidity function swapNFTsForToken(uint256[] nftIds, uint256 minExpectedTokenOutput, address payable tokenRecipient, bool isRouter, address routerCaller) external returns (uint256 outputAmount) ``` Sends a set of NFTs to the pair in exchange for token _To compute the amount of token to that will be received, call bondingCurve.getSellInfo._ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | nftIds | uint256[] | The list of IDs of the NFTs to sell to the pair | | minExpectedTokenOutput | uint256 | The minimum acceptable token received by the sender. If the actual amount is less than this value, the transaction will be reverted. | | tokenRecipient | address payable | The recipient of the token output | | isRouter | bool | True if calling from LSSVMRouter, false otherwise. Not used for ETH pairs. | | routerCaller | address | If isRouter is true, ERC20 tokens will be transferred from this address. Not used for ETH pairs. | #### Return Values | Name | Type | Description | | ---- | ---- | ----------- | | outputAmount | uint256 | The amount of token received | ### Mining(in the contract: StakingPool) - Deposit ```solidity function stake(uint256 _tokenId, uint256 _amount) public ``` _To stake `amount` into staking pool from token ID MUST approve the token ID first_ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | _tokenId | uint256 | The token ID to stake from. | | _amount | uint256 | The amount will be staked and transfered | - Withdraw ```solidity function withdraw(uint256 _tokenId, uint256 _amount) public ``` _To withdraw `amount` from staking pool, the value will be transfer into token ID_ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | _tokenId | uint256 | The token ID to withdraw to. | | _amount | uint256 | The amount will be withdrawn and transfered | - exit ```solidity function exit(uint256 _tokenId) external ``` _To exit from staking pool, the total staked balance will be transfer into token ID_ ##### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | _tokenId | uint256 | The token ID to withdraw to. | - ClaimReward ```solidity function getReward(uint256 _tokenId) public ``` _To get reward for the token ID from staking pool_ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | _tokenId | uint256 | The token ID to get reward for. | - Earned ```solidity function earned(address _account, uint256 slot) public view returns (uint256) ``` _To get the amount of reward of a given account and slot_ ##### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | _account | address | The address of the account | | slot | uint256 | The slot of the reward | ##### Return Values | Name | Type | Description | | ---- | ---- | ----------- | | [0] | uint256 | the amount of reward | ### View functions #### PositionManagement - getAllNftPairs ```solidity function getAllNftPairs(address _nft) external view returns (address[] _allPairs) ``` _Returns all nft pairs created under the nft_ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | _nft | address | The address of nft to get the pairs | #### Return Values | Name | Type | Description | | ---- | ---- | ----------- | | _allPairs | address[] | The array of pair addresses | - pairs ```solidity function pairs(uint256 slot) external view returns (address pair) ``` _Returns corresponding pair of the slot_ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | slot | uint256 | The id of the slot | #### Return Values | Name | Type | Description | | ---- | ---- | ----------- | | pair | address | The addresses of the pair | #### LSSVMPair - getAllHeldIds ```solidity function getAllHeldIds() external view returns (uint256[] tokenIds) ``` _Returns all token ids hold by the pair_ #### Return Values | Name | Type | Description | | ---- | ---- | ----------- | | tokenIds | uint256[] | all token ids hold by the pair | - getSellNFTQuote ```solidity function getSellNFTQuote(uint256 numNFTs) external view returns ( CurveErrorCodes.Error error, uint256 newSpotPrice, uint256 newDelta, uint256 outputAmount, uint256 protocolFee ) ``` _Returns the sell price info for the pair_ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | numNFTs | uint256 | The number of the nfts to sell | #### Return Values | Name | Type | Description | | ---- | ---- | ----------- | | error | CurveErrorCodes.Error | The error code if any error happens | | newSpotPrice | uint256 | The new spot price for the pair | | newDelta | uint256 | The new delta for the pair | | outputAmount | uint256 | The output amount of the selling | | protocolFee | uint256 | the protocol fee of the selling | - getBuyNFTQuote ```solidity function getBuyNFTQuote(uint256 numNFTs) external view returns ( CurveErrorCodes.Error error, uint256 newSpotPrice, uint256 newDelta, uint256 inputAmount, uint256 protocolFee ) ``` _Returns the buy price info for the pair_ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | numNFTs | uint256 | The number of the nfts to buy | #### Return Values | Name | Type | Description | | ---- | ---- | ----------- | | error | CurveErrorCodes.Error | The error code if any error happens | | newSpotPrice | uint256 | The new spot price for the pair | | newDelta | uint256 | The new delta for the pair | | inputAmount | uint256 | The input amount of the buying | | protocolFee | uint256 | the protocol fee of the buying | #### ERC721Enumberator - tokensOfOwner ```solidity function tokensOfOwner(address erc721, address owner) public view returns (uint256[] memory tokenIds) ``` _Returns the token ids of `erc721` owned by `owner`_ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | erc721 | address | The address of the erc721, must support enumerable | | owner | address | The address of the owner to query | #### Return Values | Name | Type | Description | | ---- | ---- | ----------- | | tokenIds | uint256[] | The token ids owned by owner | #### StakingPool - totalSupply ```solidity function totalSupply(uint256 slot) public view returns (uint256) ``` _Returns the total staked value of `slot`_ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | slot | uint256 | The slot of the total staked value to query | #### Return Values | Name | Type | Description | | ---- | ---- | ----------- | | [0] | uint256 | the total staked value of `slot` | - balanceOf ```solidity function balanceOf(address account, uint256 slot) public view returns (uint256) ``` _Returns the staked value of `account` in `slot`_ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | account | address | The address of the account | | slot | uint256 | The slot of the total staked value to query | #### Return Values | Name | Type | Description | | ---- | ---- | ----------- | | [0] | uint256 | the staked value of `account` in `slot` | - rewardRate ```solidity function rewardRate(uint256 slot) public view returns (uint256) ``` _Returns the reward rate of `slot`_ #### Parameters | Name | Type | Description | | ---- | ---- | ----------- | | slot | uint256 | The slot of the reward rate to query | #### Return Values | Name | Type | Description | | ---- | ---- | ----------- | | [0] | uint256 | the reward rate `slot` | --- ## Architecture ### Overview ![](https://hackmd.io/_uploads/SJjzDWwdo.png) ### Use cases #### Mint new token ```mermaid sequenceDiagram actor user Note right of user: Choose collection pair to mint position user ->> PositionManager:mint(slot, nftIds, rank, rankProof) critical PositionManager-->>RankingStrategy:validateRank() if necessary PositionManager->>PositionManager:slot = getPool(collection, rank) PositionManager->>PositionManager:l = addLiquidity(collection, pair, nftIds, amount) PositionManager->>LSSVMPair:asset transfer alt Mint a new token id PositionManager->>PositionManager:ERC3525:mint(msg.sender, id, slot, l) end alt Add to a existing tokenId PositionManager->>PositionManager:ERC3525:mintValue(msg.sender, id, slot, l) end end ``` #### Add Liquidity ```mermaid sequenceDiagram actor user user ->> PositionManager: n = balanceOf(user) loop Get user's all token ids user ->> PositionManager: tokenIds = tokenOfOwnerByIndex(user, 0..n-1) end user ->> PositionManager:addLiquidity(tokenId, nftIds, rank, rankProof) critical PositionManager-->>RankingStrategy:validateRank() if necessary PositionManager->>PositionManager:slot = getPool(collection, rank) PositionManager->>PositionManager:l = addLiquidity(collection, pair, nftIds, amount) PositionManager->>LSSVMPair:asset transfer alt Mint a new token id PositionManager->>PositionManager:ERC3525:mint(msg.sender, id, slot, l) end alt Add to a existing tokenId PositionManager->>PositionManager:ERC3525:mintValue(msg.sender, id, slot, l) end end ``` #### Remove Liquidity ```mermaid sequenceDiagram actor user user ->> PositionManager: n = balanceOf(user) loop Get user's all token ids user ->> PositionManager: tokenIds = tokenOfOwnerByIndex(user, 0..n-1) end user ->> PositionManager:removeLiquidity(tokenId, nftIds) critical PositionManager->>PositionManager:slot = getPool(collection, rank) PositionManager->>PositionManager:l = removeLiquidity(collection, pair, nftIds, amount) LSSVMPair->>PositionManager:asset transfer alt Add to a existing tokenId PositionManager->>PositionManager:ERC3525:burnValue(msg.sender, id, slot, l) end end ``` #### ### Ranking Strategy - Ranking data is generated off-chain or can be obtained from third-party sources. - The on-chain module stores the ranking results in terms of [merkle tree](https://github.com/OpenZeppelin/merkle-tree) roots which leaves are the token ids of the rank, as showed below: | Collection | Rank | Merkle Root of TokenIds | |:----------:|:----:| --------------------------:| | BAYC | 1 | MerkleRoot([#1234]) | | BAYC | 2 | MerkleRoot([#1100, #1223]) | | PUNK | 1 | MerkleRoot([#9527]) | | PUNK | 2 | MerkleRoot([#12345]) | - Rank and the corresponding proof are validated when NFTs getting added, and can be added to the pool if **NFT's rank <= Pool's rank** - `RANK_LOWEST` is used if no ranking data is available for the specified collection or token id ### Liquidity #### Liquidity Provision | User | Provide ETH | Provide NFT | Pid | |:-----:|:-----------:|:-----------:| ---:| | Alice | 100 | 2 BAYC | 0 | | Alice | 100 | 10 AZUKI | 1 | | Bob | 50 | 1 BAYC | 0 | | Bob | 100 | 1 PUNK | 2 | #### ERC3525 Position layout | Token ID | Owner | Slot | Value | | --------:|:-----:| ---- | -----:| | 1 | Alice | 0 | 200 | | 2 | Alice | 1 | 1000 | | 3 | Bob | 0 | 50 | | 4 | Bob | 2 | 100 | ### Liquidity Calculation #### Add liquidity - When initial liquidity of $(𝑥_0, 𝑦_0)$ is provided into the pool $$ l_0 = y_0 $$ where $𝑥_0$ is the number of NFT where $𝑦_0$ is the amount of ETH and bound by the bonding curve $p$ $$ y_0 = \int_{-x_0}^{0} p $$ - After the initial adding liquidity $$ {\varDelta l} = \frac{\varDelta x} x \cdot l = \frac{\varDelta y} y \cdot l $$ $$ {\varDelta y} = \frac{\varDelta l} l \cdot y $$ where $x$, $y$ is the current reserve of NFT and ETH in pool and $l$ is the current liquidity in pool - Implementation ``` ``` #### Remove liquidity - When $\varDelta x$ is removed from pool $\varDelta l$ is burned $$ \varDelta l = \frac{\varDelta x} x \cdot l $$ and $\varDelta y$ is removed proportionally $$ \varDelta y = \frac{\varDelta l} l \cdot y = \frac{\varDelta x} x \cdot y $$ - Implementation Rounding errors should be taken into account ``` ``` ### Bonding Curve Currently Sudoswap supports 3 types of Bonding curve ![](https://hackmd.io/_uploads/HJSw-pfci.png) - Linear: ![](https://hackmd.io/_uploads/r12jEJd3o.png) - To Update delta from current delta $$ newSpotPrice = oldSpotPrice + delta * nftNums $$ When add liquidity: $$ newDelta = oldDelta * originalNftNums / (inputNftNums + originalNftNums) $$ - To update delta from price range $$ delta = \frac {priceUpper - spotPrice} {nftNums} $$ - Exponential: ![](https://hackmd.io/_uploads/S1QT4yd2i.png) - To update delta from current delta $$ newSpotPrice = oldSpotPrice \cdot (1 + delta) ^ {nftNums} $$ When add liquidity: $$ T = inputNftNums + originalNftNums $$ $$ newDelta = {(1+oldDelta)}^{originalNftNums/T} - 1 = 2^{originalNftNums/T \cdot \log_2 (1+oldDelta)} - 1 $$ - To update delta from price range $$ delta = \frac {priceUpper} {spotPrice} ^ {\frac 1 {nftNums}} - 1 $$ $$ = 2 ^ {{\frac 1 {nftNums}} \cdot \log_2 {\frac {priceUpper} {spotPrice}}} -1 $$ $$ delta \cdot 1e18 = {\frac {priceUpper} {spotPrice} ^ {\frac 1 {nftNums}}} \cdot 1e18 - 1e18 $$ $$ = {\frac {priceUpper \cdot 1e18 ^ {nftNum}}{spotPrice} ^ {\frac 1 {nftNums}}} - 1e18 $$ $$ = 2 ^ {{\frac 1 {nftNums}} \cdot \log_2 {\frac {priceUpper \cdot 1e18 ^ {nftNum}} {spotPrice}}} - 1e18 $$ - xyk In xyk pair, the $𝑥,y$ are the initial parameters for the pair, not necessarily the initial liquidity deposited. For example, a pair can be initialized as $$ 𝑥 = 100, y = 100 $$ and the initial liquidity can be $$ 𝑥_0 = 1, y_0 = 0.99 $$ | | NFT Changed | spotPrice | delta | upperPrice | |:------ |:----------- |:--------- |:-------:|:---------- | | Create | 0 -> !0 | record | record | record | | Add | !0 -> !0 | no change | changed | no change | | | 0 -> !0 | no change | ? | no change | | Remove | !0 -> !0 | no change | changed | no change | | | !0 -> 0 | no change | ? | no change | ### Liquidity Mining #### Basic Ideas - 1 Staking Pool distribute reward across a set of pairs/slots - The reward weight of each pair is proportional based on the volume #### Implementation The typical liquidity mining program distribute a certain amount of reward to all stakers. The reward of a staker can be calculated as: $$ R = RewardRate \cdot T \cdot {\frac l {L}} $$ For a set of pools: $$ R = \sum R_i = RewardRate_i \cdot T \cdot {\frac {l_i} {L_i}} $$ If the volume of pools are the goals of the incentive, then for a certain amount of reward should be distributed proportionally base on the volume exchanged during that peroid. $$ RewardRate_i = RewardRate \cdot {\frac {V_i} {V}} $$ The $RewardRate_i$ changes every time a swap happens in a pool as the swap changes the $V_i$ and $V$. An accumulative `accAmount` is introduced in the pool to track the volume since the pool creation, getting accumulated by the amount every time a swap taking place. The staking contract should calculate $V_i$ as: $$ V_i = accAmount_{t1} - accAmount_{t0} $$ New Changes in LSSVMPair(03-03): - uint256 public accTradeVolume; New Changes in Amy.CalcHelper(03-16): When k is equal to 0, it is the first time to add liquidity: ```javascript newNftVirtualLiquidity = currentDelta * nftAmpFactor + currentDelta newTokenVirtualLiquidity = currentSportPrice * tokenAmpFactor + currentSportPrice ``` When add/remove liquidity: ```javascript newNftVirtualLiquidity = oldNftVirtualLiquidity * newNftTotalAmount / oldNftTotalAmount newTokenVirtualLiquidity = oldTokenVirtualLiquidity * newTokenTotalAmount / oldTokenTotalAmount ``` ## Temporarily hung up(03/24/2023) **How to test:** ```bash cd Penny rm -rf ./deployments/localhost npx hardhat deploy --network localhost cd ../Amy rm -rf ./deployments/localhost cp -r ../Penny/deployments/localhost deployments/ ``` **Current progress:** [Branch feature-xyk-amp](https://github.com/dforce-network/Amy/tree/feature-xyk-amp): Use virtual liquidity for xyk bonding curve. **Excel example:** **Know issues:** - When remove all liquidity, what are the values of `spotPrice` and `delta`. - Firstly remove all liquidity, and then add liquidity, how to calculate `spotPrice` and `delta`. - In Penny, after swapping, the amount of NFT can not be zero. - Update code with the flag named `TODO:`