###### tags: `SAIGATE`
# Opensea ソースコード仕様
## 項目一覧
1. EX:WyvernExchange
2. Registry
3. tokenTransferProxy
4. OPENSTORE
## EX:WyvernExchange
### 概要
主な機能は商品の購入(BuyNow)セット売り(BuyBundle)オファーの受け入れ(Accept)オークションのビッドの受け入れを許可可能。また、キャンセル(CanselOrder)などの定義もこの部分でされています。
### 単語の定義について
#### owner
msg.sender
#### onlyowner()
require(msg.sender == owner);
### 主要機能
#### Safe math(library)
```
function mul(uint256 a, uint256 b) internal pure returns (uint256 c) {
if (a == 0) {
return 0;
}
c = a * b;
assert(c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
// uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return a / b;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
c = a + b;
assert(c >= a);
return c;
}
}
```
SafeMathは、コントラクト内で四則演算するとき、値がオーバーフロー、アンダーフローすることに対応してくれる四則演算ライブラリです。
#### contract ERC20Basic
ERC20の運用についての定義
#### contract ERC20
```
function allowance(address owner, address spender)
public view returns (uint256);
function transferFrom(address from, address to, uint256 value)
public returns (bool);
function approve(address spender, uint256 value) public returns (bool);
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
}
```
ERC20トークンに関する操作。
#### Transfar token
```
function transferTokens(address token, address from, address to, uint amount)
internal
{
if (amount > 0) {
require(tokenTransferProxy.transferFrom(token, from, to, amount));
}
}
```
トークンを移動します。この関数の前でトークン所有者による手数料の変更と後に買い手による手数料のチャージの部分があり、これは支払いの際にGASの負担は買い手側になるように設計されています。
#### Validate Order
```
function validateOrder(bytes32 hash, Order memory order, Sig memory sig)
internal
view
returns (bool)
{
if (!validateOrderParameters(order)) {
return false;
}
if (cancelledOrFinalized[hash]) {
return false;
}
if (approvedOrders[hash]) {
return true;
}
if (ecrecover(hash, sig.v, sig.r, sig.s) == order.maker) {
return true;
}
return false;
}
```
取引においてユーザーに不正が見られないかを確認します。スマートコントラクトによって呼び出され、デジタル署名のハッシュに基づいて、出品者と買い手の不正を確認します。
#### Approve Order
```
function approveOrder(Order memory order, bool orderbookInclusionDesired)
internal
{
require(msg.sender == order.maker);
bytes32 hash = hashToSign(order);
require(!approvedOrders[hash]);
approvedOrders[hash] = true;
{
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);
}
}
```
コントラクトが保有している(既に取引されている2回目以降の取引)トークンに関して、所有権を保有しているユーザーがアプルーブを求めます。スマートコントラクトから呼び出され、署名から呼び出し者と所有者が同じかどうかを確かめます。
#### canselOrder
```
function cancelOrder(Order memory order, Sig memory sig)
internal
{
bytes32 hash = requireValidOrder(order, sig);
require(msg.sender == order.maker);
cancelledOrFinalized[hash] = true;
emit OrderCancelled(hash);
}
```
トランスファーが行われる前であれば、キャンセルを申し立てることができます。
これは、売り手側から申し立てます。
#### executeFundsTransfer
```
function executeFundsTransfer(Order memory buy, Order memory sell)
internal
returns (uint)
{
if (sell.paymentToken != address(0)) {
require(msg.value == 0);
}
uint price = calculateMatchPrice(buy, sell);
if (price > 0 && sell.paymentToken != address(0)) {
transferTokens(sell.paymentToken, buy.maker, sell.maker, price);
}
uint receiveAmount = price;
uint requiredAmount = price;
if (sell.feeRecipient != address(0)) {
require(sell.takerRelayerFee <= buy.takerRelayerFee);
if (sell.feeMethod == FeeMethod.SplitFee) {
require(sell.takerProtocolFee <= buy.takerProtocolFee);
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 {
chargeProtocolFee(buy.maker, buy.feeRecipient, buy.makerRelayerFee);
chargeProtocolFee(sell.maker, buy.feeRecipient, buy.takerRelayerFee);
}
}
if (sell.paymentToken == address(0)) {
require(msg.value >= requiredAmount);
sell.maker.transfer(receiveAmount);
uint diff = SafeMath.sub(msg.value, requiredAmount);
if (diff > 0) {
buy.maker.transfer(diff);
}
}
return price;
}
```
取引における支払いの部分になります。事前に決定していた販売価格と購入価格にズレがないか、購入者は確かに支払い能力を有しているかなどが判断されます。また、手数料に関しても出品時に設定した最高手数料価格よりも支払う手数料が安くなっているかなどの判断項目が存在します。送金が成功した場合はリターンとして取引価格が設定されています
#### atomicMatch
```
function atomicMatch(Order memory buy, Sig memory buySig, Order memory sell, Sig memory sellSig, bytes32 metadata)
internal
reentrancyGuard
{
bytes32 buyHash;
if (buy.maker == msg.sender) {
require(validateOrderParameters(buy));
} else {
buyHash = requireValidOrder(buy, buySig);
}
bytes32 sellHash;
if (sell.maker == msg.sender) {
require(validateOrderParameters(sell));
} else {
sellHash = requireValidOrder(sell, sellSig);
}
require(ordersCanMatch(buy, sell));
uint size;
address target = sell.target;
assembly {
size := extcodesize(target)
}
require(size > 0);
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));
OwnableDelegateProxy delegateProxy = registry.proxies(sell.maker);
require(delegateProxy != address(0));
require(delegateProxy.implementation() == registry.delegateProxyImplementation());
AuthenticatedProxy proxy = AuthenticatedProxy(delegateProxy);
if (msg.sender != buy.maker) {
cancelledOrFinalized[buyHash] = true;
}
if (msg.sender != sell.maker) {
cancelledOrFinalized[sellHash] = true;
}
uint price = executeFundsTransfer(buy, sell);
require(proxy.proxy(sell.target, sell.howToCall, sell.calldata));
if (buy.staticTarget != address(0)) {
require(staticCall(buy.staticTarget, sell.calldata, buy.staticExtradata));
}
if (sell.staticTarget != address(0)) {
require(staticCall(sell.staticTarget, sell.calldata, sell.staticExtradata));
}
emit OrdersMatched(buyHash, sellHash, sell.feeRecipient != address(0) ? sell.maker : buy.maker, sell.feeRecipient != address(0) ? buy.maker : sell.maker, price, metadata);
}
```
買い手の購入情報について確認します。また売却情報が署名されているものかどうか、また、キャンセルや実行済みのものではないかどうかを確認します。問題がなければERC20トークンで支払いを行います。また、その後Openseaに手数料を支払い、NFTを売り手から買い手にトランスファーします。その際にアプルーブオーダーなどが行われます。最終的にABIを記述して終了します。
#### ProxyRegistry is Ownable
### アプリケーション上の機能詳細
#### SellOrder
販売者は商品販売の際に以下の情報(サンプル)について署名を行う必要があります。
この情報に基づいて先述の関数における価格の検証などが行われます。
また、購入者はこの条件に同意するために署名を行います。
```
. basePrice: "1000000000000000" //販売価格(ETH)
. calldata: "0x23b872dd000000000000000000000000e6b48d76bc4805abf61f38a55f1d7c362c8bfda800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"
. exchange: "0x5206e78b21ce315ce284fb24cf05e0585a93b1d9"
.
expirationTime: "0"
. extra: "0"
. feeMethod: 1
. feeRecipient: "0x5b3256965e7c3cf26e11fcaf296dfc8807c01073"
. hash: "0xb4fc308f42aa95eb42e5f54fd133c73554517908938708bfa10b5b190849e900"
. howToCall: 0
. listingTime: "1625391765"
. maker: "0xe6b48d76bc4805abf61f38a55f1d7c362c8bfda8"
. makerProtocolFee: "0"
. makerReferrerFee: "0"
. makerRelayerFee: "250"
. metadata: {asset: {id: "1", address: "0x6a55df8080c8cef5ae2d941356792fa925fdefea"}, schema: "ERC721"}
. paymentToken: "0x0000000000000000000000000000000000000000"
. quantity: "1"
. r: "0x3533205369ab9c22035830bd94ee2fc62474bb6fa343d458a8adfb45fc27ee21"
. replacementPattern: "0x000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000"
. s: "0x5e4923851c46b5d720ca4098ce7ed62d977db6458f66c4c91a8f65fe4db7e2d0"
. saleKind: 0
. salt: "39105810472022714477231934228712424453123455914673974930876616049220543647209"
. side: 1
. staticExtradata: "0x"
. staticTarget: "0x0000000000000000000000000000000000000000"
. taker: "0x0000000000000000000000000000000000000000"
. takerProtocolFee: "0"
. takerRelayerFee: "0"
. target: "0x6a55df8080c8cef5ae2d941356792fa925fdefea"
. v: 27
```
### MakeOffer
購入者がオファーを出す際に、以下の内容に署名しなければなりません。
販売者はこの内容に署名することで内容に同意したことになります。
```
. basePrice: "1230000000000000" // オファーの価格(WETH)
. calldata: "0x23b872dd000000000000000000000000000000000000000000000000000000000000000000000000000000000000000046352bf252f8e150f87a54cc09372b89e538c2bd0000000000000000000000000000000000000000000000000000000000000001"
. exchange: "0x5206e78b21ce315ce284fb24cf05e0585a93b1d9"
. expirationTime: "1626000859"
. extra: "0"
. feeMethod: 1
. feeRecipient: "0x5b3256965e7c3cf26e11fcaf296dfc8807c01073"
. hash: "0x257ad0ff4bb057c54e2300b4cb6058f93c7511ec36f3a9fe3a0fa33897a44ec9"
. howToCall: 0
. listingTime: "1625395990"
. maker: "0x46352bf252f8e150f87a54cc09372b89e538c2bd"
. makerProtocolFee: "0"
. makerReferrerFee: "0"
. makerRelayerFee: "0"
. metadata: {asset: {id: "1", address: "0x6a55df8080c8cef5ae2d941356792fa925fdefea"}, schema: "ERC721"}
. paymentToken: "0xc778417e063141139fce010982780140aa0cd5ab"
. quantity: "1"
. r: "0x5bce1c2c1a7ed2257de19ec74196695902bd56d97c8b7570afef55dfe7c6e83b"
. replacementPattern: "0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
.
s: "0x78a271bb0056c5fa504dce456d2ee113ce0a7549fe9b636c4975513b517fc4e9"
. saleKind: 0
. salt: "56589070341976203264620168480772649861226097129076943384856741423280029670357"
. side: 0
. staticExtradata: "0x"
. staticTarget: "0x0000000000000000000000000000000000000000"
. taker: "0x0000000000000000000000000000000000000000"
. takerProtocolFee: "0"
. takerRelayerFee: "250"
. target: "0x6a55df8080c8cef5ae2d941356792fa925fdefea"
. v: 28
```
### Auction + Place Bid
出品者はオークション形式でNFT作品を販売できます。Minimum Bid、Reserve price、Expiration Dateなどの情報で、sell orderを作れます。このsell orderに署名することで、指定の内容でオークション販売することを許可します。
```
. basePrice: "3000000000000000"
. calldata: "0x23b872dd000000000000000000000000e6b48d76bc4805abf61f38a55f1d7c362c8bfda800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003"
. englishAuctionReservePrice: "1000000000000000000"
. exchange: "0x5206e78b21ce315ce284fb24cf05e0585a93b1d9"
. expirationTime: "1626433752"
. extra: "0"
. feeMethod: 1
. feeRecipient: "0x0000000000000000000000000000000000000000"
. hash: "0x50081676ee3ab2fc28aca11124ab67505d041b3d34358cafd19a10147ebba876"
. howToCall: 0
. listingTime: "1625828952"
. maker: "0xe6b48d76bc4805abf61f38a55f1d7c362c8bfda8"
. makerProtocolFee: "0"
. makerReferrerFee: "0"
. makerRelayerFee: "0"
. metadata: {asset: {id: "3", address: "0x6a55df8080c8cef5ae2d941356792fa925fdefea"}, schema: "ERC721"}
. paymentToken: "0xc778417e063141139fce010982780140aa0cd5ab"
. quantity: "1"
. r: "0xb4f73a86448d60bb2a0f353d572da396518675755dbee26e59780455fa2b56f5"
.
replacementPattern: "0x000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000"
. s: "0x27d1b5f3bf75a1dcb5d077a5c5b1526e1c98457f1852d9e51edae2baa3fd926e"
. saleKind: 0
. salt: "25810885847732897704358855487251854621543428487682073494449509339899062516907"
. side: 1
. staticExtradata: "0x589ad31c"
. staticTarget: "0xe291abab95677bc652a44f973a8e06d48464e11c"
. taker: "0x0000000000000000000000000000000000000000"
. takerProtocolFee: "0"
. takerRelayerFee: "250"
. target: "0x6a55df8080c8cef5ae2d941356792fa925fdefea"
. v: 27
```