# LooksRare平台架构与核心流程剖析 **Published by:** [Webber Woo](https://paragraph.com/@webber-woo/) **Published on:** 2022-07-31 **URL:** https://paragraph.com/@webber-woo/looksrare ## Content 0. 交易场景 0.1 术语 一单完整的交易必须存在2种角色:maker和taker。 maker :账本深度的制造者。 taker:账本深度的移除者。在DEX中它扮演者最重要角色,它永远是交易的推动者。 交易中还有2种行为:ask和bid ask:卖方出价,用NFT换取token。 bid:买方出价,用token购买NFT。 以上两两组和之后就会形成一下的成对交易方式: MakerAsk -- TakerBid:买家出售NFT以换取相应的Token,卖家拿Token换取NFT。 MakerBid --TakerAsk:买家以token申购NFT,卖家接受买家报价并执行交易和兑换。 0.2 释例 交易场景 case_1: User1拥有两个NFT,他对NFT1有卖出意向,于是在交易所中进行了挂单。 User2对NFT1感兴趣,用手里的token购买了NFT1。 User2是交易的实际发起者,他将负担交易的gas fee。 User1--maker,他的行为是ask。 User2--taker,他的行为是bid。 case_2: User1当前并未打算出售NFT2,所以他并未对其挂单。 User2对NFT2非常着迷,尝试对NFT1进行报价,用token求购之。 NFT2难以拒绝的报价让User1决定接受之。 User1是交易的实际发起者,他将负担交易的gas fee。 User1--taker,他的行为是ask。 User2--maker,他的行为是bid。 1.LooksRare领域模型 1.1 子域划分 子域划分 LooksRare总体来说分为三个核心子域,分别是 支付域 负责合法的交易token管理。 负责NFT交易的转账管理。 版权域 负责Collection版权费、平台佣金和用户的收益分成管理。 执行域 负责执行动作和执行策略的实施。 1.2 平台架构 平台架构 Looksrare交易部分当前由四大组件构成:货币管理器、NFT选择器、执行管理器、版权费管理器。 1.2.1 支付域 货币管理器 货币管理器主要功能是在Looksrare上可以进行交易的合法货币币种进行增加/移除/查询等操作。能执行该操作的人限定是Looksrare平台方。 NFT选择器 NFT选择器主要的职能是选择正确的接口选择器(ERC721,ERC1155)调用safeTransferFrom方法。LooksRare针对非标准的NFT Collection也提供了相应的适配,不过它底层调用的是transferFrom方法存在一定安全风险。 1.2.2 版权域 版权域主要的职责是管理各个Collection的版权费计算器和设置相应的费率条件。 版费设置器 为Collection的Owner提供设置/修改Collection管理员、版费接收账户和版费,这构成了「版费对象」。 版费注册器 它的首要职能将collection与设置的自定义版费对象关联起来。其次它还负责运行时计算对应的版费。 个人觉得这里设计上还有优化空间。还应该增加一个版费计算器接口并实现之。这样注册和计算解耦,也为外部合作者提供了自定义版费计算方式的生态玩法。 版费管理器 它仅仅充当版权域的「聚合根」作用。 1.2.3 执行域 执行管理器 ask和bid的执行动作有不同的执行策略。执行管理器主要负责执行策略的增加/移除查询。官方实现的执行策略如下有: StrategyStandardSaleForFixedPrice StrategyPrivateSale StrategyDutchAuction StrategyAnyItemInASetForFixedPrice StrategyAnyItemFromCollectionForFixedPrice 1.3 实体 1.3.1 Maker订单 struct MakerOrder { bool isOrderAsk; // true --> ask【卖】 / false --> bid【买】 address signer; // signer of the maker order address collection; // collection address uint256 price; // price (used as ) uint256 tokenId; // id of the token uint256 amount; // amount of tokens to sell/purchase (must be 1 for ERC721, 1+ for ERC1155) address strategy; // strategy for trade execution (e.g., DutchAuction, StandardSaleForFixedPrice) address currency; // currency (e.g., WETH) uint256 nonce; // order nonce (must be unique unless new maker order is meant to override existing one e.g., lower ask price) uint256 startTime; // startTime in timestamp uint256 endTime; // endTime in timestamp uint256 minPercentageToAsk; // slippage protection (9000 --> 90% of the final price must return to ask) bytes params; // additional parameters uint8 v; // v: parameter (27 or 28) bytes32 r; // r: parameter bytes32 s; // s: parameter } 1.3.2 Taker订单 struct TakerOrder { bool isOrderAsk; // true --> ask【买】 / false --> bid【卖】 address taker; // msg.sender uint256 price; // final price for the purchase uint256 tokenId; uint256 minPercentageToAsk; // // slippage protection (9000 --> 90% of the final price must return to ask) bytes params; // other params (e.g., tokenId) } 2.核心流程 2.1 matchAskWithTakerBidUsingETHAndWETH 2.1.1 入参校验 必须满足以下条件: maker是挂单者,售卖NFT;taker是吃单者,用token买NFT。 maker接受的货币类型必须是WETH。 调用方是taker。 2.1.2 付款金额校验 付款金额不得大于NFT售价。 如果付款金额小于NFT售价,则尝试用msg.sender账户的WETH转移到该合约WETH账户。 2.1.3 ETH统一转换为WETH存入合约WETH账户 2.1.4 Maker订单信息校验 订单信息校验又可以分为以下步骤: a.基础校验 maker的该订单没有被执行过 maker的nonce不低于合约中user的最小执行nonce maker发起者不是address(0) maker的出售数额>0 b.maker签名校验 这里使用https://eips.ethereum.org/EIPS/eip-712 规范进行校验。 如果是调用方是合约,验证是否接口满足InterfaceSelector。 如果调用方是外部地址,则从hash中恢复出signer',判断外部传入signer == singer' 。 c.交易货币合法性校验 d.交易策略合法性校验 2.1.5 执行参数校验 使用maker.strategy的地址所代表出售策略执行 IExecutionStrategy.canExecuteTakerBid(TakerOrder taker, MakerOrder maker),其中可以被maker事先选择执行的合法策略有: StrategyStandardSaleForFixedPrice (固定价格销售) { return ( ((makerAsk.price == takerBid.price) && (makerAsk.tokenId == takerBid.tokenId) && (makerAsk.startTime <= block.timestamp) && (makerAsk.endTime >= block.timestamp)), makerAsk.tokenId, makerAsk.amount ); } StrategyPrivateSale(售卖给指定用户) { // 从maker签名中反解出指定售卖人 address targetBuyer = abi.decode(makerAsk.params, (address)); return ( ((targetBuyer == takerBid.taker) && //msg.sender必须为buyer才能进行下去 (makerAsk.price == takerBid.price) && (makerAsk.tokenId == takerBid.tokenId) && (makerAsk.startTime <= block.timestamp) && (makerAsk.endTime >= block.timestamp)), makerAsk.tokenId, makerAsk.amount ); } StrategyDutchAuction(荷兰式拍卖) { uint256 startPrice = abi.decode(makerAsk.params, (uint256)); //maker设定的起拍价 uint256 endPrice = makerAsk.price; //maker设定的底价 uint256 startTime = makerAsk.startTime; //maker设定的拍卖起止时间 uint256 endTime = makerAsk.endTime; require(endTime >= (startTime + minimumAuctionLengthInSeconds), "Dutch Auction: Length must be longer"); require(startPrice > endPrice, "Dutch Auction: Start price must be greater than end price"); //随着时间流逝,当前拍卖价格会逐渐接近于maker的底价 uint256 currentAuctionPrice = startPrice - (((startPrice - endPrice) * (block.timestamp - startTime)) / (endTime - startTime)); return ( (startTime <= block.timestamp) && //拍卖时间内才运行成交 (endTime >= block.timestamp) && (takerBid.price >= currentAuctionPrice) && //只要买家出价不小于当前拍卖喊价即可 (takerBid.tokenId == makerAsk.tokenId), makerAsk.tokenId, makerAsk.amount ); } 2.1.6 费用结算 费用结算主要是对taker的付款金额按照协议费、版权费清算后再将剩余款项转给卖家。 1)协议费结算 协议费结算也与maker选定的执行策略先关,主要是使用 IExecutionStrategy.viewProtocolFee(),其中合法的策略如【2.1.5】阐述所示,其中对应的协议率均为各自contract部署的时候设置,且无修改接口。 协议费的计算公式是 协议费 = 交易金额 * 协议费率 协议费以WETH的形式转入交易所协议费收款账户protocolFeeRecipient,剩下的钱继续后续结算。from账户是合约WETH账户。 2)版权费结算 版权费的结算“费基” 也是原始交易金额。 版权费是结算给对应Collection方,并且各个Collection的版权费收款地址和费率各不相同。这些版权费信息被封装在一个个FeeInfo对象中,并且Collection与FeeInfo的映射关系在RoyaltyFeeRegistry的_royaltyFeeInfoCollection存放。 struct FeeInfo { address setter; //版权费设置人 address receiver;//版权费收款人 uint256 fee;//版权费率 } LooksRare未遵循ERC-2918协议,接口采用royaltyInfo(addresscollection, uint256amount) returns (address, uint256),返回合约版权费收款账户和版权费。 协议费的计算公式是 版权费 = 交易金额 * 版权费率 *如果Collection未在LooksRare设置版权费信息,则会尝试调用Collection实现的ERC2981协议计算版权费。 3)卖家结算 卖家收款金额 = 交易金额 - 协议费 - 版权费 卖家的结算也会以WETH的形式从交易所合约WETH账户转入。 2.1.7 NFT结算 1)选择转让管理器TransferManager 在之前被注册到交易所上的transferSelectorNFT中根据Collection地址寻找合适的转让管理器。Collection与transferManger地址的映射关系存放于transferManagerSelectorForCollection中。 如果Collection没有绑定对应的TransferManager,LooksRare会根据Collection实现的接口进行判断,如果实现了ERC721,则用TRANSFER_MANAGER_ERC721,如果实现ERC1155,则用INTERFACE_ID_ERC1155。 2)根据不同的transferManager,实际调用的是标准的IERC721/1155的safeTransferFrom方法。 2.2 matchAskWithTakerBid 与【2.1】重要区别是付款方式不再是ETH或WETH,而是另外一些ERC20。 2.2.1 基本参数校验 必须满足以下条件: maker是挂单者,售卖nft;taker是吃单者,用token买nft。 调用方是taker。 2.2.2 费用结算 费用结算与【2.1.6】的协议费、版权费和卖家结算金额计算方式相同,唯一不同之处是采用maker认定的货币类型进行结算。 IERC20(currency).safeTransferFrom(from, protocolFeeRecipient, protocolFeeAmount); 2.3 matchBidWithTakerAsk 2.3.1 基本参数校验 maker是挂单者,用token买nft;taker是吃单者,nft换token。 taker是调用者 2.3.2 maker订单信息校验 与之前相同。 2.3.3 执行参数校验 使用maker.strategy的地址所代表申购策略执行。 IExecutionStrategy.canExecuteTakerAsk(TakerOrder taker, MakerOrder maker),其中可以被maker事先选择执行的合法策略有: StrategyStandardSaleForFixedPrice (固定价格申购) { return ( ((makerBid.price == takerAsk.price) && (makerBid.endTime >= block.timestamp) && (makerBid.startTime <= block.timestamp)), takerAsk.tokenId, makerBid.amount ); } StrategyAnyItemInASetForFixedPrice(固定价格购买指定集合中的任意Item) { // Precomputed merkleRoot (that contains the tokenIds that match a common characteristic) bytes32 merkleRoot = abi.decode(makerBid.params, (bytes32)); // MerkleProof + indexInTree + tokenId bytes32[] memory merkleProof = abi.decode(takerAsk.params, (bytes32[])); // Compute the node bytes32 node = keccak256(abi.encodePacked(takerAsk.tokenId)); // Return whether the order can be executed, the tokenId, and the amount to sell return ( (MerkleProof.verify(merkleProof, merkleRoot, node)&& (makerBid.price == takerAsk.price) && (makerBid.endTime >= block.timestamp) && (makerBid.startTime <= block.timestamp)), takerAsk.tokenId,//这里体现出任意,即Set持有者选一个给maker makerBid.amount ); } StrategyAnyItemFromCollectionForFixedPrice(固定架构购买指定Collection中任意item) { return ( ((makerBid.price == takerAsk.price) && (makerBid.endTime >= block.timestamp) && (makerBid.startTime <= block.timestamp)), takerAsk.tokenId,//这里体现出任意,即Collection持有者选一个给maker makerBid.amount ); } 2.3.4 NFT结算 与前面不同的是,这里的gas费是由NFT所有人支付。 2.3.5 费用结算 strategy:申购者选定的策略 currency:申购者选定的货币类型 amount:卖家确定的售价 1)协议费结算 与【2.1.6】中的协议费结算类似。 协议费计算公式依然是, 协议费 = 交易金额 * 协议费率 所以,不管交易是由买家还是卖家推动,LooksRare交易所的协议费收费方式都一致。 2)版权费结算 与【2.1.6】中的协议费结算类似。 版权费计算公式依然是, 版权费 = 交易金额 * 版权费率 所以,不管交易是由买家还是卖家推动,Collection版权费收费方式都一致。 3)卖家结算 与【2.1.6】中的结算方式一致。 卖家收款金额 = 交易金额 - 协议费 - 版权费 当然货币类似是taker指定的货币类型。 3. 总结 LooksRare的代码框架还是非常整洁,代码逻辑清晰,分工职责合理。同样这套架构框架,稍微改改即可变成「NFT租赁平台」。 LooksRare最大的吸引点是「挂单奖励」,与租赁市场相结合后会擦出怎样的火花我们拭目以待。 4. 参考文档 https://docs.looksrare.org/developers/looksrare-exchange-overview https://github.com/LooksRare/contracts-exchange-v1 ## Publication Information - [Webber Woo](https://paragraph.com/@webber-woo/): Publication homepage - [All Posts](https://paragraph.com/@webber-woo/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@webber-woo): Subscribe to updates