Web3.0 на Python, часть 1: основы

от автора

Привет, Хабр! Начиная знакомиться с Web3, было сложно найти в одном месте понятные примеры базовых операций на Web3Py. Например: просмотр баланса, отправка транзакций, минтинг NFT, взаимодействие с контрактами и тд. В этой статье я попытался собрать примеры, которые покрывают > 90% потребностей для разработки бэкенда для web3 приложений. Кстати, все примеры будут применимы и для Web3.js с поправкой на название методов и синтаксис.

Анонс второй части статьи, в которой речь пойдет о более продвинутых примерах с оптимизацией запросов к нодам и сокращении числа этих самых запросов, будет у меня в канале.

А здесь же рассмотрим следующие темы:

Все эти примеры удобно воспроизводить в jupyter-notebook. Предварительно нужно установить Web3Py и завести себе кошелёк, например, MetaMask, чтоб у вас был ваш адрес.

Подключение к блокчейну ⛓️

Подключиться к блокчейну не значит, что нужно локально выкачивать себе все данные с него, нужно всего лишь подключиться к одной из его нод (копий). Можно сделать это 3 способами с помощью специального URL к ноде:

  • HTTP

  • WebSocket

  • RPC

Где взять URL?

Существует отличный сервис, который собрал все бесплатные ссылки на бесплатные ноды в одном месте — chainlink. Очевидный минус этих нод, что их используют все кому не лень. Из-за этого они не всегда выдерживают нагрузку и могут отвечать дольше нужного или не отвечать вовсе.

Если хотите свою приватную ноду за $, то стоит посмотреть на сервисы Infura и Ankr, во втором намного больше сетей.

Будем испытывать Web3 на сети Testnet Binance Smart Chain (BSC).

from web3 import Web3  binance_testnet_rpc_url = "https://data-seed-prebsc-1-s1.binance.org:8545/" web3 = Web3(Web3.HTTPProvider(binance_testnet_rpc_url)) print(f"Is connected: {web3.isConnected()}")  # Is connected: True # С подключением вас ?

Подключившись к ноде, можно посмотреть некоторые ее параметры:

print(f"gas price: {web3.eth.gas_price} BNB")  # кол-во Wei за единицу газа print(f"current block number: {web3.eth.block_number}") print(f"number of current chain is {web3.eth.chain_id}")  # 97
Почему так много цифр в цене газа или что такое Wei?

В Web3 все числа измеряются в минимально возможной единице измерения, т.е. Wei. Это как если бы у нас все измерялось не в рублях, а в копейках, т.е, Ether = 10^{18} Wei. Также еще распространена единица измерения Gwei, Ether = 10^{10} Gwei. Можно поиграться вот тут.

Смотрим баланс кошелька ?

wallet_address = "0x2a647559a6c5dcb76ce1751101449ebbc039b157"  # ваш адрес balance = web3.eth.get_balance(wallet_address) print(f"balance of {wallet_address}={balance}") # InvalidAddress: Web3.py only accepts checksum addresses

Код выше выбросит ошибку, т.к. адрес не является checksum адресом.

Что такое Checksum Address?

Checksum адрес отличается от не checksum только тем, что некоторые буквы в адресе будут в верхнем регистре. Checksum address нужен для того, чтобы убедиться, что адрес валиден и не содержит опечаток. Поэтому все функции в Web3Py принимают только его.

А вот уже правильный способ посмотреть баланс. В этом случае мы используем функцию Web3.toChecksumAddress для перевода адреса в checksum адрес.

wallet_address = "0x2a647559a6c5dcb76ce1751101449ebbc039b157"  # ваш адрес checksum_address = Web3.toChecksumAddress(wallet_address) balance = web3.eth.get_balance(checksum_address) print(f"balance of {wallet_address}={balance} Wei")
Как пополнить баланс в Testnet сетях?

Для каждой сети есть специальный сервис, называемый faucet (кран), который с определенным лимитом может пополнять ваш баланс. Вот, например, для Testnet BSC https://testnet.binance.org/faucet-smart

