# 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功能為主(參考以下功能流程)

- HOME PAGE

- CREATE SWAP

- YOURS NFTS

## 流程截圖
### NFTX

### Sudo Swap

### NFTrader

### 初始功能定案 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 預錄影片