# NFT SWAPER [TOC] ## 典範 [NFT Trader](https://www.nfttrader.io/) [Sudo Swap](https://sudoswap.xyz/#/) ## 商務模式 - 每筆交易收取的交易手續費(從雙方收取固定的金額或是地板的%,可以設定最高收取上限值) - 如果交易有補上差價(補ETH或USDT etc.),收取交易手續費與價差的%費用 - 發行平台的NFT,NFT持有者可以免手續費交易 ## 執行內容 - 白皮書 - Norman - 使用者操作流程 - Andy - 智能合約撰寫 - CT Joe - 前端WEB JS CSS開發 -Lu Andy - 智能合約與WEB串接 -Joe - 前端UX/UI設計(OPTION) ## Session note: - 白名單交換,尚有待解決問題 - NFT交換 - 補差價交易 - 跨Standard trade - 跨Collection trade - NFT一對多trade - 顯示某錢包所有NFT, Opensea - 需不需要平台幣? 原因 - 平台公信力問題 - 合約安全性 - 交易流程: 線上媒合交易? 上不上架交易? - scenario 1: - scenario 2: - 手續費 ## Spec - 功能需求 1. 可以進行NFT交換(雙方交易者確認好交易的需求,由任一方發起交易請求,將會產生一個唯一連結傳給他方並進行交易) 2. NFT交易可以補價差 3. 可以查看所有的交易紀錄 4. 個人資料頁(OPTION) 5. 可以連結METAMASK顯示所有的NFT(OPTION) - 前端元件架構 - 非功能需求 - store設計 - 優化 - accessibility - 擴展性考量 ## 6/5會議討論 1. 雙方交易的GAS支付確認,GAS負擔原則 2. 連結錢包截圖當有NFT時的介面呈現 (https://nftx.io/rewards/dashboard) 3. 介面設計先不用太複雜,因為功能不多簡單明瞭即可 4. 先以參考sudoswap功能為主(參考以下功能流程) ![](https://i.imgur.com/4w8qq8M.png) - HOME PAGE ![](https://i.imgur.com/lluriZA.png) - CREATE SWAP ![](https://i.imgur.com/XoxppUq.png) - YOURS NFTS ![](https://i.imgur.com/MKvrprl.png) ## 流程截圖 ### NFTX ![](https://i.imgur.com/IvYcbLy.jpg) ### Sudo Swap ![](https://i.imgur.com/ghrM71R.jpg) ### NFTrader ![](https://i.imgur.com/cg8qVcD.jpg) ### 初始功能定案 2022/06/11 1. 創建交易紀錄(NFT SWAP) 2. 首頁:列出所有的交易紀錄 3. 你的NFT:顯示錢包的所有的NFT收集 4. 交換的交易紀錄(兩個TAB以下): 1.交易中的紀錄 2.交易完成的紀錄 --- ## NFTSwap Contract ```jsx= // SPDX-License-Identifier: MIT pragma solidity ^0.8.4; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; contract NFTSwap is Initializable, ERC721Upgradeable, PausableUpgradeable, OwnableUpgradeable { // Transaction State enum TransactionState { PENDING, // 0 uint256() COMPLETED //1 uint256() } // Transaction struct struct Transaction { uint transactionId; address initiator; address receiver; address contractAddress; uint256 haveTokenId; uint256 wantTokenId; uint state; bool designated; // option uint amount; // option uint256 expiredDate; // option } // mapping from address => transaction mapping(address => uint []) public ownerTransactions; // transactions list Transaction[] public transactions; // modifier isAddress(address ethereumAddress) { // string memory regex = "^0x[0-9a-f]{40}$"; // require(!ethereumAddress.matches(regex),"invalid address"); // _; // } // Check address is not zero modifier isZeroAddress(address _ethereumAddress) { require(_ethereumAddress != address(0), "Can't input zero address"); _; } // Check transaction exist modifier transactionExists(uint _transactionId) { require(_transactionId < transactions.length, "Transaction does not exist"); _; } // Check transaction state is completed modifier transactionIsCompleted(uint _transactionId) { require(!(uint(transactions[_transactionId].state) == 1), "transaction already completed"); _; } // Check is transaction receiver modifier isTransactionReceiver(uint _transactionId) { require(transactions[_transactionId].receiver == msg.sender,"You're not the transaction receiver"); _; } // Check swap address is not equal modifier notSameAddress(address _initiator, address _reciver) { require(_initiator != _reciver ,"Initiator address can't equal reciver address (Not same address in the transaction)"); _; } // Check swap token id is correct modifier isCorrectTokenIdToSwap(uint _transactionId, uint _wantTokenId) { require(transactions[_transactionId].wantTokenId == _wantTokenId,"The Swap NFT token is incorrect"); _; } // Check token of owner modifier isTokenOwner(address _contractAddress, uint256 _tokenId) { // call another contract ownerOf require(ERC721(_contractAddress).ownerOf(_tokenId) == msg.sender, "You're not token owner"); _; } // Check token of owner modifier isTokenApproved(address _contractAddress, uint256 _tokenId) { // call another contract getApproved require(ERC721(_contractAddress).getApproved(_tokenId) != address(0), "Please approve token first"); _; } // function test(address _contractAddress, uint256 _tokenId) public view returns (bool) { // return ERC721(_contractAddress).getApproved(_tokenId) != address(0); // } constructor() { _disableInitializers(); } function createTransaction( address _receiver, address _contractAddress, uint256 _haveTokenId, uint256 _wantTokenId, bool _designated, uint _amount, uint256 _expiredDate ) public isTokenOwner(_contractAddress,_haveTokenId) isTokenApproved(_contractAddress,_haveTokenId) { require(_receiver != msg.sender,"Initiator address can't equal reciver address (Not same address in the transaction)"); require(ERC721(_contractAddress).ownerOf(_haveTokenId) != ERC721(_contractAddress).ownerOf(_wantTokenId),"Can't not swap nft to same owner"); // get transaction current total count uint index = transactions.length; // push transaction data to transactions transactions.push( Transaction({ transactionId:index, initiator:msg.sender, receiver:_receiver, contractAddress:_contractAddress, haveTokenId:_haveTokenId, wantTokenId:_wantTokenId, state:uint(TransactionState.PENDING), designated:_designated, amount:_amount, expiredDate:_expiredDate }) ); // push transaction data to ownerTransactions for initiator ownerTransactions[msg.sender].push(index); // push transaction data to ownerTransactions for receiver ownerTransactions[_receiver].push(index); } function confirmTransaction( address _contractAddress, uint _transactionId, uint _wantTokenId ) public transactionExists(_transactionId) transactionIsCompleted(_transactionId) isTransactionReceiver(_transactionId) isCorrectTokenIdToSwap(_transactionId,_wantTokenId) isTokenOwner(_contractAddress,_wantTokenId) isTokenApproved(_contractAddress,_wantTokenId) { // get transaction data Transaction storage transaction = transactions[_transactionId]; // update transaction state to completed transaction.state = uint(TransactionState.COMPLETED); //excute NFT swap _excuteSwap(transaction.transactionId); } function _excuteSwap(uint _transactionId) private { // get transaction data Transaction storage transaction = transactions[_transactionId]; // initiator transfer to receiver haveTokenId ERC721(transaction.contractAddress).transferFrom( transaction.initiator, transaction.receiver, transaction.haveTokenId ); // receiver transfer to initiator wantTokenId ERC721(transaction.contractAddress).transferFrom( transaction.receiver, transaction.initiator, transaction.wantTokenId ); } function getOwnerTransactions(address ownerAddress) public view returns (uint[] memory) { return ownerTransactions[ownerAddress]; } function getTransactionCount() public view returns (uint) { return transactions.length; } function getTransaction(uint _transactionId) public view transactionExists(_transactionId) returns( uint transactionId, address initiator, address receiver, address contractAddress, uint256 haveTokenId, uint256 wantTokenId, uint256 state, bool designated, uint amount, uint256 expiredDate ) { Transaction storage transaction = transactions[_transactionId]; return ( transaction.transactionId, transaction.initiator, transaction.receiver, transaction.contractAddress, transaction.haveTokenId, transaction.wantTokenId, transaction.state, transaction.designated, transaction.amount, transaction.expiredDate ); } function initialize() initializer public { __ERC721_init("NFTSWAP", "NFTS"); __Pausable_init(); __Ownable_init(); } function pause() public onlyOwner { _pause(); } function unpause() public onlyOwner { _unpause(); } function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal whenNotPaused override { super._beforeTokenTransfer(from, to, tokenId); } } ``` ## Write function - createTransaction - confirmTransaction - ## Read function - getOwnerTransactions - getTransactionCount - getTransaction ## initialize? ## _beforeTokenTransfer - revert ? - check pause? ## 6/14 Meeting minutes ### Contract - add revoke->**CT** - approval debug->**CT** - transaction record need jason.array for FE calling->**Andy** - create NFT projects with enough metadata->**Joe** ### FE - optimization ->**Lu** ### Whitepaper - Flow modification->**Norman** ## 6/15 Feedback - 寫test contract - 確認NFT和ETH是否還在,在confirm交換前 ## 6/16(四) 19:00 專題會議 - 進度確認與討論事項 1. 透過前端Approve交換的ERC721 TOKEN 2. 交易明細與清單取得 2.1 取得所有交易編號-(PS:透過promise all方式去跑所有交易明細欄位) 2.2 取得持有者發起或被指定的交換者交易編號 2.3 透過ID取得交易明細欄 3. 部署NFTSWAP合約,並進行前段串接(PS:基本的『SWAP』功能以及要可以『REVOKE』交易) 4. NFTSWAP進階功能 4.1 補ETH 4.2 加入交易到期日 4.3 可以指定或不指定交換者 5. *進行交換判斷事項*: 5.1 雙方都APPROVE了,最後進行CONFIRM進行交換時,需要再次判斷雙邊交易者是否還持有TOKEN以及足夠的ETH 6. 白皮書的內容太單薄要補強(CT提出可以以ROADMAP形式呈現,這部分需要由) * 以上為今日會議確認與討論內容,希望今天的會議有初步可以進行流程的DEMO,甚至看能否直接部署到RINKEBY直接走一次測試流程。 ```jsx= // add transactions of owner ownerTransactions[msg.sender].push(Id); ownerTransactions[_receiver].push(Id); // transactions of owner mapping(address => uint[]) public ownerTransactions; // Get transactions of owner. function getOwnerTransactions(address ownerAddress) public view returns (uint[] memory) { return ownerTransactions[ownerAddress]; } // Get transaction count. function getTransaction(uint id) public view returns ( uint256 transactionId, address requestor, address receiver, uint256 myToken, uint256 wantToken, uint256 state ) { Transaction storage transaction = transactions[id]; return ( transaction.transactionId, transaction.requestor, transaction.receiver, transaction.myToken, transaction.wantToken, transaction.state ); } // Get transaction count. function getTransactionCount() public view returns (uint) { return transactions.length; } ``` ## Whitepaper - Introduction - Team Background - Website Tutorial - Roadmap - 1st Stage: Platform, ERC721 token swap fuction - 2nd Stage: ERC721 trading with ETH - 3rd Stage: Launch platform NFT (utility:swap fee discount.airdrop Swaper$) - 4th Stahe: Launch platform token-Swaper$ -20%:Team -40%:Treasury -10%:Airdrop -30%:IDO(Initial DEX Offering) ## 6/18 Pre-Demo FeedBack - 可展示目前市場NFT交易需求 (Opensea交易量/場外交易需求) - 平台Demo流程優化(預錄影片?) - 有哪些優勢功能, 可讓手續費提高但仍就有需求 - 要怎麼找到對家讓需求浮現 - Demo 預錄影片