Балансы также отдаются в Wei. Чтобы посмотреть в более привычном нам формате (ether), можно использовать встроенные в Web3Py функции перевода из одной единицы измерения в другую.

balance = 1000000000000000000  # 18 нулей, 1 BNB  ether_balance = Web3.fromWei(balance, 'ether')  # Decimal('1') gwei_balance = Web3.fromWei(balance, 'gwei')  # Decimal('1000000000') wei_balance = Web3.toWei(ether_balance, 'ether')  # 1000000000000000000

Вторым параметром в функции Web3.fromWei является система измерений, в какую нужно перевести. Не пугайтесь, что она называется ether в сети BSC, это универсальное название, не зависящее от сети.
А в функции Web3.toWei второй параметр — это система измерений, из которой нужно переводить.

Отправка нативной валюты ?

Исполним транзакцию, которая отправляет нативную валюту сети (в нашем случае BNB) на другой адрес. Отправка транзакции состоит из 3 шагов:

  1. Создание (build) транзакции.

  2. Подпись с помощью приватного ключа.

  3. Отправка.

from typing import Optional from hexbytes import HexBytes  # не переживаем, что адрес одинаковый my_address = '0x2A647559a6c5dcB76ce1751101449ebbC039b157'  # checksum someone_address = '0x2A647559a6c5dcB76ce1751101449ebbC039b157'  # checksum  # эти 2 строчки нужны только для получения приватника из мнемонической фразы # при повторном выполнении возникнет ошибка # выполняйте один раз в момент инициализации Web3 web3.middleware_onion.inject(geth_poa_middleware, layer=0) web3.eth.account.enable_unaudited_hdwallet_features()  # Можно получить, например, из MetaMask. KEEP IN SECRET MNEMONIC = 'eight adult sketch quit divide ...' # Не используйте такой способ получения приватника в продакшене account = web3.eth.account.from_mnemonic(MNEMONIC) private_key = account.privateKey  # hex адрес   # 1. функция для build'a транзакции def build_txn(   *,   web3: Web3,   from_address: str,  # checksum адрес   to_address: str,  # checksum адрес   amount: float,  # например, 0.1 BNB ) -> dict[str, int | str]:   # цена газа     gas_price = web3.eth.gas_price          # количество газа     gas = 2_000_000  # ставим побольше      # число подтвержденных транзакций отправителя     nonce = web3.eth.getTransactionCount(from_address)      txn = {       'chainId': web3.eth.chain_id,       'from': from_address,       'to': to_address,       'value': int(Web3.toWei(amount, 'ether')),       'nonce': nonce,        'gasPrice': gas_price,       'gas': gas,     }     return txn   transaction = build_txn(   web3=web3,   from_address=my_address,   to_address=to_address,   amount=0.1, )   # 2. Подписываем транзакцию с приватным ключом signed_txn = web3.eth.account.sign_transaction(transaction, private_key)   # 3. Отправка транзакции txn_hash = web3.eth.sendRawTransaction(signed_txn.rawTransaction)  # Получаем хэш транзакции # Можно посмотреть статус тут https://testnet.bscscan.com/ print(txn_hash.hex())

Тут важно правильно указать параметр gas. Если поставить слишком маленький gas, то для транзакции его может просто не хватить, и она сфейлиться. В другую сторону можно ошибаться, т.к. лишний газ, не используемый в транзакции, просто не будет потрачен.

А сейчас поймём, как же посмотреть статус только что отправленной нами транзакции.

В Web3Py есть два метода получить транзакцию по txn_hash, немного отличающиеся выходными данными:

  1. get_transaction

  2. get_transaction_receipt

  1. get_transaction

