Как стать автором
Обновить

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

Время на прочтение19 мин
Количество просмотров62K

Привет, Хабр! Начиная знакомиться с 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. Больше постов ищите тут.

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

UPD: а вот и она https://habr.com/ru/post/699560/

Теги:
Хабы:
+6
Комментарии4

Публикации

Истории

Работа

Python разработчик
142 вакансии
Data Scientist
63 вакансии

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн