Closed peterpolman closed 2 years ago
@peterpolman
the most popular dexes swap contracts use the uniswap contract as router to manage the liquidity pools. Also Quick swap which is based on Polygon is based on Uniswap Protocol. We could build up a prototype to test if that protocol works with our pools.
The market places like for example the most popular Opensea, give to the customer the opportunity to list their NFT for the sell. There are many configuration that the customer can choose:
Besides the popular market place is also possible to build a custom market place. Depend on complexity, if we adopt the solution to use built up apis, there are some options: TATUM: (this is very popular) https://apidoc.tatum.io/tag/NFT-(ERC-721-or-compatible)#operation/NftTransferErc721 https://tatum.io/pricing
Moralis Web3 https://moralis.io/nft-api/ https://moralis.io/pricing/
QuickNode https://www.quicknode.com/nft-api https://www.quicknode.com/pricing
All those library support the most used blockchain, included polygon.
Regarding the openzeppelin part, yes there is the escrow contract but is very basic. https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/escrow/Escrow.sol What would purpose for this? That I didn't get :)
@valeriagrazzini Functionality for those use cases is clear to me, so in general it was required to have more insight into how what actual code is implemented to support these functionalities and if we can learn a thing or two about that.
I have provided some feedback on this through Discord; please extend your findings over here and try to not spend too much time on it. I'd like to start development for this early next week.
@peterpolman I have looked into the quick swap smart contracts repo. The contracts are the exact copy of the uniswap v2 contracts. So the swap mechanism is based on the liquidity pools amount transfer and balancing. The swap is executed by 3 principal functions:
function swapExactTokensForTokens( uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline ) external virtual override ensure(deadline) returns (uint[] memory amounts) { amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path); require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'); TransferHelper.safeTransferFrom(path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]); _swap(amounts, path, to); }
Which I suppose is the one that fits on our requirements. What is crucial in this function is that there is a first transfer of the swap amount to the liquidity of the first token in the path.
// SWAP // requires the initial amount to have already been sent to the first pair function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual { for (uint i; i < path.length - 1; i++) { (address input, address output) = (path[i], path[i + 1]); (address token0,) = UniswapV2Library.sortTokens(input, output); uint amountOut = amounts[i + 1]; (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0)); address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to; IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap( amount0Out, amount1Out, to, new bytes(0) ); } }
this is the function that prepares the and checks the amount of the swap to be passed to the core swap function.
What is difficult to me to understand is the value of the last argument (new bytes(0)) passed to the core swap function
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
uint balance0;
uint balance1;
{ // scope for _token{0,1}, avoids stack too deep errors
address _token0 = token0;
address _token1 = token1;
require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
}
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
{ // scope for reserve{0,1}Adjusted, avoids stack too deep errors
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
}
_update(balance0, balance1, _reserve0, _reserve1);
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}
this is the function when the magic happens:
the expected output amount is transferred to the sender ad there is an adjustment of the pool balances, also the is a verification at the and to maintain the correct ratio in the pool
Usecases:
Alice transfers ownership of a minted NFT for an x amount of ERC20 tokens
Bob launched a new ERC20 token contract and allows the token holders of his old ERC20 to swap them for the new ones
Charlie mints NFT's for an x amount of ERC20 tokens
[ ] See if Open Zeppelin has contract standards available for these use cases (Escrow or conditional escrow?)
[ ] Research how DEXes swap tokens securely
[ ] Research how marketplaces sell NFT's
[ ] Research how NFT projects mint NFTs after a payment is made
[ ] Research if we can make use of existing protocols or API's for this