txn_hash = '0x2c1c5e4bfa407bec664d944a11282cc19c3ca58ddc2a256861eefa4ca7667f6d' txn = web3.eth.get_transaction(txn_hash) # AttributeDict({'blockHash': HexBytes('0x63f7206918f35a1d4bb17f48f0e71a1bfbe0995057af879b74be5f1b13e0efad'), #  'blockNumber': 20683950, #  'from': '0x2A647559a6c5dcB76ce1751101449ebbC039b157', #  'gas': 2000000, #  'gasPrice': 10000000000, #  'hash': HexBytes('0x2c1c5e4bfa407bec664d944a11282cc19c3ca58ddc2a256861eefa4ca7667f6d'), #  'input': '0x', #  'nonce': 54, #  'to': '0x2A647559a6c5dcB76ce1751101449ebbC039b157', #  'transactionIndex': 2, #  'value': 100000000000000000, #  'type': '0x0', #  'v': 229, #  'r': HexBytes('0x5ec0dda4490a67289f52b22879ea93de0aa8bcc00002342e0d42262c147b11b1'), #  's': HexBytes('0x0944b0ebef4a2624e5e961b30cbe0118833b745e88ac10bb0e33f248bb23da35')})

Возвращается много полезных параметров: в какой блок попала транзакция (blockNumber), откуда и кому отправляли, сколько отправляли, сколько газа было указано в транзакции.

Если мы попытаемся получить транзакцию в тот момент, когда она еще не смайнилась, то получим ошибку web3.exceptions.TransactionNotFound.

  1. get_transaction_receipt

txn_hash = '0x2c1c5e4bfa407bec664d944a11282cc19c3ca58ddc2a256861eefa4ca7667f6d' txn_receipt = web3.eth.get_transaction_receipt(txn_hash) # AttributeDict({'blockHash': HexBytes('0x63f7206918f35a1d4bb17f48f0e71a1bfbe0995057af879b74be5f1b13e0efad'), #  'blockNumber': 20683950, #  'contractAddress': None, #  'cumulativeGasUsed': 79047, #  'from': '0x2A647559a6c5dcB76ce1751101449ebbC039b157', #  'gasUsed': 21000, #  'logs': [], #  'logsBloom': HexBytes('0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), #  'status': 1, #  'to': '0x2A647559a6c5dcB76ce1751101449ebbC039b157', #  'transactionHash': HexBytes('0x2c1c5e4bfa407bec664d944a11282cc19c3ca58ddc2a256861eefa4ca7667f6d'), #  'transactionIndex': 2, #  'type': '0x0'})

Тут многие параметры повторяются, но есть и отличные от пункта 1. Например, gasUsedсколько газа было фактически использовано в транзакции. Status является очень важным параметром. Если у него значение 1, то транзакция прошла успешно. Если 0, то транзакция была отклонена EVM.

Смарт-контракты ?

Можно воспринимать контракт как некую программу, которая есть внутри блокчейна и которая может с ним взаимодействовать. Интерфейс взаимодействия задаётся с помощью ABI — списка словарей, описывающих каждую функцию контракта. Функции контракта можно разделить на 2 категории:

  1. Read operations

  2. Write operations

Первая категория содержит в себе функции, которые не изменяют состояния блокчейна и которые являются бесплатными для нас. Можно воспринимать их как GET запросы.
Вторая категория изменяет состояние блокчейна и требует исполнения транзакций с затратами нативной валюты на газ. По аналогии это POST запросы.

ERC20 токены ?

Почему ERC20 токены называются именно так?

«ERC» расшифровывается как “Ethereum Request for Comments”, т.е. по факту пулл реквест для улучшения сети Ethereum. А «20» — просто id этого реквеста.

ERC20 токены, например, USDT, USDC, BUSD и тд тоже являются смарт-контрактами. Все эти токены имеют единый ABI. Начнем с инициализации контракта.

import json  # одинаковый для всех ERC20 токенов ERC20_ABI = json.loads('''[{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"uint256","name":"_initialSupply","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"decimals_","type":"uint8"}],"name":"setupDecimals","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]''')  # USDT токен usdt_contract_address = '0xA11c8D9DC9b66E209Ef60F0C8D969D3CD988782c'  # инициализация USDT контракта usdt_contract = web3.eth.contract(usdt_contract_address, abi=ERC20_ABI)  # просмотр всех возможных функций all_functions = usdt_contract.all_functions() print(f"Все функции ERC20 токена:\n{all_functions}")

Все функции можно посмотреть и в сканере.

А ниже примеры вызова Read operations (бесплатных функций) из этого контракта. Интерфейс для вызова функций следующий:

