Привет, Хабр! Начиная знакомиться с 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/