Classify opensea nft trades
This commit is contained in:
parent
cfa3443f88
commit
f92737b00c
1
mev_inspect/abis/opensea/WyvernExchange.json
Normal file
1
mev_inspect/abis/opensea/WyvernExchange.json
Normal file
File diff suppressed because one or more lines are too long
@ -13,6 +13,7 @@ from .erc20 import ERC20_CLASSIFIER_SPECS
|
||||
from .uniswap import UNISWAP_CLASSIFIER_SPECS
|
||||
from .weth import WETH_ADDRESS, WETH_CLASSIFIER_SPECS
|
||||
from .zero_ex import ZEROX_CLASSIFIER_SPECS
|
||||
from .opensea import OPENSEA_CLASSIFIER_SPECS
|
||||
|
||||
ALL_CLASSIFIER_SPECS = (
|
||||
ERC20_CLASSIFIER_SPECS
|
||||
@ -24,6 +25,7 @@ ALL_CLASSIFIER_SPECS = (
|
||||
+ BALANCER_CLASSIFIER_SPECS
|
||||
+ COMPOUND_CLASSIFIER_SPECS
|
||||
+ CRYPTOPUNKS_CLASSIFIER_SPECS
|
||||
+ OPENSEA_CLASSIFIER_SPECS
|
||||
+ BANCOR_CLASSIFIER_SPECS
|
||||
)
|
||||
|
||||
|
52
mev_inspect/classifiers/specs/opensea.py
Normal file
52
mev_inspect/classifiers/specs/opensea.py
Normal 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]
|
@ -26,6 +26,7 @@ from mev_inspect.crud.punks import (
|
||||
write_punk_snipes,
|
||||
)
|
||||
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.traces import (
|
||||
delete_classified_traces_for_block,
|
||||
@ -121,6 +122,9 @@ async def inspect_block(
|
||||
delete_punk_snipes_for_block(inspect_db_session, block_number)
|
||||
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(
|
||||
block.miner, block.base_fee_per_gas, classified_traces, block.receipts
|
||||
)
|
||||
|
44
mev_inspect/nft_trades.py
Normal file
44
mev_inspect/nft_trades.py
Normal 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
|
@ -6,6 +6,7 @@ from pydantic import BaseModel
|
||||
from .swaps import Swap
|
||||
from .traces import Classification, DecodedCallTrace, Protocol
|
||||
from .transfers import Transfer
|
||||
from .nft_trade import NftTrade
|
||||
|
||||
|
||||
class Classifier(ABC):
|
||||
@ -52,6 +53,16 @@ class SeizeClassifier(Classifier):
|
||||
def get_classification() -> Classification:
|
||||
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):
|
||||
abi_name: str
|
||||
|
20
mev_inspect/schemas/nft_trade.py
Normal file
20
mev_inspect/schemas/nft_trade.py
Normal 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
|
@ -33,6 +33,7 @@ class Classification(Enum):
|
||||
seize = "seize"
|
||||
punk_bid = "punk_bid"
|
||||
punk_accept_bid = "punk_accept_bid"
|
||||
nft_trade = "nft_trade"
|
||||
|
||||
|
||||
class Protocol(Enum):
|
||||
@ -48,6 +49,7 @@ class Protocol(Enum):
|
||||
cream = "cream"
|
||||
cryptopunks = "cryptopunks"
|
||||
bancor = "bancor"
|
||||
opensea = "opensea"
|
||||
|
||||
|
||||
class ClassifiedTrace(Trace):
|
||||
|
Loading…
x
Reference in New Issue
Block a user