contract.functions.<function_name>(*params).call()

user_address = '0x2A647559a6c5dcB76ce1751101449ebbC039b157' some_contract_address = '0x64544969ed7EBf5f083679233325356EbE738930'  token_name = usdt_contract.functions.name().call()  balance_of_token = usdt_contract.functions.balanceOf(   user_address).call()  # in Wei  token_symbol = usdt_contract.functions.symbol().call()  token_decimals = usdt_contract.functions.decimals().call()  allowance = usdt_contract.functions.allowance(   some_contract_address, user_address).call()  ether_balance = balance_of_token/ 10 ** token_decimals print(f"Balance of {token_name}({token_symbol}) is {ether_balance}") print(f"Allowance for {some_contract_address} is {allowance}")  # Balance of USDT(USDT) is 196.0 # Allowance for 0x64544969ed7EBf5f083679233325356EbE738930 is 0

Остановимся тут на двух моментах.

Во-первых, нужно заметить, что в отличие от нативной валюты (BNB в BSC, ETH в Ethereum, MATIC в Polygon), у которых decimals=18, токены могут иметь другое значение decimals. В нашем случае у USDT это 6. Более того, decimals в разных сетях у одного и того же токена может быть разным. Например, USDC в BSC имеет decimals=18, а в Ethereum decimals=6.

Во-вторых, очень важно сказать про allowance. Allowance — сколько другой контракт (some_contract_address) может потратить ваших (user_address) USDT. Например, это понадобится, когда вы захотите обменять свои токены на обменнике. Контракт, который будет совершать обмен, как раз и запросит у вас approve (разрешение), чтобы потратить ваши токены. Просят approve либо на точное количество обмениваемых вами токенов, либо на так называемый бесконечный allowance, т.е. 2^256 — 1 токенов.

Посмотрим, как же отправлять свои токены на другой адрес и как давать allowance другому контракту, т.е. сделаем Write operations.

  1. Отправка 1 USDT

# не пугайтесь, что они одинаковые # отправим сами себе ? user_address = '0x2A647559a6c5dcB76ce1751101449ebbC039b157' someone_address = '0x2A647559a6c5dcB76ce1751101449ebbC039b157'  dict_transaction = {   'chainId': web3.eth.chain_id,   'from': user_address,   'gasPrice': web3.eth.gas_price,   'nonce': web3.eth.getTransactionCount(user_address), } usdt_decimals = usdt_contract.functions.decimals().call() one_usdt = 1 * 10 ** usdt_decimals  # отправляем 1 USDT  # создаём транзакцию transaction = usdt_contract.functions.transfer(     someone_address, one_usdt ).buildTransaction(dict_transaction)  # подписываем signed_txn = web3.eth.account.sign_transaction(transaction, private_key)  # Отправляем, смотрим тут https://testnet.bscscan.com/ txn_hash = web3.eth.sendRawTransaction(signed_txn.rawTransaction) print(txn_hash.hex())
  1. Даём approve другому контракту на «бесконечный» allowance. Контракт с адресом some_contract_address сможет сколько угодно (почти) тратить ваших USDT. Зачем это нужно, объяснил чуть выше.

user_address = '0x2A647559a6c5dcB76ce1751101449ebbC039b157' some_contract_address = '0x64544969ed7EBf5f083679233325356EbE738930'  dict_transaction = {     'chainId': web3.eth.chain_id,      'gas': 210000,      'gasPrice': web3.eth.gas_price,     'nonce': web3.eth.getTransactionCount(user_address), }  approve_amount = 2 ** 256 - 1  # Создаем транзакцию transaction = usdt_contract.functions.approve(     some_contract_address, approve_amount ).buildTransaction(dict_transaction)  # Подписываем signed_txn = web3.eth.account.signTransaction(transaction, private_key)  # Отправляем, смотрим тут https://testnet.bscscan.com/ txn_hash = web3.eth.sendRawTransaction(signed_txn.rawTransaction) print(txn_hash.hex())

NFT ?

