# 从代码层面聊聊 PartyDao **Published by:** [Smithereens](https://paragraph.com/@soyoung/) **Published on:** 2022-07-08 **URL:** https://paragraph.com/@soyoung/partydao ## Content 今天来聊聊一个神奇的项目 PartyDAO,这个项目最近融资1640万美元,估值两亿美金,其来源于推特的一番对话,具体可以参考超哥的一篇文章:PartyDAO的故事,对话的后续是 Mirror 的创始人 Denis Nazarov 在推特上提出了一个想法并讲了基本的流程,我把整个逻辑翻译下如下: 如果部署自动一个 DAO 来竞标任何 NFT 拍卖,那不是很酷吗?给它起名 PartyBid,它包含以下功能:构造函数:auction_id任何人存入 ETH,取回 ERC20合同自动投标拍卖如果获胜,资助者将获得 NFT 的部分所有权如果输了,资金退回这个想法被一个程序员大佬 _anishagnihotri 看到了,于是他就在一个周末把这个功能实现了,在这里膜拜大佬,我们这里就看看整个代码是怎样实现的,首先大佬先把 Denis Nazarov 提出的想法扩展了下,想法毕竟是想法不是完整的 PM 需求,日常开发如果 PM 提出了这么个不完整的需求肯定是会挨程序员的骂的,但这里 anishagnihotri 显然是对这个想法感兴趣的,所以就把需求完善了下并描述了下该合约是如何工作的:首先部署一个 PartyBidRA.sol 合约,构造函数有四个参数 (_ReserveAuctionV3Address, _auctionID, _bidAmount, _exitTimeout),其中 _ReserveAuctionV3Address 我们随后再讲,_auctionID 对应的是某 NFT 的 TokenId,bidAmount 对应的是出价,这里应该是 ETH 计价,exitTimeout 过期时间。接下来,个人可以调用 join() 方法成为 DAO 的成员,发送他们的 ETH 来积累后续投标拍卖的资金。于是后续会产生三种可能性:这个 DAO 在 exitTimeout 时间之前参与人过少,筹集的 ETH 没有达到预订的 bidAmount, 此前参与的用户可以调用合约的 exit() 方法来提取他们投资的 ETH。如果达到了预订的 bidAmount,DAO 成员可以调用 placeBid() 进行出价。如果 DAO 未能成功赢得 NFT,DAO 成员可以调用 exit() 来提取他们的资金。筹集的资金达到了预订的 bidAmount,并且最终出价成功买入了该 NFT,后续会有如下的可能操作:DAO 成员可以调用 DAOProposeZoraBid() 方法发起一个新的提案:在 Zora 市场将该 NFT 进行出价卖出。DAO 成员可以调用 DAOVoteForZoraBidProposal() 对上述已提出的提案进行投票,提案需要超过 50% 的选票才能被执行。DAO 成员可以调用 DAOExecuteZoraBid() 来执行一个提案,该提案:(1) 拥有超过 50% 的 DAO 投票权,并且 (2) 自投票发起以来没有改变(以防止不良行为者在投票期间改变他们的出价) 投票过程)。 这会将 NFT 转移给出价者,并接受他们的资金到合约中。最后,一旦成功转售,DAO 成员可以调用 exit() 来收回他们在投标金额中的份额。至此,整个功能的描述就完成了,当然代码也是如此完成的。上述整个流程对于不在这个圈子里面的人来说有点难以理解,这里我举个例子去描述下: 小明想买一套房子,该房子房主目前出价 100 万,小明买不起,于是发起了一个众筹,一伙人看到了也觉得这个房子100万价格合适,于是纷纷加入,最终10个人加入了,每人出资10万元,成功将该房子买入了,但这个区域是学区房,房价过了一段时间还在涨,一直涨到了150左右,其中小张坐不住了,说咱们把房子卖出去吧,于是就发起了一个提案说咱们以 150 万的价格卖出,众人觉得可以,纷纷投票赞同,投票数超过5个人的时候,小张就执行了该提议,把房子卖给了出价 150万 的那个人,于是最后每个人欢欢喜喜的拿走了自己的15万。 整个流程其实和上述描述的大差不差,唯一和现实不同的是整个操作流程是在链上完成的,都是公开透明的,资金也都是按照个人初始投资进行均分的。 OK,基本流程已经说完了,我们看下代码: 首先说下 _ReserveAuctionV3Address 这个是拍卖合约的地址,placeBid() 方法其实针对的是某一个拍卖发起的报价,所以这个流程执行的过程依赖拍卖方的,创建拍卖的方法如下: function createAuction( uint256 tokenId, uint256 duration, uint256 reservePrice, uint8 curatorFeePercent, address curator, address payable fundsRecipient ) external nonReentrant whenNotPaused auctionNonExistant(tokenId) { // Check basic input requirements are reasonable. require(curator != address(0)); require(fundsRecipient != address(0)); require(curatorFeePercent < 100, "Curator fee should be < 100"); // 初始化拍卖结构体信息 auctions[tokenId] = Auction({ duration: duration, reservePrice: reservePrice, curatorFeePercent: curatorFeePercent, curator: curator, fundsRecipient: fundsRecipient, amount: 0, firstBidTime: 0, bidder: address(0) }); // Transfer the NFT into this auction contract, from whoever owns it. // 将要拍卖的 NFT 转移到拍卖合约 IERC721(nftContract).transferFrom( IERC721(nftContract).ownerOf(tokenId), address(this), tokenId ); } 当一个拍卖发起后,才可以执行 placeBid 进行叫价,最终到达拍卖结束时间后叫价最高的账户将赢得拍卖,结束拍卖的方法如下: function endAuction(uint256 tokenId) external nonReentrant whenNotPaused auctionComplete(tokenId) { // Store relevant auction data in memory for the life of this function. address winner = auctions[tokenId].bidder; uint256 amount = auctions[tokenId].amount; address curator = auctions[tokenId].curator; uint8 curatorFeePercent = auctions[tokenId].curatorFeePercent; address payable fundsRecipient = auctions[tokenId].fundsRecipient; // Remove all auction data for this token from storage. delete auctions[tokenId]; // We don't use safeTransferFrom, to prevent reverts at this point, // which would break the auction. // 赢的拍卖的获取到该 NFT IERC721(nftContract).transferFrom(address(this), winner, tokenId); // First handle the curator's fee. if (curatorFeePercent > 0) { // Determine the curator amount, which is some percent of the total. uint256 curatorAmount = amount.mul(curatorFeePercent).div(100); // Send it to the curator. transferETHOrWETH(curator, curatorAmount); // Subtract the curator amount from the total funds available // to send to the funds recipient and original NFT creator. amount = amount.sub(curatorAmount); // Emit the details of the transfer as an event. emit CuratorFeePercentTransfer(tokenId, curator, curatorAmount); } // Get the address of the original creator, so that we can split shares // if appropriate. address payable nftCreator = payable( address(IMediaModified(nftContract).tokenCreators(tokenId)) ); // If the creator and the recipient of the funds are the same // (and we expect this to be common), we can just do one transaction. if (nftCreator == fundsRecipient) { transferETHOrWETH(nftCreator, amount); } else { // Otherwise, we should determine the percent that goes to the creator. // Collect share data from Zora. uint256 creatorAmount = // Call the splitShare function on the market contract, which // takes in a Decimal and an amount. IMarket(IMediaModified(nftContract).marketContract()) .splitShare( // Fetch the decimal from the BidShares data on the market. IMarket(IMediaModified(nftContract).marketContract()) .bidSharesForToken(tokenId) .creator, // Specify the amount. amount ); // 分红给 nftCreator transferETHOrWETH(nftCreator, creatorAmount); // 转移剩下的资金给拍卖方 transferETHOrWETH(fundsRecipient, amount.sub(creatorAmount)); } } 最核心的代码就是上面两部分了,完整的代码可以这里查看partybid初始代码。但你会发现和官网的操作并不一样,因为这份代码是最原始的版本,随着项目的进展,他们后续引入了 PM 以及有合约开发自荐进行迭代开发,新合约代码地址。所以你会发现,现在 partybid官网 的操作是没有拍卖的,因为后续是接入了类似 Opensea 市场直接进行买入,简化了拍卖的这一步,侧重点放到了众筹买入这个功能,当然有时间也可以积极的去官网进行参与下,说不定后面有空投惊喜呢。 ## Publication Information - [Smithereens](https://paragraph.com/@soyoung/): Publication homepage - [All Posts](https://paragraph.com/@soyoung/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@soyoung): Subscribe to updates - [Twitter](https://twitter.com/stoneworld1991): Follow on Twitter