Pythonを利用して、スマートコントラクトを操作する方法を解説していきます。
今回は、コントラクトを操作する方法になります。
※ERC-1155のNFT(1枚のイラストに対して複数枚発行されたNFT)を配布する際は、てんでんさん(@ytenden)が作った下記のツールがお勧めです。
https://opensea-tools.app/multi-send
【注意事項】コントラクトを操作する危険性について
本編に入る前に、コントラクトの操作や本ページに記載しているプログラムの実行の危険性について、触れたいと思います。
本当に操作して大丈夫?
もしまだ前編を読んでいないのであれば、まずは、こちらを読んでください!
コントラクトの操作ができる関数は、コントラクトによって自由に名前を定義することができます。
そのため、同じ名前の関数であっても実行される内容が大きく異なる可能性があります。
極端な話ですが、safeTransferとなっている関数を実行したとき、多くの場合は、transferするだけです。
しかし、ウォレットのコインを抜き取る関数が組み込まれている可能性だってあります。
難しいかもしれませんが、必ず操作するコントラクトがどうなっているか確認してから操作しましょう!
※コントラクト読めるエンジニアさんを味方につけるのがオススメです!
コントラクト読むだけでも、実は結構手間がかかったりします!きちんと報酬支払ってあげてくださいね
シークレットキーの使用
Write contractの操作では、シークレットキーを使用します。
このシークレットキーが外部に漏れると、誰でもなんでも操作することが出来てしまいます。
そのため、シークレットキーの取扱には十分気をつけてください。
事前準備(contract address/abiの取得)
事前準備については、前編に記載していますので、ご確認ください。
プログラムの構成について
ここでは、参考までにプログラムの構成を紹介したいと思います。
①.env
ウォレットの情報(シークレットキー)やAPIの情報など、人に知られたくない情報を書いてます。
WALLET1 = "〇〇〇"
WALLET2 = "〇〇〇"
SECRET_KEY = "〇〇〇"
INFURA_URL = "https://mainnet.infura.io/v3/〇〇〇"
ETHERSCAN_API_KEY = "〇〇〇"
POLYGONSCAN_API_KEY = "〇〇〇"
②config.py
.envの定数の呼び出しとよく使う変数を格納しています。
import os
from os.path import join, dirname
from dotenv import load_dotenv
load_dotenv(verbose=True)
dotenv_path = join(dirname(__file__), ".env")
load_dotenv(dotenv_path)
def getWALLET(name):
if name == "〇〇〇":
WALLET = os.environ.get("WALLET1")
elif name == "〇〇〇":
WALLET = os.environ.get("WALLET2")
else:
WALLET = ""
return WALLET
def getNetworkURL(chain):
if chain == "ETH":
network_url = os.environ.get("INFURA_URL")
elif chain == "BSC":
network_url = "https://bsc-dataseed.binance.org/"
elif chain == "POLYGON":
network_url = "https://polygon-rpc.com/"
elif chain == "RINKEBY":
network_url = "https://rinkeby.infura.io/v3/"
elif chain == "MUMBAI":
network_url = "https://rpc-mumbai.matic.today"
else:
network_url = ""
return network_url
ETHERSCAN_API_KEY = os.environ.get("ETHERSCAN_API_KEY")
例えば、main.pyよりETHを引数としてNETWORK URLを呼び出す場合、config.py経由で、.envよりETH用のRPC URL(INFULAのAPIキー)が戻り値として、POLYGONを引数とした場合、config.pyの定数として定めたPOLYGON用のRPC URLが戻り値として返ってくるような構成にしています。
Web3.pyを使って、Write Contractしてみよう!
今回使用するプログラムの全体は、こちらになります。
このプログラムでは、指定した相手にNFTを送付するプログラムになっています。
#解説1
import json
from web3 import Web3
#操作したい自分のウォレットアドレスの情報の設定
wallet_address = "自分のアドレス"
key = "シークレットキー"
#操作したいコントラクトの情報の設定
network_url = "https://polygon-rpc.com/"
contract_address = "0xb405f48F67a06916A366c77bCF42992903ef0619"
json_open = open("abi.json", "r")
abi = json.load(json_open)
#解説2
#コントラクトのファンクションを操作するのに必要な情報の設定
tokenId = 1
from_address = wallet_address
to_address = "送付したい相手のアドレス"
#解説3
#スマートコントラクトに接続
web3 = Web3(Web3.HTTPProvider(network_url))
contract = web3.eth.contract(address=contract_address, abi=abi)
#解説4
#ガス代の設定
maxFeePerGas = "250"
maxPriorityFeePerGas = "50"
#解説5
#トランザクションの送信に必要な情報の設定
nonce = web3.eth.getTransactionCount(from_address)
tx_content = {
"type": "0x2",
"nonce": nonce,
"from": from_address,
"maxFeePerGas": web3.toWei(maxFeePerGas, "gwei"),
"maxPriorityFeePerGas": web3.toWei(maxPriorityFeePerGas, "gwei")
}
#解説6
#トランザクションの送信
tx = contract.functions.safeTransferFrom(from_address,to_address,tokenId).buildTransaction(tx_content)
signed_tx = web3.eth.account.sign_transaction(tx, key)
tx_hash = web3.eth.sendRawTransaction(signed_tx.rawTransaction)
まずは、初期設定を行っていきます。
import json
from web3 import Web3
#1 操作したい自分のウォレットアドレスの情報の設定
wallet_address = "自分のアドレス"
key = "シークレットキー"
#2 操作したいコントラクトの情報の設定
network_url = "https://polygon-rpc.com/"
contract_address = "0xb405f48F67a06916A366c77bCF42992903ef0619"
json_open = open("abi.json", "r")
abi = json.load(json_open)
- 操作したい自分のウォレットアドレスの情報を入力します。
※何度も言いますが、シークレットキーの扱いには十分に注意してください! - 操作したいコントラクトの情報
こちらについては、前編同様のコントラクトを参考に使っています。
次に、コントラクトのファンクションを操作するのに必要な情報を設定していきます。
まずは、操作するコントラクトのファンクションをチェックしていきましょう!
今回操作するコントラクトは、前編同様こちらになります。
https://polygonscan.com/address/0xb405f48f67a06916a366c77bcf42992903ef0619
『Contract』を押して、『Write Contract』を押してください。
今回操作するファンクションは、『safeTransferFrom』になります。
そのため、『safeTransferFrom』の操作に必要な引数は、『from』、『to』、『tokenId』の3つになります。
ちなみに今回のコントラクトには、『safeTransferFrom』のファンクションが2つあります。
引数の数で自動判別してくれるので、特段気にすることはありません。
#解説2
#コントラクトのファンクションを操作するのに必要な情報の設定
tokenId = 1
from_address = wallet_address
to_address = "送付したい相手のアドレス"
といことで、コントラクトのファンクションを操作するのに必要な引数の情報を入力していきましょう!
#解説3
#スマートコントラクトに接続
web3 = Web3(Web3.HTTPProvider(network_url))
contract = web3.eth.contract(address=contract_address, abi=abi)
スマートコントラクトに接続します。
#解説4
#ガス代の設定
maxFeePerGas = "250"
maxPriorityFeePerGas = "50"
次にガス代の設定です。
今回は、ロンドンフォーク以降のEIP-1559に対応したガス代の設定になります。
- BaseFeePerGas:最低限のガス代になります。
- PriorityFeePerGas:トランザクションを処理してくれるマイナーへ支払われるチップになります。
- maxFeePerGas:BaseFeePerGas+PriorityFeePerGas
EIP-1559は、以前のレガシートランザクションと異なり、高いガス代を設定してもお釣りが返ってくる仕組みになっています。
しかし、最適なガス代にはなりません。
ガス代については、Polygonscanでも確認することが出来ます。
Polygonscanを開き、ガスステーションのマークをクリックすると、上記のような画面に切り替わります。
赤枠の中を見てもらうと、採用すべき数値がわかるかと思います。
例えば、30秒から60秒で処理してほしい場合は、BaseFeePerGasは0、PriorityFeePerGasは39.3という数値を採用することになります。
※過去の実績が表示されているので、この通りの数字を採用したからといって必ず通るわけではありません。
また、下記URLを参照することで、少しでも最適な数値を採用することが出来ます。
https://www.blocknative.com/gas-estimator?gasType=ethereum
最適なガス代の設定はチェーンによっても大きく異なります。
メインネット(ethチェーン)だと、ガス代の設定なしでもトランザクションの作成が出来たりします。
詳しくは、別記事で紹介する予定なので、そちらを参照してください!
#解説5
#トランザクションの送信に必要な情報の設定
nonce = web3.eth.getTransactionCount(from_address)
tx_content = {
"type": "0x2",
"nonce": nonce,
"from": from_address,
"maxFeePerGas": web3.toWei(maxFeePerGas, "gwei"),
"maxPriorityFeePerGas": web3.toWei(maxPriorityFeePerGas, "gwei")
}
次に、トランザクションの送信に必要な情報を設定していきます。
※EIP-1559を採用する際の書き方です。あえてレガシートランザクションを作る必要はないかと思います。
#解説6
#トランザクションの送信
tx = contract.functions.safeTransferFrom(from_address,to_address,tokenId).buildTransaction(tx_content)
signed_tx = web3.eth.account.sign_transaction(tx, key)
tx_hash = web3.eth.sendRawTransaction(signed_tx.rawTransaction)
シークレットキーを用いて、トランザクションを送信します。
最後に、送信したトランザクションのハッシュを取得しています。
取得したハッシュ値をpolygonscanで検索すると、トランザクションが通ってるか確認することが出来ます。
ここまで読んでもらった方は分かったと思いますが、コントラクトの操作自体は非常に簡単ですよね~
小ネタ1:連続処理について
複数枚のNFTの送付等を行う際は、『nance』を操作して一括で処理していく方法もあるのですが
Polygonチェーンでは、最近ガス代が乱高下していることもあり、私は一つ一つ処理していく方法を使っています。
とはいえ、1回1回起動するのはめんどくさいので、トランザクションが通ったら次のトランザクションに移行するようなプログラムを組み込んでいます。
while True:
owner = contract.functions.ownerOf(tokenId).call()
if owner == to_address:
break
else:time.sleep(30)
return web3.toHex(tx_hash)
ここでは、トランザクションが正常に処理されたかどうかを確認するのに、オーナーが変更されたかどうかを確認しています。
オーナーが変更されていなければ、待機して、再度確認するプログラムになっています。
待機時間を30秒に設定していますが、待機時間については、ガス代をいくらに設定するかによって変更しても良いかと思います。
小ネタ2:token Idについて
配布するNFTをランダムにしたい場合に使っています。
while True:
tokenId = random.randint(1,1000)
owner = contract.functions.ownerOf(tokenId).call()
if owner == from_address:
break
return tokenId
仕組みは至って簡単で、token Idをランダムに抽出し、そのNFTのオーナーを取得します。
そのオーナーが配布者のアドレスと一致しているか確認し、一致していればtokenIdを取得するプログラムになっています。
まとめ
今回は、NFTのGiveaway用bot作成に必要な内容に特化し、解説させていただきました。
NFTに限らずIDO参加やNFTmint用のbotなども簡単に作ることが出来るので、ぜひ挑戦してみてくださいね!