Свой NFT Rarity на Django
SolRarity мы все знаем, регулярно (возможно) его используем. До этого момента всем было похуй как он работает, но теперь времена меняются. И вам по прежнему похуй. Но писать мне о чем-то надо, так что будем разбирать работу таких чекеров рарности
В данной статье рассмотрен Эфир, Солана же работает ПОЧТИ также окда
🔧 Настройка проекта
Для чекера мы будем использовать шаблон Cookiecutter-Django для начальной загрузки проекта Django. Вы можете настроить проект либо для локального запуска, либо с помощью Docker. Поскольку мы будем использовать Celery, то настроим свой проект на использование Docker
Загрузите проект с помощью команды:
cookiecutter gh:cookiecutter/cookiecutter-django
Если что-то будет не так, то читайте документацию Cookiecutter-Django
🎭 Создаем приложение
Создайте новое приложение для хранения всей логики чекера рарности:
docker-compose -f local.yml run --rm django python manage.py startapp sniper
🗿 Модели
Данный проект будет иметь несколько моделей для хранения проекта NFT, атрибутов NFT и уникальных NFT. Внутри нового приложения models.py
добавьте следующий код:
from django.db import models class NFTProject(models.Model): contract_address = models.CharField(max_length=100) contract_abi = models.TextField() name = models.CharField(max_length=50) # e.g BAYC number_of_nfts = models.PositiveIntegerField() def __str__(self): return self.name class NFT(models.Model): project = models.ForeignKey( NFTProject, on_delete=models.CASCADE, related_name="nfts" ) rarity_score = models.FloatField(null=True) nft_id = models.PositiveIntegerField() image_url = models.CharField(max_length=200) rank = models.PositiveIntegerField(null=True) def __str__(self): return f"{self.project.name}: {self.nft_id}" class NFTAttribute(models.Model): project = models.ForeignKey( NFTProject, on_delete=models.CASCADE, related_name="attributes" ) name = models.CharField(max_length=50) value = models.CharField(max_length=100) def __str__(self): return f"{self.name}: {self.value}" class NFTTrait(models.Model): nft = models.ForeignKey( NFT, on_delete=models.CASCADE, related_name="nft_attributes" ) attribute = models.ForeignKey( NFTAttribute, on_delete=models.CASCADE, related_name="traits" ) rarity_score = models.FloatField(null=True) def __str__(self): return f"{self.attribute.name}: {self.attribute.value}"
🔗 Web3
Одним из самых популярных пакетов Python для взаимодействия с блокчейном Ethereum является web3.py. Используя этот пакет, мы можем взаимодействовать с существующими смарт-контрактами.
Начните с установки пакета web3 вместе с pip install web3
образами Docker и перестройте их.
1️⃣ Экспериментируем с коллекцией BAYC
В этом уроке мы сосредоточимся на одном проекте NFT — яхт-клубе Bored Ape
Чтобы найти смарт-контракт для любого проекта, выполните поиск BAYC в списке токенов Etherscan ERC721. Вы можете просмотреть токен BAYC здесь. Обратите внимание на значение Contract, указанную в Profile Summary.
Следующий шаг — просмотреть код контракта, что вы можете сделать, нажав на пункт меню Contract. Это приведет вас на эту страницу. Затем вы можете просмотреть все методы, доступные в смарт-контракте. Большинство методов в контракте используются для чтения данных, понятненько?
Метод, который нас интересует, - это tokenURI метод, который вы можете найти 20 по счету. Метод принимает tokenId качестве параметра и ищет информацию для этого конкретного идентификатора NFT.
Например, вы можете указать значение 7575, и оно вернет информацию для NFT BAYC # 7575:
2️⃣ Адрес смарт-контракта и ABI
Вам нужно две вещи, чтобы взаимодействовать со смарт-контрактом:
Обе эти вещи можно найти на Etherscan на странице адреса контракта BAYC
Адрес контракта указан в URL-адресе ссылки выше, а также в заголовке страницы. Адрес контракта: 0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D
Чтобы получить ABI, перейдите в Contract и прокрутите вниз до раздела Contract ABI. Там вы увидите текстовый ввод с длинным значением данных JSON следующим образом:
[{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint256","name":"maxNftSupply","type":"uint256"},{"internalType":"uint256","name":"saleStart","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs": .....
Этот длинный текст является значением ABI. Мы будем использовать адрес контракта и ABI в коде
3️⃣ Выполнение запросов к блокчейну Ethereum
Чтобы сделать запрос, вам нужно будет настроить провайдера web3.
Следуя из web3.py документации:
Провайдер — это то, как web3 разговаривает с блокчейном. Провайдеры принимают запросы JSON-RPC и возвращают ответ. Обычно это делается путем отправки запроса на сервер на основе сокетов HTTP или IPC.
Одним из вариантов является запуск собственного узла Ethereum, но это выходит за рамки этого урока, поэтому вместо этого мы будем использовать сервис.
► Infura
Infura предоставляет инструменты, помогающие в разработке блокчейна.
Делаем учетную запись (это бесплатно), переходим на панель мониторинга и создаем новый проект. В настройках проекта вы найдете PROJECT_ID
. Вы будете использовать это значение для подключения к своему собственному провайдеру web3. Теперь можно скамить!
4️⃣ Извлечение данных признаков из NFT
Классная вещь о NFT заключается в том, что вы можете прикреплять данные к NFT, такие как текст и изображения. Эти данные называются метаданными.
Теперь мы напишем код с использованием пакета web3 Python для взаимодействия с контрактом BAYC и получения метаданных для BAYC # 7575.
Вот команда управления Django, которую можно запустить с python manage.py fetch_nfts
помощью или docker-compose -f local.yml run --rm django python manage.py fetch_nfts
from django.core.management.base import BaseCommand from web3.main import Web3 INFURA_PROJECT_ID = "<your_project_id>" INFURA_ENDPOINT = f"https://mainnet.infura.io/v3/{INFURA_PROJECT_ID}" class Command(BaseCommand): def handle(self, *args, **options): self.fetch_nfts(7575) def fetch_nfts(self, token_id): contract_address = "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D" contract_abi = '<paste the ABI text in here>' w3 = Web3(Web3.HTTPProvider(INFURA_ENDPOINT)) contract_instance = w3.eth.contract(address=contract_address, abi=contract_abi) print(f"Fetching NFT #{token_id}") data = contract_instance.functions.tokenURI(token_id).call() print(data)
В этом скрипте мы настраиваем INFURA_ENDPOINT
значение, которое указывает на наш проект Infura. Обязательно <your_project_id>
замените его своим собственным значением из панели мониторинга проекта Infura. Затем мы используем пакет web3 Python для установки соединения с блокчейном Ethereum через Infura.
С адресом контракта и ABI мы можем позвонитьw3.eth.contract
, чтобы подключиться к контракту. После подключения мы можем выполнять методы, доступные в контракте, как мы видели в коде контракта Etherscan.
В частности, мы вызываем tokenURI метод.
Синтаксис для этого может показаться странным на первый взгляд. Наиболее важной частью этого скрипта является следующая строка:
data = contract_instance.functions.tokenURI(token_id).call()
Эта строка вызывает tokenURI функцию. Обратите внимание, что мы используем.call()
. Это происходит потому, что мы считываем данные из контракта. Если бы мы хотели записать данные в контракт, мы бы их использовали.transact()
. Подробнее об этом вы можете прочитать в документах web3.
После запуска команды управления в терминале должно появиться следующее:
ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/7575
Это значение является URL-адресом IPFS.
InterPlanetary File System (IPFS) — это распределенная файловая система. Для просмотра данных IPFS вы можете использовать такую услугу, как ipfs.io.
Перейдите к https://ipfs.io/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/7575 и вы получите следующий ответ JSON:
{"image":"ipfs://QmTE1TK15CcmETgc6wwSNDNwMgF7PvH714GGq33ShcWjR7","attributes":[{"trait_type":"Eyes","value":"Sleepy"},{"trait_type":"Mouth","value":"Bored Unshaven"},{"trait_type":"Background","value":"Orange"},{"trait_type":"Hat","value":"Prussian Helmet"},{"trait_type":"Fur","value":"Black"},{"trait_type":"Clothes","value":"Sleeveless T"}]}
Теперь вы можете видеть, что данные JSON включают URL-адрес изображения и список признаков. Некоторые из черт - это рот, фон, шляпа и т. д. Вы также можете увидеть значение для каждой черты. Эти черты мы будем использовать для расчета редкости NFT.
Для просмотра изображения вам нужно будет использовать значение URL IPFS. Опять же, вы можете использовать ipfs.io и просмотрите изображение здесь.
5️⃣ Хранение данных NFT
Теперь, когда мы понимаем, какой тип данных существует в NFT, мы будем хранить эти данные, используя наши модели Django.
Внутри sniper/admin.py
обязательно зарегистрируйте все модели:
from django.contrib import admin from .models import NFTProject, NFT, NFTTrait, NFTAttribute class NFTAdmin(admin.ModelAdmin): list_display = ["nft_id", "rank", "rarity_score"] search_fields = ["nft_id__exact"] class NFTAttributeAdmin(admin.ModelAdmin): list_display = ["name", "value"] list_filter = ["name"] admin.site.register(NFTProject) admin.site.register(NFTTrait) admin.site.register(NFT, NFTAdmin) admin.site.register(NFTAttribute, NFTAttributeAdmin)
Перейдите к администратору Django http://127.0.0.1:8000/admin и создайте новый NFTProject
со всеми данными BAYC:
Мы собираемся изменить наш скрипт Django так, чтобы он создавал все NFT и связывал их с только что созданным NFTProject:
from django.core.management.base import BaseCommand import requests from web3.main import Web3 from djsniper.sniper.models import NFTProject, NFT, NFTAttribute, NFTTrait INFURA_PROJECT_ID = "<your_project_id>" INFURA_ENDPOINT = f"https://mainnet.infura.io/v3/{INFURA_PROJECT_ID}" class Command(BaseCommand): def handle(self, *args, **options): self.fetch_nfts(1) def fetch_nfts(self, project_id): project = NFTProject.objects.get(id=project_id) w3 = Web3(Web3.HTTPProvider(INFURA_ENDPOINT)) contract_instance = w3.eth.contract( address=project.contract_address, abi=project.contract_abi ) # Hardcoding only 10 NFTs otherwise it takes long for i in range(0, 10): ipfs_uri = contract_instance.functions.tokenURI(i).call() data = requests.get( f"https://ipfs.io/ipfs/{ipfs_uri.split('ipfs://')[1]}" ).json() nft = NFT.objects.create(nft_id=i, project=project, image_url=data["image"].split('ipfs://')[1]) attributes = data["attributes"] for attribute in attributes: nft_attribute, created = NFTAttribute.objects.get_or_create( project=project, name=attribute["trait_type"], value=attribute["value"], ) NFTTrait.objects.create(nft=nft, attribute=nft_attribute)
После запуска скрипта вы должны увидеть 10 NFT внутри администратора Django. Вы также должны увидеть 48 атрибутов nft, которые вы можете фильтровать с помощью фильтра списка сбоку
6️⃣ Вычисление NFT Rarity
Rarity Tools был одним из первых проектов, которые вы могли использовать для расчета NFT rarity. Мы будем вычислять редкость, используя ту же формулу, что и инструменты редкости. Вы также можете прочитать больше о том, как рассчитывается оценка редкости
Формула для расчета редкости выглядит следующим образом:
[Оценка редкости для значения признака] = 1 / ([Количество элементов с этим значением признака] / [Общее количество элементов в коллекции])
Теперь мы напишем второй скрипт для вычисления редкости и ранга каждого NFT:
from django.core.management.base import BaseCommand from django.db.models import OuterRef, Func, Subquery from djsniper.sniper.models import NFTProject, NFTAttribute, NFTTrait class Command(BaseCommand): def handle(self, *args, **options): self.rank_nfts(1) def rank_nfts(self, project_id): project = NFTProject.objects.get(id=project_id) # calculate sum of NFT trait types trait_count_subquery = ( NFTTrait.objects.filter(attribute=OuterRef("id")) .order_by() .annotate(count=Func("id", function="Count")) .values("count") ) attributes = NFTAttribute.objects.all().annotate( trait_count=Subquery(trait_count_subquery) ) # Group traits under each type trait_type_map = {} for i in attributes: if i.name in trait_type_map.keys(): trait_type_map[i.name][i.value] = i.trait_count else: trait_type_map[i.name] = {i.value: i.trait_count} # Calculate rarity """ [Rarity Score for a Trait Value] = 1 / ([Number of Items with that Trait Value] / [Total Number of Items in Collection]) """ for nft in project.nfts.all(): # fetch all traits for NFT total_score = 0 for nft_attribute in nft.nft_attributes.all(): trait_name = nft_attribute.attribute.name trait_value = nft_attribute.attribute.value # Number of Items with that Trait Value trait_sum = trait_type_map[trait_name][trait_value] rarity_score = 1 / (trait_sum / project.number_of_nfts) nft_attribute.rarity_score = rarity_score nft_attribute.save() total_score += rarity_score nft.rarity_score = total_score nft.save() # Rank NFTs for index, nft in enumerate(project.nfts.all().order_by("-rarity_score")): nft.rank = index + 1 nft.save()
В этом скрипте происходит несколько вещей
Первое, что мы делаем, это вычисляем количество признаков, которые имеет каждый атрибут. Здесь мы используем подзапрос, чтобы мы могли аннотировать счетчик с помощью поиска по внешнему ключу.
trait_count_subquery = ( NFTTrait.objects.filter(attribute=OuterRef("id")) .order_by() .annotate(count=Func("id", function="Count")) .values("count") ) attributes = NFTAttribute.objects.all().annotate( trait_count=Subquery(trait_count_subquery) )
Данные, возвращаемые из этого запроса, выглядят следующим образом:
('Earring', 'Silver Hoop', 1) ('Background', 'Orange', 2) ('Fur', 'Robot', 3) ('Clothes', 'Striped Tee', 1) ('Mouth', 'Discomfort', 1) ('Eyes', 'X Eyes', 2) ('Mouth', 'Grin', 1) ('Clothes', 'Vietnam Jacket', 1) ...
Затем мы группируем данные в каждую категорию атрибутов, чтобы с ними было легче работать:
trait_type_map = {} for i in attributes: if i.name in trait_type_map.keys(): trait_type_map[i.name][i.value] = i.trait_count else: trait_type_map[i.name] = {i.value: i.trait_count}
{'Background': {'Aquamarine': 2, 'Army Green': 1, 'Blue': 1, 'Gray': 1, 'Orange': 2, 'Purple': 2, 'Yellow': 1}, 'Clothes': {'Bayc T Red': 1, 'Bone Necklace': 1, 'Navy Striped Tee': 1, 'Striped Tee': 1, 'Stunt Jacket': 1, 'Tweed Suit': 1, 'Vietnam Jacket': 1, 'Wool Turtleneck': 1}, ...
Теперь мы можем легко получить доступ к каждому типу признака (например, фон) и значению признака (например, синий).
Затем мы вычисляем редкость , используя формулу редкости , и , наконец , вычисляем ранг каждого NFT, упорядочивая NFT в соответствии с rarity_score
.
С нашими 10 NFT вы должны получить следующий рейтинг и оценку редкости в администраторе Django:
🔚 Заключение
Поздравляем, теперь у вас есть рабочий чекер редкости NFT. На данный момент вы можете улучшить проект следующими способами:
- Используйте Celery для извлечения данных NFT, потому что если вы получите все 10000 NFT, он будет тайм-аут, если выполняется синхронно.
- Создайте пользовательский интерфейс вокруг моделей - добавьте гламура, чтобы можно было продать за ахуенные бабки свой чекер
Канал: https://t.me/in_crypto_Info