logo Netzschleuder network catalog, repository, and centrifuge

History of nft_trades

The changes to the source code of this entry in the repository, newest first, each with the differences it made to the entry's files. They are also listed on GitLab.

[entries] Add the date when each entry was added, and remove icon_hash by Tiago de Paula Peixoto beff7c9
The dates are those of the commits that first added the entries' files
(2020-08-11 for the entries of the initial commit); renamed entries keep
the date of their first name.

Changed: none
diff --git a/entries/nft_trades/__init__.py b/entries/nft_trades/__init__.py
index dc17f87..6ea9ea1 100644
--- a/entries/nft_trades/__init__.py
+++ b/entries/nft_trades/__init__.py
@@ -51,7 +51,7 @@ tags = ['Economic', 'Cryptocurrency', 'Commerce', 'Bipartite', 'Multigraph', 'Ti
 url = ['https://osf.io/wsnzr/']
 citation = [('M. Nadini, L. Alessandretti, F. Di Giacinto, M. Martino, L. M. Aiello and A. Baronchelli, "Mapping the NFT revolution: market trends, trade networks, and visual features." Scientific Reports 11, 20902 (2021)',
              'https://doi.org/10.1038/s41598-021-00053-8')]
-icon_hash = None
+date_added = '2026-10-31'
 upstream_license = ('CC BY 4.0', 'https://creativecommons.org/licenses/by/4.0/')
 
 URLS = {"Data_API.csv.gz": "https://osf.io/download/vejrt/"}
[entries] Prefix the properties in the descriptions with (Node), (Edge) or (Graph) by Tiago de Paula Peixoto bc3dbbd
The lists of properties at the end of the descriptions marked edge
properties with "Edge" but left node properties unmarked, or grouped
them in "Nodes: `a`, ...; `b`, ..." items, so the kind of a property
was often unclear. Every item now starts with (Node), (Edge) or (Graph),
followed by the property names, a colon and the explanation, one item
per property or group of properties; qualifications (kinds of nodes,
networks) follow the names in parentheses. Properties that were only
named got a short explanation, and a few items that named the wrong
kind of property were corrected.
diff --git a/entries/nft_trades/__init__.py b/entries/nft_trades/__init__.py
index 5333d8f..dc17f87 100644
--- a/entries/nft_trades/__init__.py
+++ b/entries/nft_trades/__init__.py
@@ -33,20 +33,20 @@ The URLs of the NFTs' images, their descriptions and permanent links are not inc
 
 The networks have the following properties:
 
-- `address` (`trades`): the address of the trader on the blockchain.
-- `name` (`trader_nft`): the address of the trader, or the identifier of the NFT (its collection and token identifier, as given in the data).
-- `is_trader` (`trader_nft`): whether the node is a trader (otherwise an NFT).
-- `username`: the user name of the trader on the market, if given (the first one that appears in the data; empty for NFTs).
-- `nft_name`, `collection`, `category` (`trader_nft`): the name, collection and category of the NFT (empty for traders).
-- Edge `time`: the time of the sale (as given in the data, taken as UTC), in Unix time.
-- Edge `price_crypto`, `crypto`: the price in cryptocurrency and the cryptocurrency (e.g. `ETH`, `WAX`, `MANA`).
-- Edge `price_usd`: the price in US dollars (NaN if not given).
-- Edge `nft_id` (`trades`): the identifier of the NFT (its collection and token identifier, as given in the data).
-- Edge `nft_name` (`trades`): the name of the NFT, if given.
-- Edge `collection` (`trades`): the collection of the NFT (cleaned by the authors).
-- Edge `category` (`trades`): the category of the collection: `Art`, `Collectible`, `Games`, `Metaverse`, `Other` or `Utility`.
-- Edge `market`: the market of the sale.
-- Edge `transaction`: the hash of the transaction."""
+- (Node) `address` (`trades`): the address of the trader on the blockchain.
+- (Node) `name` (`trader_nft`): the address of the trader, or the identifier of the NFT (its collection and token identifier, as given in the data).
+- (Node) `is_trader` (`trader_nft`): whether the node is a trader (otherwise an NFT).
+- (Node) `username`: the user name of the trader on the market, if given (the first one that appears in the data; empty for NFTs).
+- (Node) `nft_name`, `collection`, `category` (`trader_nft`): the name, collection and category of the NFT (empty for traders).
+- (Edge) `time`: the time of the sale (as given in the data, taken as UTC), in Unix time.
+- (Edge) `price_crypto`, `crypto`: the price in cryptocurrency and the cryptocurrency (e.g. `ETH`, `WAX`, `MANA`).
+- (Edge) `price_usd`: the price in US dollars (NaN if not given).
+- (Edge) `nft_id` (`trades`): the identifier of the NFT (its collection and token identifier, as given in the data).
+- (Edge) `nft_name` (`trades`): the name of the NFT, if given.
+- (Edge) `collection` (`trades`): the collection of the NFT (cleaned by the authors).
+- (Edge) `category` (`trades`): the category of the collection: `Art`, `Collectible`, `Games`, `Metaverse`, `Other` or `Utility`.
+- (Edge) `market`: the market of the sale.
+- (Edge) `transaction`: the hash of the transaction."""
 tags = ['Economic', 'Cryptocurrency', 'Commerce', 'Bipartite', 'Multigraph', 'Timestamps', 'Metadata']
 url = ['https://osf.io/wsnzr/']
 citation = [('M. Nadini, L. Alessandretti, F. Di Giacinto, M. Martino, L. M. Aiello and A. Baronchelli, "Mapping the NFT revolution: market trends, trade networks, and visual features." Scientific Reports 11, 20902 (2021)',
[entries] New entry: nft_trades by Tiago de Paula Peixoto 895fb6a
diff --git a/entries/nft_trades/__init__.py b/entries/nft_trades/__init__.py
new file mode 100644
index 0000000..5333d8f
--- /dev/null
+++ b/entries/nft_trades/__init__.py
@@ -0,0 +1,183 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2026 Tiago de Paula Peixoto <tiago@skewed.de>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Affero General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option) any
+# later version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Affero General Public License along
+# with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import csv
+import sys
+from datetime import datetime, timezone
+
+from .. import *
+import process_entry
+
+title = "NFT trades on Ethereum and WAX (2017-2021)"
+description = r"""A network of the sales of non-fungible tokens (NFTs) on the Ethereum and WAX blockchains, from June 2017 to April 2021, from the dataset of M. Nadini, L. Alessandretti, F. Di Giacinto, M. Martino, L. M. Aiello and A. Baronchelli, collected through the APIs of the main NFT markets (OpenSea, Atomic, and the subgraphs of Cryptokitties, Decentraland and Gods Unchained). NFTs are tokens that certify the ownership of a digital object (e.g. a piece of digital art, a collectible card or an item of a game). There are two networks:
+
+- `trades`: each node is a trader (an address on the blockchain), and a directed edge from trader $i$ to trader $j$ is the sale of an NFT by $i$ to $j$; pairs of traders can have many sales (a multigraph).
+- `trader_nft`: a bipartite network of traders and NFTs (the property `is_trader` tells them apart); each sale gives two directed edges, from the seller to the NFT and from the NFT to the buyer, following the token.
+
+The URLs of the NFTs' images, their descriptions and permanent links are not included.
+
+The networks have the following properties:
+
+- `address` (`trades`): the address of the trader on the blockchain.
+- `name` (`trader_nft`): the address of the trader, or the identifier of the NFT (its collection and token identifier, as given in the data).
+- `is_trader` (`trader_nft`): whether the node is a trader (otherwise an NFT).
+- `username`: the user name of the trader on the market, if given (the first one that appears in the data; empty for NFTs).
+- `nft_name`, `collection`, `category` (`trader_nft`): the name, collection and category of the NFT (empty for traders).
+- Edge `time`: the time of the sale (as given in the data, taken as UTC), in Unix time.
+- Edge `price_crypto`, `crypto`: the price in cryptocurrency and the cryptocurrency (e.g. `ETH`, `WAX`, `MANA`).
+- Edge `price_usd`: the price in US dollars (NaN if not given).
+- Edge `nft_id` (`trades`): the identifier of the NFT (its collection and token identifier, as given in the data).
+- Edge `nft_name` (`trades`): the name of the NFT, if given.
+- Edge `collection` (`trades`): the collection of the NFT (cleaned by the authors).
+- Edge `category` (`trades`): the category of the collection: `Art`, `Collectible`, `Games`, `Metaverse`, `Other` or `Utility`.
+- Edge `market`: the market of the sale.
+- Edge `transaction`: the hash of the transaction."""
+tags = ['Economic', 'Cryptocurrency', 'Commerce', 'Bipartite', 'Multigraph', 'Timestamps', 'Metadata']
+url = ['https://osf.io/wsnzr/']
+citation = [('M. Nadini, L. Alessandretti, F. Di Giacinto, M. Martino, L. M. Aiello and A. Baronchelli, "Mapping the NFT revolution: market trends, trade networks, and visual features." Scientific Reports 11, 20902 (2021)',
+             'https://doi.org/10.1038/s41598-021-00053-8')]
+icon_hash = None
+upstream_license = ('CC BY 4.0', 'https://creativecommons.org/licenses/by/4.0/')
+
+URLS = {"Data_API.csv.gz": "https://osf.io/download/vejrt/"}
+upstream_prefix = None
+files = [("Data_API.csv.gz", "trades", None),
+         ("Data_API.csv.gz", "trader_nft", None)]
+
+def fetch_upstream(force=False):
+    # (a 757 MB file, downloaded through open_url and stored as it is)
+    name = __name__.split(".")[-1]
+    for fname, url in URLS.items():
+        if not force and upstream_file_exists(name, fname):
+            continue
+        if process_entry._check_health:
+            with open_url(url) as r:
+                r.read(1)
+            continue
+        with open_url(url) as r:
+            with open_upstream_file(name, fname, "wb") as out:
+                copy_download(r, out, label=fname)
+
+# For checks only: if set, only this many sales are read
+_check_rows = None
+
+def _float(x):
+    try:
+        return float(x)
+    except ValueError:
+        return numpy.nan
+
+def _rows(name, fname):
+    "The sales, as dicts with the time in Unix time (-1 if not parseable)."
+    with open_upstream_file(name, fname, "r") as f:
+        for i, r in enumerate(csv.DictReader(f)):
+            if _check_rows is not None and i >= _check_rows:
+                break
+            try:
+                r["time"] = int(datetime.fromisoformat(
+                    r["Datetime_updated_seconds"]).replace(
+                        tzinfo=timezone.utc).timestamp())
+            except ValueError:
+                r["time"] = -1
+            yield r
+
+@cache_network()
+@coerce_props(vforce={"address": "string", "username": "string",
+                      "name": "string", "nft_name": "string"},
+              eforce={"transaction": "string", "nft_id": "string",
+                      "nft_name": "string"})
+@annotate()
+def parse(alts=None):
+    name = __name__.split(".")[-1]
+    csv.field_size_limit(sys.maxsize)
+    for fname, alt, fmt in files:
+        if alts is not None and alt not in alts:
+            continue
+        users = {}
+        def user(a, u):
+            if u and a not in users:
+                users[a] = u
+        g = Graph(directed=True)
+        if alt == "trades":
+            eprops = [("time", "int64_t"), ("price_crypto", "double"),
+                      ("crypto", "string"), ("price_usd", "double"),
+                      ("nft_id", "string"), ("nft_name", "string"),
+                      ("collection", "string"), ("category", "string"),
+                      ("market", "string"), ("transaction", "string")]
+            for k, t in eprops:
+                g.ep[k] = g.new_ep(t)
+            def edges():
+                for r in _rows(name, fname):
+                    s, t = r["Seller_address"], r["Buyer_address"]
+                    user(s, r["Seller_username"])
+                    user(t, r["Buyer_username"])
+                    yield (s, t, r["time"], _float(r["Price_Crypto"]),
+                           r["Crypto"], _float(r["Price_USD"]),
+                           r["Unique_id_collection"], r["Name"],
+                           r["Collection_cleaned"], r["Category"],
+                           r["Market"], r["Transaction_hash"])
+            g.vp.address = g.add_edge_list(edges(), hashed=True,
+                                           hash_type="string",
+                                           eprops=[g.ep[k] for k, t in eprops])
+            g.vp.username = g.new_vp("string")
+            for v in g.vertices():
+                g.vp.username[v] = users.get(g.vp.address[v], "")
+        else:
+            # traders and tokens (keys prefixed, to keep them apart); each
+            # sale gives the edges seller -> token and token -> buyer
+            nfts = {}
+            eprops = [("time", "int64_t"), ("price_crypto", "double"),
+                      ("crypto", "string"), ("price_usd", "double"),
+                      ("market", "string"), ("transaction", "string")]
+            for k, t in eprops:
+                g.ep[k] = g.new_ep(t)
+            def edges():
+                for r in _rows(name, fname):
+                    s, t = r["Seller_address"], r["Buyer_address"]
+                    user(s, r["Seller_username"])
+                    user(t, r["Buyer_username"])
+                    nid = r["Unique_id_collection"]
+                    if nid not in nfts:
+                        nfts[nid] = (r["Name"], r["Collection_cleaned"],
+                                     r["Category"])
+                    e = (r["time"], _float(r["Price_Crypto"]), r["Crypto"],
+                         _float(r["Price_USD"]), r["Market"],
+                         r["Transaction_hash"])
+                    yield ("t" + s, "n" + nid) + e
+                    yield ("n" + nid, "t" + t) + e
+            key = g.add_edge_list(edges(), hashed=True, hash_type="string",
+                                  eprops=[g.ep[k] for k, t in eprops])
+            g.vp.is_trader = g.new_vp("bool")
+            g.vp.name = g.new_vp("string")
+            g.vp.username = g.new_vp("string")
+            g.vp.nft_name = g.new_vp("string")
+            g.vp.collection = g.new_vp("string")
+            g.vp.category = g.new_vp("string")
+            for v in g.vertices():
+                k = key[v]
+                x = k[1:]
+                g.vp.name[v] = x
+                if k[0] == "t":
+                    g.vp.is_trader[v] = True
+                    g.vp.username[v] = users.get(x, "")
+                else:
+                    n, c, cat = nfts[x]
+                    g.vp.nft_name[v] = n
+                    g.vp.collection[v] = c
+                    g.vp.category[v] = cat
+        yield alt, g