On July 17, 2018, I spoke at the Christies first ever annual Tech Summit entitled “Exploring Blockchain”, in London. I even got a freebie NFT!
During the event SuperRare partnered with Jason Bailey and enlisted Robbie Barrat, the first artist to ever tokenize on SuperRare. Robbie created “AI Generated Nude Portrait #7” for the event, which he intended as 300 separate frames of a single artwork. Each of the 300 frames was tokenized separately and added to redeemable ETH gift cards with directions for how to claim the 1/1 token.
A small handful of these original NFTs are known to still exist. We’ll call them “Robbies”. On April 5th, 2021, frame 269 sold for 125ETH ($265K).
This says Robbie has earned 118.66 ETH (~$317K) from sales so far. Not bad.
Ethereum transactions execute methods that are written in a contract, which is basically a bunch of code that implements a set of known methods (an interface, e.g. ERC721). As all code, method have inputs and outputs. The SuperRare contract is 0x41a322b28d0ff354040e2cbc676f0320d8c8850d, also found by examining a transaction linked from SuperRare.
Contracts are expressed in JSON, include method names, inputs, outputs, and other metadata. Contracts, along with all inputs and outputs on Ethereum, are encoded in binary format, using an application binary interface (ABI). We can fetch the contract with contract.getabi.
The ABI also gives you the ability to create an instance of a ethereum-input-data-decoder to decode data in transactions that had been executed under this contract with new InputDataDecoder(json).
Transactions and Logs
Ethereum transactions are a series of method calls. Each transaction has an address, input arguments and output results. Each method call inside a transaction receives input, or topics, that can be indexed. A successful method call creates a log entry. Etherscan lets you query logs that belong to a certain contract using indexed topics.
For example, you can query logs for all method calls for the SuperRare contract.
Dude, where’s my NFT?
So, what’s the address of my NFT? Well, there isn’t one.
The SuperRare contract mints a new NFT as a side effect of a call to the addNewToken method. The addNewToken call increments totalSupply() of tokens to obtain a new token ID. By convention, this looks like a transfer from address 0x00 to the caller using the Transfer method.
Take a look at the first Nude Portrait #7 token creation transaction. The transaction address was 0x397cf219aadb0e25afc7fcbb35f36ebccd8611375b5c7ad888e4cbacced2d7ea. It called the addNewToken method with an _uri of https://ipfs.pixura.io/ipfs/QmWkvzP1FZBrwBXjj3vD258RQm9MtV25G69zcqzYmc1cGd, which contains the JSON of frame #1.
The output of this transaction was a _tokenId of 191. Navigating to superrare.co/artwork/191 will incidentally show you the first frame from “Nude Portrait #7”.
Finding Create Transactions
As I described above, creating a token means calling Transfer from address 0x00. The tokenId argument, however, is not indexed, so I could not find how to get the transaction that created, for example, token number 191. However, I figured out how to find all the transactions that generated the 300 tokens by specifying the source address of 0x00.
Examining a Transaction
Now that we have a collection of 300 logs, we can, for each log, get the corresponding transactions, decode input data, identify the method called, etc.
After creation, the tokens were transferred from @videodrome’s address into newly created wallets. Those transfers are indexed by the sender’s address. Again, because the tokenId is not indexed in these transactions, I couldn’t figure out how to query all transfer logs for a single token, but we can get the entire set.
Sales and Bids
Reading the contract shows that bid, acceptBid and buy event logs are indexed by tokenId as the 3rd topic.
Similarly, setSalePrice is indexed by tokenId as the 1st topic.
Decoding inputs in these logs tells us, for example, the amount for the sale price set (parseInt(log.topics, 16)) or the transaction timestamp (moment.unix(parseInt(log.timeStamp, 16))).
Putting It All Together
The complete code to this blog post is here. It fetches and stores the initial create transactions, subsequent transfer transactions, then all the sales transactions.
Run npm run update to fetch any new data updates, cached locally, and npm run sales to show the most recent sales.