Classify opensea nft trades

This commit is contained in:
Shea Ketsdever 2021-12-19 12:16:49 -08:00
parent cfa3443f88
commit f92737b00c
8 changed files with 136 additions and 0 deletions

File diff suppressed because one or more lines are too long

View File

@ -13,6 +13,7 @@ from .erc20 import ERC20_CLASSIFIER_SPECS
from .uniswap import UNISWAP_CLASSIFIER_SPECS from .uniswap import UNISWAP_CLASSIFIER_SPECS
from .weth import WETH_ADDRESS, WETH_CLASSIFIER_SPECS from .weth import WETH_ADDRESS, WETH_CLASSIFIER_SPECS
from .zero_ex import ZEROX_CLASSIFIER_SPECS from .zero_ex import ZEROX_CLASSIFIER_SPECS
from .opensea import OPENSEA_CLASSIFIER_SPECS
ALL_CLASSIFIER_SPECS = ( ALL_CLASSIFIER_SPECS = (
ERC20_CLASSIFIER_SPECS ERC20_CLASSIFIER_SPECS
@ -24,6 +25,7 @@ ALL_CLASSIFIER_SPECS = (
+ BALANCER_CLASSIFIER_SPECS + BALANCER_CLASSIFIER_SPECS
+ COMPOUND_CLASSIFIER_SPECS + COMPOUND_CLASSIFIER_SPECS
+ CRYPTOPUNKS_CLASSIFIER_SPECS + CRYPTOPUNKS_CLASSIFIER_SPECS
+ OPENSEA_CLASSIFIER_SPECS
+ BANCOR_CLASSIFIER_SPECS + BANCOR_CLASSIFIER_SPECS
) )

View File

@ -0,0 +1,52 @@
from typing import List
from mev_inspect.classifiers.helpers import _filter_transfers
from mev_inspect.schemas.classifiers import ClassifierSpec, NftTradeClassifier
from mev_inspect.schemas.nft_trade import NftTrade
from mev_inspect.schemas.traces import DecodedCallTrace, Protocol
from mev_inspect.schemas.transfers import ETH_TOKEN_ADDRESS, Transfer
OPENSEA_ETH_TOKEN_ADDRESS = "0x0000000000000000000000000000000000000000"
class OpenseaClassifier(NftTradeClassifier):
@staticmethod
def parse_trade(trace: DecodedCallTrace) -> NftTrade:
uints = trace.inputs.get("uints")
addresses = trace.inputs.get("addrs")
buy_maker = addresses[1]
sell_maker = addresses[8]
base_price = uints[4]
payment_token = addresses[6]
target = addresses[4]
if payment_token == OPENSEA_ETH_TOKEN_ADDRESS:
# Opensea uses the zero-address as a sentinel value for Ether. Convert this
# to the normal eth token address.
payment_token = ETH_TOKEN_ADDRESS
return NftTrade(
abi_name=trace.abi_name,
transaction_hash=trace.transaction_hash,
transaction_position=trace.transaction_position,
block_number=trace.block_number,
trace_address=trace.trace_address,
protocol=trace.protocol,
error=trace.error,
seller_address=sell_maker,
buyer_address=buy_maker,
payment_token=payment_token,
payment_amount=base_price,
collection_address=target,
token_uri=0 # Todo
)
OPENSEA_SPEC= ClassifierSpec(
abi_name="WyvernExchange",
protocol=Protocol.opensea,
valid_contract_addresses=["0x7be8076f4ea4a4ad08075c2508e481d6c946d12b"],
classifiers={
"atomicMatch_(address[14],uint256[18],uint8[8],bytes,bytes,bytes,bytes,bytes,bytes,uint8[2],bytes32[5])": OpenseaClassifier, # TODO actual types
},
)
OPENSEA_CLASSIFIER_SPECS = [OPENSEA_SPEC]

View File

@ -26,6 +26,7 @@ from mev_inspect.crud.punks import (
write_punk_snipes, write_punk_snipes,
) )
from mev_inspect.crud.sandwiches import delete_sandwiches_for_block, write_sandwiches from mev_inspect.crud.sandwiches import delete_sandwiches_for_block, write_sandwiches
from mev_inspect.nft_trades import get_nft_trades
from mev_inspect.crud.swaps import delete_swaps_for_block, write_swaps from mev_inspect.crud.swaps import delete_swaps_for_block, write_swaps
from mev_inspect.crud.traces import ( from mev_inspect.crud.traces import (
delete_classified_traces_for_block, delete_classified_traces_for_block,
@ -121,6 +122,9 @@ async def inspect_block(
delete_punk_snipes_for_block(inspect_db_session, block_number) delete_punk_snipes_for_block(inspect_db_session, block_number)
write_punk_snipes(inspect_db_session, punk_snipes) write_punk_snipes(inspect_db_session, punk_snipes)
nft_trades = get_nft_trades(classified_traces)
logger.info(f"Block: {block_number} -- Found {len(nft_trades)} nft trades")
miner_payments = get_miner_payments( miner_payments = get_miner_payments(
block.miner, block.base_fee_per_gas, classified_traces, block.receipts block.miner, block.base_fee_per_gas, classified_traces, block.receipts
) )

44
mev_inspect/nft_trades.py Normal file
View File

@ -0,0 +1,44 @@
from typing import List, Optional
from mev_inspect.classifiers.specs import get_classifier
from mev_inspect.schemas.classifiers import NftTradeClassifier
from mev_inspect.schemas.nft_trade import NftTrade
from mev_inspect.schemas.traces import Classification, ClassifiedTrace, DecodedCallTrace
from mev_inspect.schemas.transfers import Transfer
from mev_inspect.traces import get_traces_by_transaction_hash
def get_nft_trades(traces: List[ClassifiedTrace]) -> List[NftTrade]:
nft_trades = []
for _, transaction_traces in get_traces_by_transaction_hash(traces).items():
nft_trades += _get_nft_trades_for_transaction(
list(transaction_traces)
)
return nft_trades
def _get_nft_trades_for_transaction(
traces: List[ClassifiedTrace],
) -> List[NftTrade]:
ordered_traces = list(sorted(traces, key=lambda t: t.trace_address))
nft_trades: List[NftTrade] = []
for trace in ordered_traces:
if not isinstance(trace, DecodedCallTrace):
continue
elif trace.classification == Classification.nft_trade:
nft_transfer = _parse_trade(trace)
nft_trades.append(nft_transfer)
return nft_trades
def _parse_trade(trace: DecodedCallTrace) -> Optional[NftTrade]:
classifier = get_classifier(trace)
if classifier is not None and issubclass(classifier, NftTradeClassifier):
return classifier.parse_trade(trace)
return None

View File

@ -6,6 +6,7 @@ from pydantic import BaseModel
from .swaps import Swap from .swaps import Swap
from .traces import Classification, DecodedCallTrace, Protocol from .traces import Classification, DecodedCallTrace, Protocol
from .transfers import Transfer from .transfers import Transfer
from .nft_trade import NftTrade
class Classifier(ABC): class Classifier(ABC):
@ -52,6 +53,16 @@ class SeizeClassifier(Classifier):
def get_classification() -> Classification: def get_classification() -> Classification:
return Classification.seize return Classification.seize
class NftTradeClassifier(Classifier):
@staticmethod
def get_classification() -> Classification:
return Classification.nft_trade
@staticmethod
@abstractmethod
def parse_trade(trace: DecodedCallTrace) -> NftTrade:
return NotImplementedError()
class ClassifierSpec(BaseModel): class ClassifierSpec(BaseModel):
abi_name: str abi_name: str

View File

@ -0,0 +1,20 @@
from typing import List, Optional
from mev_inspect.schemas.traces import Protocol
from pydantic import BaseModel
class NftTrade(BaseModel):
abi_name: str
transaction_hash: str
transaction_position: int
block_number: int
trace_address: List[int]
protocol: Optional[Protocol]
error: Optional[str]
seller_address: str
buyer_address: str
payment_token: str
payment_amount: int
collection_address: str
token_uri: int

View File

@ -33,6 +33,7 @@ class Classification(Enum):
seize = "seize" seize = "seize"
punk_bid = "punk_bid" punk_bid = "punk_bid"
punk_accept_bid = "punk_accept_bid" punk_accept_bid = "punk_accept_bid"
nft_trade = "nft_trade"
class Protocol(Enum): class Protocol(Enum):
@ -48,6 +49,7 @@ class Protocol(Enum):
cream = "cream" cream = "cream"
cryptopunks = "cryptopunks" cryptopunks = "cryptopunks"
bancor = "bancor" bancor = "bancor"
opensea = "opensea"
class ClassifiedTrace(Trace): class ClassifiedTrace(Trace):