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

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

Комментарии (2)


  1. MAXH0
    02.09.2022 18:52
    +3

    Ok! То что Вы рассказали слегка интересно.

    А теперь вопросы:

    1.зачем ЭТО? Что я могу сделать из того, что я не могу сделать в WWW?

    1. сколько это стоит? Как сравнить стоимость классических и Web3 решений?

    2. Можно ли Web3 отвязать от уже раскрученных валют и поднять своё замкнутое решение с нуля на независимом форке.

    3. Что будет если на Web3 обратит внимание роскомнадзор или кто то похожий. КАк на Тор или телеграмм


  1. Jorell
    03.09.2022 10:14

    del