Наконец-то добрались до темы NFT.
Я вас обрадую: вы уже умеете взаимодействовать с NFT, если освоили предыдущие два пункта с read operations и write operations для токенов. Потому что NFT это те же смарт-контракты. Единственное «но» — для NFT нет единого ABI стандарта вроде ERC20_ABI, поэтому у каждой коллекции будет собственный интерфейс для взаимодействия.

В примерах с NFT переключимся на сеть Testnet Polygon и рассмотрим Battle Shroom NFT. ABI можно найти тут, внизу страницы.

import json from web3 import Web3  polygon_testnet_rpc_url = "https://matic-mumbai.chainstacklabs.com" web3 = Web3(Web3.HTTPProvider(polygon_testnet_rpc_url)) print(f"Is connected: {web3.isConnected()}")  NFT_ABI = json.loads("""[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"_botHolders","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"_paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"_presalePaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"_whiteListed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newHolder","type":"address"}],"name":"addBotHolder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_newBotHolders","type":"address[]"}],"name":"addBotHolderMany","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_wl","type":"address"}],"name":"addWL","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_wls","type":"address[]"}],"name":"addWLMany","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getDiscount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"giveAway","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"addresses","type":"address[]"}],"name":"giveAwayMany","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"addresses","type":"address[]"}],"name":"migrateMany","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_addr","type":"address"}],"name":"migrateOne","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"mintPresaleShroom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"num","type":"uint256"}],"name":"mintShroom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"oldWalletOfOwner","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"val","type":"bool"}],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"val","type":"bool"}],"name":"presalePause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_wl","type":"address"}],"name":"removeWL","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_wls","type":"address[]"}],"name":"removeWLMany","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"baseURI","type":"string"}],"name":"setBaseURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newDiscount","type":"uint256"}],"name":"setDiscount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newReserved","type":"uint256"}],"name":"setGiftReserved","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newMaxPerTx","type":"uint256"}],"name":"setMaxPerTx","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newMax","type":"uint256"}],"name":"setMaxSupply","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newReserved","type":"uint256"}],"name":"setPresaleReserved","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newPrice","type":"uint256"}],"name":"setPrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"walletOfOwner","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdrawAll","outputs":[],"stateMutability":"payable","type":"function"}]""")  nft_address = '0x1640B6BF576Ece2e7467C009bd1705408766D976' nft_contract = web3.eth.contract(nft_address, abi=NFT_ABI)

Посмотрим название NFT из контракта и сколько же она стоит.

# функция получения цены, может называться иначе в других NFT wei_price = nft_contract.functions.getPrice().call()  matic_price = float(Web3.fromWei(wei_price, 'ether')) nft_name = nft_contract.functions.name().call()  print(f"Price of {nft_name} is {matic_price} MATIC") # Price of Battle Shrooms Gen One is 0.1 MATIC

И наконец-то сминтим/купим/выпустим себе NFT.

user_address = '0x2A647559a6c5dcB76ce1751101449ebbC039b157'  # отслыаем контракту NFT ровно столько, сколько стоит NFT wei_nft_price = nft_contract.functions.getPrice().call()  dict_transaction = {   'chainId': web3.eth.chain_id,   'from': user_address,   'value': wei_nft_price,    'gasPrice': web3.eth.gas_price,   'nonce': web3.eth.getTransactionCount(user_address), }  number_of_nfts_to_mint = 1 transaction = nft_contract.functions.mintShroom(     number_of_nfts_to_mint ).buildTransaction(dict_transaction)  # Подписываем signed_txn = web3.eth.account.sign_transaction(transaction, private_key)  # Минтим, смотрим тут https://mumbai.polygonscan.com/ txn_hash = web3.eth.sendRawTransaction(signed_txn.rawTransaction) print(txn_hash.hex())

После завершения транзакции ваш адрес должен быть в списке владельцев.

Вот и всё!

Надеюсь, что эти примеры будут полезны новичкам в web3, а тем, кто уже крутится в этой теме, послужит шпаргалкой для copy/paste. Больше постов ищите тут.

А в комментариях напишите, что я упустил и что стоило бы добавить. Возможно, что-то из этого войдет во вторую часть ?


ссылка на оригинал статьи https://habr.com/ru/post/674204/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *