Привет, Хабр! Начиная знакомиться с 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 # С подключением вас ?
Подключившись к ноде, можно посмотреть некоторые ее параметры:
Номер текущего блока
Номер сети (
chain_id)
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. Это как если бы у нас все измерялось не в рублях, а в копейках, т.е, . Также еще распространена единица измерения 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 шагов:
Создание (build) транзакции.
Подпись с помощью приватного ключа.
Отправка.
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, немного отличающиеся выходными данными:
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.
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 категории:
Read operations
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
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())
Даём
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. Больше постов ищите тут.
А в комментариях напишите, что я упустил и что стоило бы добавить. Возможно, что-то из этого войдет во вторую часть ?
UPD: а вот и она https://habr.com/ru/post/699560/