代码库 https://github.com/LooksRare/contracts-exchange-v1
contracts-exchange-v1 是LooksRare的链上交易合约其实它所实现的功能和opensea非常类似,但是代码的条理比opensea清晰许多。opensea有初始化钱包功能它没有 不过他又一个策略定制的功能。
代码结构
IExecutionStrategy 策略接口
interface IExecutionStrategy {
function canExecuteTakerAsk(OrderTypes.TakerOrder calldata takerAsk, OrderTypes.MakerOrder calldata makerBid)
external
view
returns (
bool,
uint256,
uint256
);
function canExecuteTakerBid(OrderTypes.TakerOrder calldata takerBid, OrderTypes.MakerOrder calldata makerAsk)
external
view
returns (
bool,
uint256,
uint256
);
function viewProtocolFee() external view returns (uint256);
}
executionStrategies 策略目录
1 -> StrategyAnyItemFromCollectionForFixedPrice 指定合集任意nft的固定价格
2 -> StrategyAnyItemInASetForFixedPrice 指定的n个nft的任意一个的固定价格
3 -> StrategyDutchAuction 降价拍卖
4 -> StrategyPrivateSale 指定买卖方
5 -> StrategyStandardSaleForFixedPrice 指定nft固定价格
CurrencyManager -> 支付代币管理器 只有添加白名单的代币可以用来交易
RoyaltyFeeManager -> 版费管理器
transferManagers -> nft转移的不同实现
1 -> TransferManagerERC721
2 -> TransferManagerERC1155
3 -> TransferManagerNonCompliantERC721
TransferSelectorNFT -> nft转移的管理器 可以针对不同的合集采用不同的安全转账实现
ExecutionManager -> 执行策略管理器 针对上面的策略做白名单增减,校验
LooksRareExchange -> 交易合约 签名采用的是eip712
构造函数
constructor(
address _currencyManager,
address _executionManager,
address _royaltyFeeManager,
address _WETH,
address _protocolFeeRecipient
) {
// Calculate the domain separator
DOMAIN_SEPARATOR = keccak256(
abi.encode(
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f, // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
0xda9101ba92939daf4bb2e18cd5f942363b9297fbc3232c9dd964abb1fb70ed71, // keccak256("LooksRareExchange")
0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6, // keccak256(bytes("1")) for versionId = 1
block.chainid,
address(this)
)
);
currencyManager = ICurrencyManager(_currencyManager); //设置 支付代币管理器
executionManager = IExecutionManager(_executionManager); //设置 执行策略管理器
royaltyFeeManager = IRoyaltyFeeManager(_royaltyFeeManager); 设置 版费管理器
WETH = _WETH;
protocolFeeRecipient = _protocolFeeRecipient; //交易费的接收地址
}
修改各种管理器
/**
* @notice Update currency manager
* @param _currencyManager new currency manager address
*/
function updateCurrencyManager(address _currencyManager) external onlyOwner {
require(_currencyManager != address(0), "Owner: Cannot be null address");
currencyManager = ICurrencyManager(_currencyManager);
emit NewCurrencyManager(_currencyManager);
}
/**
* @notice Update execution manager
* @param _executionManager new execution manager address
*/
function updateExecutionManager(address _executionManager) external onlyOwner {
require(_executionManager != address(0), "Owner: Cannot be null address");
executionManager = IExecutionManager(_executionManager);
emit NewExecutionManager(_executionManager);
}
/**
* @notice Update protocol fee and recipient
* @param _protocolFeeRecipient new recipient for protocol fees
*/
function updateProtocolFeeRecipient(address _protocolFeeRecipient) external onlyOwner {
protocolFeeRecipient = _protocolFeeRecipient;
emit NewProtocolFeeRecipient(_protocolFeeRecipient);
}
/**
* @notice Update royalty fee manager
* @param _royaltyFeeManager new fee manager address
*/
function updateRoyaltyFeeManager(address _royaltyFeeManager) external onlyOwner {
require(_royaltyFeeManager != address(0), "Owner: Cannot be null address");
royaltyFeeManager = IRoyaltyFeeManager(_royaltyFeeManager);
emit NewRoyaltyFeeManager(_royaltyFeeManager);
}
/**
* @notice Update transfer selector NFT
* @param _transferSelectorNFT new transfer selector address
*/
function updateTransferSelectorNFT(address _transferSelectorNFT) external onlyOwner {
require(_transferSelectorNFT != address(0), "Owner: Cannot be null address");
transferSelectorNFT = ITransferSelectorNFT(_transferSelectorNFT);
emit NewTransferSelectorNFT(_transferSelectorNFT);
}
在挂单时指定策略地址
event TakerAsk(
bytes32 orderHash, // bid hash of the maker order
uint256 orderNonce, // user order nonce
address indexed taker, // sender address for the taker ask order
address indexed maker, // maker address of the initial bid order
address indexed strategy, // strategy that defines the execution //策略地址
address currency, // currency address
address collection, // collection address
uint256 tokenId, // tokenId transferred
uint256 amount, // amount of tokens transferred
uint256 price // final transacted price
);
event TakerBid(
bytes32 orderHash, // ask hash of the maker order
uint256 orderNonce, // user order nonce
address indexed taker, // sender address for the taker bid order
address indexed maker, // maker address of the initial ask order
address indexed strategy, // strategy that defines the execution //策略地址
address currency, // currency address
address collection, // collection address
uint256 tokenId, // tokenId transferred
uint256 amount, // amount of tokens transferred
uint256 price // final transacted price
);
matchAskWithTakerBidUsingETHAndWETH //挂单
function matchAskWithTakerBidUsingETHAndWETH(
OrderTypes.TakerOrder calldata takerBid,
OrderTypes.MakerOrder calldata makerAsk
) external payable override nonReentrant {
require((makerAsk.isOrderAsk) && (!takerBid.isOrderAsk), "Order: Wrong sides");
require(makerAsk.currency == WETH, "Order: Currency must be WETH");
require(msg.sender == takerBid.taker, "Order: Taker must be the sender");
// If not enough ETH to cover the price, use WETH
if (takerBid.price > msg.value) {
IERC20(WETH).safeTransferFrom(msg.sender, address(this), (takerBid.price - msg.value));
} else {
require(takerBid.price == msg.value, "Order: Msg.value too high");
}
// Wrap ETH sent to this contract
IWETH(WETH).deposit{value: msg.value}();
// Check the maker ask order
bytes32 askHash = makerAsk.hash();
_validateOrder(makerAsk, askHash); //校验
// Retrieve execution parameters
(bool isExecutionValid, uint256 tokenId, uint256 amount) = IExecutionStrategy(makerAsk.strategy)
.canExecuteTakerBid(takerBid, makerAsk); //策略校验是否符合
require(isExecutionValid, "Strategy: Execution invalid");
// Update maker ask order status to true (prevents replay)
_isUserOrderNonceExecutedOrCancelled[makerAsk.signer][makerAsk.nonce] = true; //设置ask的这笔交易已执行
// Execution part 1/2 //支付
_transferFeesAndFundsWithWETH(
makerAsk.strategy,
makerAsk.collection,
tokenId,
makerAsk.signer,
takerBid.price,
makerAsk.minPercentageToAsk
);
// Execution part 2/2
_transferNonFungibleToken(makerAsk.collection, makerAsk.signer, takerBid.taker, tokenId, amount); //转移nft
emit TakerBid(
askHash,
makerAsk.nonce,
takerBid.taker,
makerAsk.signer,
makerAsk.strategy,
makerAsk.currency,
makerAsk.collection,
tokenId,
amount,
takerBid.price
);
}
matchAskWithTakerBid //挂单
function matchAskWithTakerBid(OrderTypes.TakerOrder calldata takerBid, OrderTypes.MakerOrder calldata makerAsk)
external
override
nonReentrant
{
require((makerAsk.isOrderAsk) && (!takerBid.isOrderAsk), "Order: Wrong sides");
require(msg.sender == takerBid.taker, "Order: Taker must be the sender");
// Check the maker ask order
bytes32 askHash = makerAsk.hash();
_validateOrder(makerAsk, askHash);
(bool isExecutionValid, uint256 tokenId, uint256 amount) = IExecutionStrategy(makerAsk.strategy)
.canExecuteTakerBid(takerBid, makerAsk);
require(isExecutionValid, "Strategy: Execution invalid");
// Update maker ask order status to true (prevents replay)
_isUserOrderNonceExecutedOrCancelled[makerAsk.signer][makerAsk.nonce] = true;
// Execution part 1/2
_transferFeesAndFunds(
makerAsk.strategy,
makerAsk.collection,
tokenId,
makerAsk.currency,
msg.sender,
makerAsk.signer,
takerBid.price,
makerAsk.minPercentageToAsk
);
// Execution part 2/2
_transferNonFungibleToken(makerAsk.collection, makerAsk.signer, takerBid.taker, tokenId, amount);
emit TakerBid(
askHash,
makerAsk.nonce,
takerBid.taker,
makerAsk.signer,
makerAsk.strategy,
makerAsk.currency,
makerAsk.collection,
tokenId,
amount,
takerBid.price
);
}
matchBidWithTakerAsk //询价
function matchBidWithTakerAsk(OrderTypes.TakerOrder calldata takerAsk, OrderTypes.MakerOrder calldata makerBid)
external
override
nonReentrant
{
require((!makerBid.isOrderAsk) && (takerAsk.isOrderAsk), "Order: Wrong sides");
require(msg.sender == takerAsk.taker, "Order: Taker must be the sender");
// Check the maker bid order
bytes32 bidHash = makerBid.hash();
_validateOrder(makerBid, bidHash);
(bool isExecutionValid, uint256 tokenId, uint256 amount) = IExecutionStrategy(makerBid.strategy)
.canExecuteTakerAsk(takerAsk, makerBid);
require(isExecutionValid, "Strategy: Execution invalid");
// Update maker bid order status to true (prevents replay)
_isUserOrderNonceExecutedOrCancelled[makerBid.signer][makerBid.nonce] = true;
// Execution part 1/2
_transferNonFungibleToken(makerBid.collection, msg.sender, makerBid.signer, tokenId, amount);
// Execution part 2/2
_transferFeesAndFunds(
makerBid.strategy,
makerBid.collection,
tokenId,
makerBid.currency,
makerBid.signer,
takerAsk.taker,
takerAsk.price,
takerAsk.minPercentageToAsk
);
emit TakerAsk(
bidHash,
makerBid.nonce,
takerAsk.taker,
makerBid.signer,
makerBid.strategy,
makerBid.currency,
makerBid.collection,
tokenId,
amount,
takerAsk.price
);
}
付款
function _transferFeesAndFunds(
address strategy,
address collection,
uint256 tokenId,
address currency,
address from,
address to,
uint256 amount,
uint256 minPercentageToAsk
) internal {
// Initialize the final amount that is transferred to seller
uint256 finalSellerAmount = amount;
// 1. Protocol fee
{
uint256 protocolFeeAmount = _calculateProtocolFee(strategy, amount); //计算手续费
// Check if the protocol fee is different than 0 for this strategy
if ((protocolFeeRecipient != address(0)) && (protocolFeeAmount != 0)) {
IERC20(currency).safeTransferFrom(from, protocolFeeRecipient, protocolFeeAmount);
finalSellerAmount -= protocolFeeAmount;
}
}
// 2. Royalty fee
{
(address royaltyFeeRecipient, uint256 royaltyFeeAmount) = royaltyFeeManager
.calculateRoyaltyFeeAndGetRecipient(collection, tokenId, amount);
// Check if there is a royalty fee and that it is different to 0
if ((royaltyFeeRecipient != address(0)) && (royaltyFeeAmount != 0)) {
IERC20(currency).safeTransferFrom(from, royaltyFeeRecipient, royaltyFeeAmount);
finalSellerAmount -= royaltyFeeAmount;
emit RoyaltyPayment(collection, tokenId, royaltyFeeRecipient, currency, royaltyFeeAmount);
}
}
require((finalSellerAmount * 10000) >= (minPercentageToAsk * amount), "Fees: Higher than expected");
// 3. Transfer final amount (post-fees) to seller
{
IERC20(currency).safeTransferFrom(from, to, finalSellerAmount);
}
}
转移nft
function _transferNonFungibleToken(
address collection,
address from,
address to,
uint256 tokenId,
uint256 amount
) internal {
// Retrieve the transfer manager address
address transferManager = transferSelectorNFT.checkTransferManagerForToken(collection); //根据不同的合集选择不同的安全转移合约
// If no transfer manager found, it returns address(0)
require(transferManager != address(0), "Transfer: No NFT transfer manager available");
// If one is found, transfer the token
ITransferManagerNFT(transferManager).transferNonFungibleToken(collection, from, to, tokenId, amount);
}
校验订单
function _validateOrder(OrderTypes.MakerOrder calldata makerOrder, bytes32 orderHash) internal view {
// Verify whether order nonce has expired
require(
(!_isUserOrderNonceExecutedOrCancelled[makerOrder.signer][makerOrder.nonce]) &&
(makerOrder.nonce >= userMinOrderNonce[makerOrder.signer]),
"Order: Matching order expired"
); //是否已经执行或者已经取消或者已经过期
// Verify the signer is not address(0)
require(makerOrder.signer != address(0), "Order: Invalid signer");
// Verify the amount is not 0
require(makerOrder.amount > 0, "Order: Amount cannot be 0");
// Verify the validity of the signature
require(
SignatureChecker.verify(
orderHash,
makerOrder.signer,
makerOrder.v,
makerOrder.r,
makerOrder.s,
DOMAIN_SEPARATOR
),
"Signature: Invalid"
); //检查签名
// Verify whether the currency is whitelisted
require(currencyManager.isCurrencyWhitelisted(makerOrder.currency), "Currency: Not whitelisted");
// Verify whether strategy can be executed
require(executionManager.isStrategyWhitelisted(makerOrder.strategy), "Strategy: Not whitelisted");
}