代码库 https://github.com/ProjectWyvern/wyvern-ethereum(不在opensea的仓库下,所以之前没有找到)
如果要在opensea卖nft 首先需要初始化钱包那什么是初始化钱包呢?
初始化钱包我理解就是帮你针对opensea 创建一个合约账户
调用 ProxyRegistry 合约的 registerProxy()方法就创建了,ProxyRegistry是使用opensea用户的合约账户的管理合约
function registerProxy()
public
returns (OwnableDelegateProxy proxy)
{
require(proxies[msg.sender] == address(0));
// new 一个钱包。这个钱包合约是个可升级的代理合约,
proxy = new OwnableDelegateProxy(msg.sender, delegateProxyImplementation, abi.encodeWithSignature("initialize(address,address)", msg.sender, address(this)));
//保存到mapping 以 所有的的地址作为索引的key
proxies[msg.sender] = proxy;
return proxy;
}
合约账户的初始化
contract OwnableDelegateProxy is OwnedUpgradeabilityProxy {
constructor(address owner, address initialImplementation, bytes calldata)
public
{
setUpgradeabilityOwner(owner);
_upgradeTo(initialImplementation);
require(initialImplementation.delegatecall(calldata));
}
}
OwnableDelegateProxy合约账户的代理合约(存储) 它继承至OwnedUpgradeabilityProxy -> Proxy, OwnedUpgradeabilityStorage,这个合约主要是代理合约的逻辑升级,和代理调用的逻辑 ,用户是可以修改这个合约的实现的
AuthenticatedProxy 这个合约是合约账户的逻辑合约 也就是OwnableDelegateProxy的逻辑功能。他继承了TokenRecipient,OwnedUpgradeabilityStorage。这个合约主要有三个方法
1 初始化
//这个方法是 new OwnableDelegateProxy 时调用的设置了所属用户 和管理合约 这两个值时可以修改的 1是在proxy 方法delegatecall修改,还有就是升级逻辑合约
function initialize (address addrUser, ProxyRegistry addrRegistry)
public
{
require(!initialized);
initialized = true;
user = addrUser;
registry = addrRegistry;
}
2取消opensea的代理权限
function setRevoke(bool revoke)
public
{
require(msg.sender == user);
revoked = revoke;
emit Revoked(revoke);
3 代理
function proxy(address dest, HowToCall howToCall, bytes calldata)
public
returns (bool result)
{
require(msg.sender == user || (!revoked && registry.contracts(msg.sender))); //setRevoke 后就只有用户自己可以调用
if (howToCall == HowToCall.Call) {
result = dest.call(calldata);
} else if (howToCall == HowToCall.DelegateCall) {
result = dest.delegatecall(calldata);
}
return result;
}
4
function proxyAssert(address dest, HowToCall howToCall, bytes calldata)
public
{
require(proxy(dest, howToCall, calldata));
}
TokenTransferProxy -> 专门转账的合约
contract TokenTransferProxy {
/* Authentication registry. */
ProxyRegistry public registry;
/**
* Call ERC20 `transferFrom`
*
* @dev Authenticated contract only
* @param token ERC20 token address
* @param from From address
* @param to To address
* @param amount Transfer amount
*/
function transferFrom(address token, address from, address to, uint amount)
public
returns (bool)
{
require(registry.contracts(msg.sender));
return ERC20(token).transferFrom(from, to, amount);
}
}
opensea的具体交易功能(以v2为列)
WyvernExchangeWithBulkCancellations -> Exchange ->ExchangeCore
WyvernExchangeWithBulkCancellations 就一个构造方法
constructor (ProxyRegistry registryAddress, TokenTransferProxy tokenTransferProxyAddress, ERC20 tokenAddress, address protocolFeeAddress) public {
registry = registryAddress; //合约账户的管理合约
tokenTransferProxy = tokenTransferProxyAddress; //负责转账的
exchangeToken = tokenAddress; // The token used to pay exchange fees 交手续费的地址
protocolFeeRecipient = protocolFeeAddress; //接受手续费的地址 opensea的版税是有这个地址先同意收取,再分合集分发的
owner = msg.sender;
}
Exchange 主要是对 ExchangeCore的内部部调用。主要逻实现在ExchangeCore 两个方法除外 这三个也是给链下提供查询继续
1
function guardedArrayReplace(bytes array, bytes desired, bytes mask)
public
pure
returns (bytes)
{
ArrayUtils.guardedArrayReplace(array, desired, mask);
return array;
}
2,
计算价格 有两种交易类型 1.定价,2-卖家价格随时间减少 买-价格随时间增加
function calculateFinalPrice(SaleKindInterface.Side side, SaleKindInterface.SaleKind saleKind, uint basePrice, uint extra, uint listingTime, uint expirationTime)
public
view
returns (uint)
{
return SaleKindInterface.calculateFinalPrice(side, saleKind, basePrice, extra, listingTime, expirationTime);
}
function calculateFinalPrice(Side side, SaleKind saleKind, uint basePrice, uint extra, uint listingTime, uint expirationTime)
view
internal
returns (uint finalPrice)
{
if (saleKind == SaleKind.FixedPrice) {
return basePrice;
} else if (saleKind == SaleKind.DutchAuction) {//extra 价格变化的乘数 也就是速率
uint diff = SafeMath.div(SafeMath.mul(extra, SafeMath.sub(now, listingTime)), SafeMath.sub(expirationTime, listingTime));
if (side == Side.Sell) {
/* Sell-side - start price: basePrice. End price: basePrice - extra. */
return SafeMath.sub(basePrice, diff);
} else {
/* Buy-side - start price: basePrice. End price: basePrice + extra. */
return SafeMath.add(basePrice, diff);
}
}
}
3
//两个订单是否匹配 这部分不是特别理解
function orderCalldataCanMatch(bytes buyCalldata, bytes buyReplacementPattern, bytes sellCalldata, bytes sellReplacementPattern)
public
pure
returns (bool)
{
if (buyReplacementPattern.length > 0) {
ArrayUtils.guardedArrayReplace(buyCalldata, sellCalldata, buyReplacementPattern);
}
if (sellReplacementPattern.length > 0) {
ArrayUtils.guardedArrayReplace(sellCalldata, buyCalldata, sellReplacementPattern);
}
return ArrayUtils.arrayEq(buyCalldata, sellCalldata);
}
ExchangeCore
1 构造方法
constructor () public {
// 主要时eip712的设置
require(keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") == _EIP_712_DOMAIN_TYPEHASH);
require(keccak256(bytes(name)) == _NAME_HASH);
require(keccak256(bytes(version)) == _VERSION_HASH);
require(keccak256("Order(address exchange,address maker,address taker,uint256 makerRelayerFee,uint256 takerRelayerFee,uint256 makerProtocolFee,uint256 takerProtocolFee,address feeRecipient,uint8 feeMethod,uint8 side,uint8 saleKind,address target,uint8 howToCall,bytes calldata,bytes replacementPattern,address staticTarget,bytes staticExtradata,address paymentToken,uint256 basePrice,uint256 extra,uint256 listingTime,uint256 expirationTime,uint256 salt,uint256 nonce)") == _ORDER_TYPEHASH);
require(DOMAIN_SEPARATOR == _deriveDomainSeparator()); //自己部署时要修改DOMAIN_SEPARATOR
}
2.approvedOrders //手动approve 后 不需要签名sig就能交易
function approveOrder(Order memory order, bool orderbookInclusionDesired)
internal
{
/* CHECKS */
/* Assert sender is authorized to approve order. */
require(msg.sender == order.maker); //谁挂的单
/* Calculate order hash. */
bytes32 hash = hashToSign(order, nonces[order.maker]); //签名的hash
/* Assert order has not already been approved. */
require(_approvedOrdersByNonce[hash] == 0);
/* EFFECTS */
/* Mark order as approved. */
_approvedOrdersByNonce[hash] = nonces[order.maker] + 1;
/* Log approval event. Must be split in two due to Solidity stack size limitations. */
{
emit OrderApprovedPartOne(hash, order.exchange, order.maker, order.taker, order.makerRelayerFee, order.takerRelayerFee, order.makerProtocolFee, order.takerProtocolFee, order.feeRecipient, order.feeMethod, order.side, order.saleKind, order.target);
}
{
emit OrderApprovedPartTwo(hash, order.howToCall, order.calldata, order.replacementPattern, order.staticTarget, order.staticExtradata, order.paymentToken, order.basePrice, order.extra, order.listingTime, order.expirationTime, order.salt, orderbookInclusionDesired);
}
}
hashToSign
function hashToSign(Order memory order, uint nonce)
internal
pure
returns (bytes32)
{
return keccak256(
abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hashOrder(order, nonce))
);
}
function hashOrder(Order memory order, uint nonce)
internal
pure
returns (bytes32 hash)
{
/* Unfortunately abi.encodePacked doesn't work here, stack size constraints. */
uint size = 800;
bytes memory array = new bytes(size);
uint index;
assembly {
index := add(array, 0x20)
}
index = ArrayUtils.unsafeWriteBytes32(index, _ORDER_TYPEHASH);
index = ArrayUtils.unsafeWriteAddressWord(index, order.exchange);
index = ArrayUtils.unsafeWriteAddressWord(index, order.maker);
index = ArrayUtils.unsafeWriteAddressWord(index, order.taker);
index = ArrayUtils.unsafeWriteUint(index, order.makerRelayerFee);
index = ArrayUtils.unsafeWriteUint(index, order.takerRelayerFee);
index = ArrayUtils.unsafeWriteUint(index, order.makerProtocolFee);
index = ArrayUtils.unsafeWriteUint(index, order.takerProtocolFee);
index = ArrayUtils.unsafeWriteAddressWord(index, order.feeRecipient);
index = ArrayUtils.unsafeWriteUint8Word(index, uint8(order.feeMethod));
index = ArrayUtils.unsafeWriteUint8Word(index, uint8(order.side));
index = ArrayUtils.unsafeWriteUint8Word(index, uint8(order.saleKind));
index = ArrayUtils.unsafeWriteAddressWord(index, order.target);
index = ArrayUtils.unsafeWriteUint8Word(index, uint8(order.howToCall));
index = ArrayUtils.unsafeWriteBytes32(index, keccak256(order.calldata));
index = ArrayUtils.unsafeWriteBytes32(index, keccak256(order.replacementPattern));//类型为bytes的进行的hash 与 metamask - Sign Typed Data v4 对应
index = ArrayUtils.unsafeWriteAddressWord(index, order.staticTarget);
index = ArrayUtils.unsafeWriteBytes32(index, keccak256(order.staticExtradata));
index = ArrayUtils.unsafeWriteAddressWord(index, order.paymentToken);
index = ArrayUtils.unsafeWriteUint(index, order.basePrice);
index = ArrayUtils.unsafeWriteUint(index, order.extra);
index = ArrayUtils.unsafeWriteUint(index, order.listingTime);
index = ArrayUtils.unsafeWriteUint(index, order.expirationTime);
index = ArrayUtils.unsafeWriteUint(index, order.salt);
index = ArrayUtils.unsafeWriteUint(index, nonce);
//这部分应该等同于abi.encode()
assembly {
hash := keccak256(add(array, 0x20), size)
}
return hash;
}
取消订单
function cancelOrder(Order memory order, Sig memory sig, uint nonce)
internal
{
/* CHECKS */
/* Calculate order hash. */
bytes32 hash = requireValidOrder(order, sig, nonce); //验证签名 - 我认为取消是没有必要验证签名 只要验证参数就行
/* Assert sender is authorized to cancel order. */
require(msg.sender == order.maker); //只有自己才能取消自己的挂单
/* EFFECTS */
/* Mark order as cancelled, preventing it from being matched. */
cancelledOrFinalized[hash] = true;//把订单设置为取消或者以交易
/* Log cancel event. */
emit OrderCancelled(hash);
}
function requireValidOrder(Order memory order, Sig memory sig, uint nonce)
internal
view
returns (bytes32)
{
bytes32 hash = hashToSign(order, nonce); //获取签名的hash
require(validateOrder(hash, order, sig));
return hash;
}
function validateOrder(bytes32 hash, Order memory order, Sig memory sig)
internal
view
returns (bool)
{
/* Not done in an if-conditional to prevent unnecessary ecrecover evaluation, which seems to happen even though it should short-circuit. */
/* Order must have valid parameters. */
if (!validateOrderParameters(order)) {//参数验证
return false;
}
/* Order must have not been canceled or already filled. */
if (cancelledOrFinalized[hash]) { //订单是否已经取消或者成交
return false;
}
/* Return true if order has been previously approved with the current nonce */
uint approvedOrderNoncePlusOne = _approvedOrdersByNonce[hash];
if (approvedOrderNoncePlusOne != 0) { //订单之前已经手动approve就直接返回true 不需要再验证签名
return approvedOrderNoncePlusOne == nonces[order.maker] + 1;
}
//验证签名
/* Prevent signature malleability and non-standard v values. */
if (uint256(sig.s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return false;
}
if (sig.v != 27 && sig.v != 28) {
return false;
}
/* recover via ECDSA, signed by maker (already verified as non-zero). */
if (ecrecover(hash, sig.v, sig.r, sig.s) == order.maker) {
return true;
}
// 如果是合约
/* fallback — attempt EIP-1271 isValidSignature check. */
return _tryContractSignature(order.maker, hash, sig);
}
//验证参数
function validateOrderParameters(Order memory order)
internal
view
returns (bool)
{
/* Order must be targeted at this protocol version (this Exchange contract). */
if (order.exchange != address(this)) {
return false;
}
/* Order must have a maker. */
if (order.maker == address(0)) {
return false;
}
/* Order must possess valid sale kind parameter combination. */
if (!SaleKindInterface.validateParameters(order.saleKind, order.expirationTime)) {
return false;
}
/* If using the split fee method, order must have sufficient protocol fees. */
if (order.feeMethod == FeeMethod.SplitFee && (order.makerProtocolFee < minimumMakerProtocolFee || order.takerProtocolFee < minimumTakerProtocolFee)) {
return false;
}
return true;
}
atomicMatch 交易
function atomicMatch(Order memory buy, Sig memory buySig, Order memory sell, Sig memory sellSig, bytes32 metadata)
internal
reentrancyGuard
{
/* CHECKS */
/* Ensure buy order validity and calculate hash if necessary. */
bytes32 buyHash;
if (buy.maker == msg.sender) { //如果maker是合约调用者就不验证签名
require(validateOrderParameters(buy));
} else {
buyHash = _requireValidOrderWithNonce(buy, buySig);
}
/* Ensure sell order validity and calculate hash if necessary. */
bytes32 sellHash;
if (sell.maker == msg.sender) {
require(validateOrderParameters(sell));
} else {
sellHash = _requireValidOrderWithNonce(sell, sellSig);
}
/* Must be matchable. */
require(ordersCanMatch(buy, sell));//订单是否匹配
/* Target must exist (prevent malicious selfdestructs just prior to order settlement). */
uint size;
address target = sell.target;
assembly {
size := extcodesize(target)
}
require(size > 0);
/* Must match calldata after replacement, if specified. */
if (buy.replacementPattern.length > 0) {
ArrayUtils.guardedArrayReplace(buy.calldata, sell.calldata, buy.replacementPattern);
}
if (sell.replacementPattern.length > 0) {
ArrayUtils.guardedArrayReplace(sell.calldata, buy.calldata, sell.replacementPattern);
}
require(ArrayUtils.arrayEq(buy.calldata, sell.calldata)); //买方要买的 与卖方要卖的是同样的东西。
/* Retrieve delegateProxy contract. */
OwnableDelegateProxy delegateProxy = registry.proxies(sell.maker); //获取卖方的合约账户
/* Proxy must exist. */
require(delegateProxy != address(0)); //只有初始钱包才能卖
/* Access the passthrough AuthenticatedProxy. */
AuthenticatedProxy proxy = AuthenticatedProxy(delegateProxy); //逻辑合约实例化
/* EFFECTS */
/* Mark previously signed or approved orders as finalized. */
if (msg.sender != buy.maker) {
cancelledOrFinalized[buyHash] = true;
}
if (msg.sender != sell.maker) {
cancelledOrFinalized[sellHash] = true;
}
/* INTERACTIONS */
/* Execute funds transfer and pay fees. */
uint price = executeFundsTransfer(buy, sell); //交钱,
/* Assert implementation. */
require(delegateProxy.implementation() == registry.delegateProxyImplementation()); //用户没有修改合约账户的实现
/* Execute specified call through proxy. */ //用户挂单卖货是nft只是授权给自己的合约账户 这也是很多nft合约再检查授权的时候把这个 地址也做了放行 就是说再opensea挂单卖的时候不需要额外授权
require(proxy.proxy(sell.target, sell.howToCall, sell.calldata)); //卖货 这里是通过用户的合约账户转的nft,由于有时候需要批量转 所以这里就还涉及一个批量call的合约
/* Static calls are intentionally done after the effectful call so they can check resulting state. */
/* Handle buy-side static call if specified. */
if (buy.staticTarget != address(0)) {
require(staticCall(buy.staticTarget, sell.calldata, buy.staticExtradata)); //这里可以做收货校验
}
/* Handle sell-side static call if specified. */
if (sell.staticTarget != address(0)) {
require(staticCall(sell.staticTarget, sell.calldata, sell.staticExtradata)); //这里可以做收钱校验
}
/* Log match event. */
emit OrdersMatched(buyHash, sellHash, sell.feeRecipient != address(0) ? sell.maker : buy.maker, sell.feeRecipient != address(0) ? buy.maker : sell.maker, price, metadata);
}
检查订单是否匹配
function ordersCanMatch(Order memory buy, Order memory sell)
internal
view
returns (bool)
{
return (
/* Must be opposite-side. */
(buy.side == SaleKindInterface.Side.Buy && sell.side == SaleKindInterface.Side.Sell) &&///买卖方
/* Must use same fee method. */
(buy.feeMethod == sell.feeMethod) && //手续费类型一致
/* Must use same payment token. */
(buy.paymentToken == sell.paymentToken) && //支付方式匹配
/* Must match maker/taker addresses. */
(sell.taker == address(0) || sell.taker == buy.maker) &&//是否符合买(卖)对卖(买)方的需求
(buy.taker == address(0) || buy.taker == sell.maker) &&
/* One must be maker and the other must be taker (no bool XOR in Solidity). */
((sell.feeRecipient == address(0) && buy.feeRecipient != address(0)) || (sell.feeRecipient != address(0) && buy.feeRecipient == address(0))) &&//两方必须有一方且只有一方设置了feeRecipient 地址
/* Must match target. */
(buy.target == sell.target) && //
/* Must match howToCall. */
(buy.howToCall == sell.howToCall) && //
/* Buy-side order must be settleable. */
SaleKindInterface.canSettleOrder(buy.listingTime, buy.expirationTime) &&
/* Sell-side order must be settleable. */
SaleKindInterface.canSettleOrder(sell.listingTime, sell.expirationTime)
);
}
转账
function executeFundsTransfer(Order memory buy, Order memory sell)
internal
returns (uint)
{
/* Only payable in the special case of unwrapped Ether. */
if (sell.paymentToken != address(0)) {//address(0) 代表eth
require(msg.value == 0);
}
/* Calculate match price. */
uint price = calculateMatchPrice(buy, sell); //计算成交价格
/* If paying using a token (not Ether), transfer tokens. This is done prior to fee payments to that a seller will have tokens before being charged fees. */
if (price > 0 && sell.paymentToken != address(0)) {
transferTokens(sell.paymentToken, buy.maker, sell.maker, price); //付钱给卖家
}
/* Amount that will be received by seller (for Ether). */
uint receiveAmount = price;
/* Amount that must be sent by buyer (for Ether). */
uint requiredAmount = price;
/* Determine maker/taker and charge fees accordingly. */
if (sell.feeRecipient != address(0)) {
/* Sell-side order is maker. */ //只有执行操作的是买家 才能实现eth付款
/* Assert taker fee is less than or equal to maximum fee specified by buyer. */
require(sell.takerRelayerFee <= buy.takerRelayerFee);
if (sell.feeMethod == FeeMethod.SplitFee) {
/* Assert taker fee is less than or equal to maximum fee specified by buyer. */
require(sell.takerProtocolFee <= buy.takerProtocolFee);
/* Maker fees are deducted from the token amount that the maker receives. Taker fees are extra tokens that must be paid by the taker. */
if (sell.makerRelayerFee > 0) {
uint makerRelayerFee = SafeMath.div(SafeMath.mul(sell.makerRelayerFee, price), INVERSE_BASIS_POINT);
if (sell.paymentToken == address(0)) {
receiveAmount = SafeMath.sub(receiveAmount, makerRelayerFee);
sell.feeRecipient.transfer(makerRelayerFee);
} else {
transferTokens(sell.paymentToken, sell.maker, sell.feeRecipient, makerRelayerFee);
}
}
if (sell.takerRelayerFee > 0) {
uint takerRelayerFee = SafeMath.div(SafeMath.mul(sell.takerRelayerFee, price), INVERSE_BASIS_POINT);
if (sell.paymentToken == address(0)) {
requiredAmount = SafeMath.add(requiredAmount, takerRelayerFee);
sell.feeRecipient.transfer(takerRelayerFee);
} else {
transferTokens(sell.paymentToken, buy.maker, sell.feeRecipient, takerRelayerFee);
}
}
if (sell.makerProtocolFee > 0) {
uint makerProtocolFee = SafeMath.div(SafeMath.mul(sell.makerProtocolFee, price), INVERSE_BASIS_POINT);
if (sell.paymentToken == address(0)) {
receiveAmount = SafeMath.sub(receiveAmount, makerProtocolFee);
protocolFeeRecipient.transfer(makerProtocolFee);
} else {
transferTokens(sell.paymentToken, sell.maker, protocolFeeRecipient, makerProtocolFee);
}
}
if (sell.takerProtocolFee > 0) {
uint takerProtocolFee = SafeMath.div(SafeMath.mul(sell.takerProtocolFee, price), INVERSE_BASIS_POINT);
if (sell.paymentToken == address(0)) {
requiredAmount = SafeMath.add(requiredAmount, takerProtocolFee);
protocolFeeRecipient.transfer(takerProtocolFee);
} else {
transferTokens(sell.paymentToken, buy.maker, protocolFeeRecipient, takerProtocolFee);
}
}
} else {
/* Charge maker fee to seller. */
chargeProtocolFee(sell.maker, sell.feeRecipient, sell.makerRelayerFee);
/* Charge taker fee to buyer. */
chargeProtocolFee(buy.maker, sell.feeRecipient, sell.takerRelayerFee);
}
} else {
/* Buy-side order is maker. */
/* Assert taker fee is less than or equal to maximum fee specified by seller. */
require(buy.takerRelayerFee <= sell.takerRelayerFee);
if (sell.feeMethod == FeeMethod.SplitFee) {
/* The Exchange does not escrow Ether, so direct Ether can only be used to with sell-side maker / buy-side taker orders. */
require(sell.paymentToken != address(0));
/* Assert taker fee is less than or equal to maximum fee specified by seller. */
require(buy.takerProtocolFee <= sell.takerProtocolFee);
if (buy.makerRelayerFee > 0) {
makerRelayerFee = SafeMath.div(SafeMath.mul(buy.makerRelayerFee, price), INVERSE_BASIS_POINT);
transferTokens(sell.paymentToken, buy.maker, buy.feeRecipient, makerRelayerFee);
}
if (buy.takerRelayerFee > 0) {
takerRelayerFee = SafeMath.div(SafeMath.mul(buy.takerRelayerFee, price), INVERSE_BASIS_POINT);
transferTokens(sell.paymentToken, sell.maker, buy.feeRecipient, takerRelayerFee);
}
if (buy.makerProtocolFee > 0) {
makerProtocolFee = SafeMath.div(SafeMath.mul(buy.makerProtocolFee, price), INVERSE_BASIS_POINT);
transferTokens(sell.paymentToken, buy.maker, protocolFeeRecipient, makerProtocolFee);
}
if (buy.takerProtocolFee > 0) {
takerProtocolFee = SafeMath.div(SafeMath.mul(buy.takerProtocolFee, price), INVERSE_BASIS_POINT);
transferTokens(sell.paymentToken, sell.maker, protocolFeeRecipient, takerProtocolFee);
}
} else {
/* Charge maker fee to buyer. */
chargeProtocolFee(buy.maker, buy.feeRecipient, buy.makerRelayerFee);
/* Charge taker fee to seller. */
chargeProtocolFee(sell.maker, buy.feeRecipient, buy.takerRelayerFee);
}
}
if (sell.paymentToken == address(0)) { //是eth
/* Special-case Ether, order must be matched by buyer. */
require(msg.value >= requiredAmount);
sell.maker.transfer(receiveAmount); //付钱给卖家
/* Allow overshoot for variable-price auctions, refund difference. */
uint diff = SafeMath.sub(msg.value, requiredAmount);
if (diff > 0) {
buy.maker.transfer(diff); //多了退
}
}
/* This contract should never hold Ether, however, we cannot assert this, since it is impossible to prevent anyone from sending Ether e.g. with selfdestruct. */
return price;
}
function transferTokens(address token, address from, address to, uint amount)
internal
{
if (amount > 0) {
require(tokenTransferProxy.transferFrom(token, from, to, amount)); //交易前授权应该是收给转账合约
}
}
计算价格
function calculateMatchPrice(Order memory buy, Order memory sell)
view
internal
returns (uint)
{
/* Calculate sell price. */
uint sellPrice = SaleKindInterface.calculateFinalPrice(sell.side, sell.saleKind, sell.basePrice, sell.extra, sell.listingTime, sell.expirationTime);
/* Calculate buy price. */
uint buyPrice = SaleKindInterface.calculateFinalPrice(buy.side, buy.saleKind, buy.basePrice, buy.extra, buy.listingTime, buy.expirationTime);
/* Require price cross. */
require(buyPrice >= sellPrice);
/* Maker/taker priority. */
return sell.feeRecipient != address(0) ? sellPrice : buyPrice;
}
手续费这一块有点不太明白
require(proxy.proxy(sell.target, sell.howToCall, sell.calldata))
单个执行的 有个MerkleValidator 合约 主网地址:0xBAf2127B49fC93CbcA6269FAdE0F7F31dF4c88a7
/**
*Submitted for verification at Etherscan.io on 2022-02-02
*/
pragma solidity 0.8.11;
interface IERC721 {
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function transferFrom(address from, address to, uint256 tokenId) external;
}
interface IERC1155 {
function safeTransferFrom(address from, address to, uint256 tokenId, uint256 amount, bytes calldata data) external;
}
/// @title MerkleValidator enables matching trait-based and collection-based orders for ERC721 and ERC1155 tokens.
/// @author 0age
/// @dev This contract is intended to be called during atomicMatch_ via DELEGATECALL.
contract MerkleValidator {
/// @dev InvalidProof is thrown on invalid proofs.
error InvalidProof();
/// @dev UnnecessaryProof is thrown in cases where a proof is supplied without a valid root to match against (root = 0)
error UnnecessaryProof();
/// @dev Match an ERC721 order, ensuring that the supplied proof demonstrates inclusion of the tokenId in the associated merkle root.
/// @param from The account to transfer the ERC721 token from — this token must first be approved on the seller's AuthenticatedProxy contract.
/// @param to The account to transfer the ERC721 token to.
/// @param token The ERC721 token to transfer.
/// @param tokenId The ERC721 tokenId to transfer.
/// @param root A merkle root derived from each valid tokenId — set to 0 to indicate a collection-level or tokenId-specific order.
/// @param proof A proof that the supplied tokenId is contained within the associated merkle root. Must be length 0 if root is not set.
/// @return A boolean indicating a successful match and transfer.
function matchERC721UsingCriteria(
address from,
address to,
IERC721 token,
uint256 tokenId,
bytes32 root,
bytes32[] calldata proof
) external returns (bool) {
// Proof verification is performed when there's a non-zero root.
if (root != bytes32(0)) {
_verifyProof(tokenId, root, proof);
} else if (proof.length != 0) {
// A root of zero should never have a proof.
revert UnnecessaryProof();
}
// Transfer the token.
token.transferFrom(from, to, tokenId);
return true;
}
/// @dev Match an ERC721 order using `safeTransferFrom`, ensuring that the supplied proof demonstrates inclusion of the tokenId in the associated merkle root.
/// @param from The account to transfer the ERC721 token from — this token must first be approved on the seller's AuthenticatedProxy contract.
/// @param to The account to transfer the ERC721 token to.
/// @param token The ERC721 token to transfer.
/// @param tokenId The ERC721 tokenId to transfer.
/// @param root A merkle root derived from each valid tokenId — set to 0 to indicate a collection-level or tokenId-specific order.
/// @param proof A proof that the supplied tokenId is contained within the associated merkle root. Must be length 0 if root is not set.
/// @return A boolean indicating a successful match and transfer.
function matchERC721WithSafeTransferUsingCriteria(
address from,
address to,
IERC721 token,
uint256 tokenId,
bytes32 root,
bytes32[] calldata proof
) external returns (bool) {
// Proof verification is performed when there's a non-zero root.
if (root != bytes32(0)) {
_verifyProof(tokenId, root, proof);
} else if (proof.length != 0) {
// A root of zero should never have a proof.
revert UnnecessaryProof();
}
// Transfer the token.
token.safeTransferFrom(from, to, tokenId);
return true;
}
/// @dev Match an ERC1155 order, ensuring that the supplied proof demonstrates inclusion of the tokenId in the associated merkle root.
/// @param from The account to transfer the ERC1155 token from — this token must first be approved on the seller's AuthenticatedProxy contract.
/// @param to The account to transfer the ERC1155 token to.
/// @param token The ERC1155 token to transfer.
/// @param tokenId The ERC1155 tokenId to transfer.
/// @param amount The amount of ERC1155 tokens with the given tokenId to transfer.
/// @param root A merkle root derived from each valid tokenId — set to 0 to indicate a collection-level or tokenId-specific order.
/// @param proof A proof that the supplied tokenId is contained within the associated merkle root. Must be length 0 if root is not set.
/// @return A boolean indicating a successful match and transfer.
function matchERC1155UsingCriteria(
address from,
address to,
IERC1155 token,
uint256 tokenId,
uint256 amount,
bytes32 root,
bytes32[] calldata proof
) external returns (bool) {
// Proof verification is performed when there's a non-zero root.
if (root != bytes32(0)) {
_verifyProof(tokenId, root, proof);
} else if (proof.length != 0) {
// A root of zero should never have a proof.
revert UnnecessaryProof();
}
// Transfer the token.
token.safeTransferFrom(from, to, tokenId, amount, "");
return true;
}
/// @dev Ensure that a given tokenId is contained within a supplied merkle root using a supplied proof.
/// @param leaf The tokenId.
/// @param root A merkle root derived from each valid tokenId.
/// @param proof A proof that the supplied tokenId is contained within the associated merkle root.
function _verifyProof(
uint256 leaf,
bytes32 root,
bytes32[] memory proof
) private pure {
bytes32 computedHash = bytes32(leaf);
for (uint256 i = 0; i < proof.length; i++) {
bytes32 proofElement = proof[i];
if (computedHash <= proofElement) {
// Hash(current computed hash + current element of the proof)
computedHash = _efficientHash(computedHash, proofElement);
} else {
// Hash(current element of the proof + current computed hash)
computedHash = _efficientHash(proofElement, computedHash);
}
}
if (computedHash != root) {
revert InvalidProof();
}
}
/// @dev Efficiently hash two bytes32 elements using memory scratch space.
/// @param a The first element included in the hash.
/// @param b The second element included in the hash.
/// @return value The resultant hash of the two bytes32 elements.
function _efficientHash(
bytes32 a,
bytes32 b
) private pure returns (bytes32 value) {
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
批量执行合约 WyvernAtomicizer 主网地址:0xC99f70bFD82fb7c8f8191fdfbFB735606b15e5c5
/**
*Submitted for verification at Etherscan.io on 2018-03-08
*/
pragma solidity ^0.4.13;
library WyvernAtomicizer {
function atomicize (address[] addrs, uint[] values, uint[] calldataLengths, bytes calldatas)
public
{
require(addrs.length == values.length && addrs.length == calldataLengths.length);
uint j = 0;
for (uint i = 0; i < addrs.length; i++) {
bytes memory calldata = new bytes(calldataLengths[i]);
for (uint k = 0; k < calldataLengths[i]; k++) {
calldata[k] = calldatas[j];
j++;
}
require(addrs[i].call.value(values[i])(calldata));
}
}
}
opensea token(WyvernToken) 主网地址:0x056017c55aE7AE32d12AeF7C679dF83A85ca75Ff 在构造参数传入的。
在chargeProtocolFee中用到 当订单的feeMethod 为ProtocolFee时用到
关于交易费
当订单的feeMethod 为SplitFee ,收取交易费的地址时订单传进来的feeRecipient 交易费的比率也是参数传进来的 。并且合约没有做收费地址校验和手续费比率校验。那应该就是后台做了校验 。也就是说你的挂单要出现在opensea页面opensea就要收取你的交易费 否则不用。
opensea的合约代码不多,但是团队并没有给出说明文档。要搞清楚具体细节还是需要花些时间。