ERC721でトークン発行

About

ERC721を使ってトークンを作成・発行してみよう。とした備忘録ログ。

参考

やっていこう

参考の記事を上からなぞっていくよ

Gethインストール・起動

GethとはGo Ethereumの略でイーサリアム用のクライアントのこと。実装は名前の通りGo lang。

Gethインストール
$ brew tap ethereum/ethereum $ brew install ethereum

Alethがbrewからバイバイされてるでって警告でる。調べた感じC++用ライブラリっぽいからとりあえずは置いておこう。必要になったら ここ から。

任意ディレクトリにeth_testnetを作成・移動
$ cd (任意パス) $ mkdir eth_testnet $ cd eth_testnet

バグり散らかして葬り去った過去ログ。見なくていい。
ジェネシスファイル作成

ジェネシスファイルとはジェネシスブロックの情報を書いたjson形式ファイル。

vimでもGUIでも。内容は以下。

{ "config": { "chainId": 10, "homesteadBlock": 0, "eip155Block": 0, "eip158Block": 0 }, "alloc" : {}, "coinbase" : "0x0000000000000000000000000000000000000000", "difficulty" : "0x20000", "extraData" : "", "gasLimit" : "0x2fefd8", "nonce" : "0x0000000000000042", "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp" : "0x00" }
genesis.json
ジェネシスファイル初期化
$ geth --datadir ~/eth_testnet init ./genesis.json
INFO [08-05|23:21:41.226] Maximum peer count ETH=50 LES=0 total=50 INFO [08-05|23:21:41.227] Set global gas cap cap=50,000,000 INFO [08-05|23:21:41.227] Allocated cache and file handles database=/Users/shmn7iii/eth_testnet/geth/chaindata cache=16.00MiB handles=16 INFO [08-05|23:21:41.242] Writing custom genesis block INFO [08-05|23:21:41.243] Persisted trie from memory database nodes=0 size=0.00B time="17.23µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B Fatal: Failed to write genesis block: unsupported fork ordering: eip150Block not enabled, but eip155Block enabled at 0

うんなんかエラー吐いてるね。

エラー対処

Fatal: Failed to write genesis block: unsupported fork ordering: eip150Block not enabled, but eip155Block enabled at 0

該当はこの部分。日本語訳すると「ジェネシスブロックの書き込みに失敗しました:サポートされていないフォーク順序です:EIP150ブロックは有効化されず、EIP155ブロックは0で有効化されました。」

んじゃあEIP150も0にたれってことでjson書き換え:

{ "config": { "chainId": 10, "homesteadBlock": 0, "eip150Block": 0, "eip155Block": 0, "eip158Block": 0 }, "alloc" : {}, "coinbase" : "0x0000000000000000000000000000000000000000", "difficulty" : "0x20000", "extraData" : "", "gasLimit" : "0x2fefd8", "nonce" : "0x0000000000000042", "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp" : "0x00" }

太字緑部分が追記部分。これで同じコマンドを実行すると

INFO [08-05|23:29:36.531] Maximum peer count ETH=50 LES=0 total=50 INFO [08-05|23:29:36.534] Set global gas cap cap=50,000,000 INFO [08-05|23:29:36.535] Allocated cache and file handles database=/Users/shmn7iii/eth_testnet/geth/chaindata cache=16.00MiB handles=16 INFO [08-05|23:29:36.566] Writing custom genesis block INFO [08-05|23:29:36.568] Persisted trie from memory database nodes=0 size=0.00B time="20.481µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B INFO [08-05|23:29:36.569] Successfully wrote genesis state database=chaindata hash=5e1fc7..d790e0 INFO [08-05|23:29:36.569] Allocated cache and file handles database=/Users/shmn7iii/eth_testnet/geth/lightchaindata cache=16.00MiB handles=16 INFO [08-05|23:29:36.583] Writing custom genesis block INFO [08-05|23:29:36.583] Persisted trie from memory database nodes=0 size=0.00B time="3.32µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B INFO [08-05|23:29:36.584] Successfully wrote genesis state database=lightchaindata hash=5e1fc7..d790e0

成功!!

Geth起動
$ geth --networkid 4649 --nodiscover --maxpeers 0 --datadir ./ console 2>> ./geth.log
Welcome to the Geth JavaScript console! ...

と出れば成功。

ETH送金テスト


アカウント作成

geth上で操作。とりあえずふたつ作成。本にもあったようにイーサはUTXOモデルではなくアカウントモデルなので、ワレットを作成するビットコインとは違い作成するのはアカウント。

> personal.newAccount("pass1") "0x2e95c7a44ca7f7ce8fab1beb59e2a6645b175048"
pass1はパスワードを入力。二行目は返答。アカウントIDかな多分。
> personal.newAccount("pass2") "0xe378e90cc7ce921da2a97cf3ffa7ad646f14d6c1"
同様に。
ブロックと残高を確認
> eth.blockNumber 0 > eth.getBalance(eth.accounts[0]) 0 > eth.getBalance(eth.accounts[1]) 0

上から
> ブロック数確認
> ひとつ目のアカウントの残高
> ふたつ目のアカウントの残高

採掘
> miner.start() null

これで採掘が始まります。始まったら止めなきゃね。

> miner.stop() null

止まりました。ではブロック数を確認しましょう。

> eth.blockNumber 0

な ん で な ん

エラー対処

miner.stop()でnull返ってきたから止まってないのかな?なんかパソコンめっさ唸ってるし。ってことで今マイニング中か確認。

参考:「Ethereum入門」

> eth.mining false

違うらしいですはい。

なんか調べたら最初のカウントアップには10分くらいかかるらしい?ので再度miner.start()。

参考:「Ethereum コントラクトの動作を確認するまでの手順」

> miner.start() null

確認

> eth.mining undefined

??????????????

強制終了してもっかいやってみよう。

> miner.stop() null > exit

コンソールを出て終了しました。この瞬間にCPU稼働率が98%→16%へ激減したので裏で何かやってたっぽい?わからん。

再度起動。

$ geth --networkid 4649 --nodiscover --maxpeers 0 --datadir ./ console 2>> ./geth.log

念の為確認しましょう。

> eth.blockNumber 0 > eth.mining false

まあそうですよねよかったです。ではマイニングスタート。

> miner.start() null

どんどんCPUが食われていきます。愉快。確認しよう。

> eth.mining true

キタコレ

> eth.hashrate 97978
現在の採掘処理のハッシュ・レートを確認。ハッシュ・レートがゼロよりも大きければ採掘処理が行われていると考えてよい。

こっちも動いた。よーーし。

ということで eth.blockNumber が0以外を返すまで待機。

10分経過、0です。

20分経過、0です。 eth.mining はちゃんと true

そろそろ40分、0です。hashrateの伸びがすごい遅くなってきてる。

50分、まだ。もう眠い。寝る。現在時刻 2021/08/06 01:05AM

お隣さんうるさくて寝れん。一回チェーンごとリセットかけます。

genesis.jsondifficulty"0x4000" にして再生成します。

いや最初からやろう

ここから記事変えましたこっち→「Ethereum入門」

ジェネシスファイル作成

ジェネシスファイルとはジェネシスブロックの情報を書いたjson形式ファイル。

vimでもGUIでも。内容は以下。

{ "config": { "chainId": 15 }, "nonce": "0x0000000000000042", "timestamp": "0x0", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "", "gasLimit": "0x8000000", "difficulty": "0x4000", "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x3333333333333333333333333333333333333333", "alloc": {} }
genesis.json
ジェネシスブロック初期化
$ geth --datadir ./ init ./genesis.json
INFO [08-06|02:20:34.807] Maximum peer count ETH=50 LES=0 total=50 INFO [08-06|02:20:34.809] Set global gas cap cap=50,000,000 INFO [08-06|02:20:34.809] Allocated cache and file handles database=/Users/shmn7iii/Documents/GitHub/Blockchain-Technical-Introduction/chap9/ERC721/eth_testnet/geth/chaindata cache=16.00MiB handles=16 INFO [08-06|02:20:34.819] Writing custom genesis block INFO [08-06|02:20:34.820] Persisted trie from memory database nodes=0 size=0.00B time="24.618µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B INFO [08-06|02:20:34.821] Successfully wrote genesis state database=chaindata hash=7b2e8b..7e0432 INFO [08-06|02:20:34.821] Allocated cache and file handles database=/Users/shmn7iii/Documents/GitHub/Blockchain-Technical-Introduction/chap9/ERC721/eth_testnet/geth/lightchaindata cache=16.00MiB handles=16 INFO [08-06|02:20:34.836] Writing custom genesis block INFO [08-06|02:20:34.836] Persisted trie from memory database nodes=0 size=0.00B time="7.461µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B INFO [08-06|02:20:34.837] Successfully wrote genesis state database=lightchaindata hash=7b2e8b..7e0432

OK

geth起動
$ geth --networkid "15" --nodiscover --datadir ./ console 2>> ./geth_err.log
・--datadir :本オプションはgethの動作時のブロックチェーンデータや各種ログの出力先を指定します。genesisブロックの初期化で指定したディレクトリと同一のものを指定してください。 ・--networkid "15" :本オプションで任意の正の整数のIDを指定することで、ライブ・ネットとは異なるネットワークを立ち上げることが可能です(ここでは15を指定)。genesisブロックの初期化で指定したchainidと同一の値を指定する必要があります。 ・--nodiscover :Gethはデフォルトで自動的に(同じネットワークID)のEthereumネットワークのノード(Peer)を探し接続を試みます。プライベート・ネットでは未知のノードとの接続を避けるため、このオプションで自動Peer探索機能を無効にします。 ・console:Gethには採掘やトランザクションの生成などを対話的に進めることができるコンソールが用意されています。consoleサブ・コマンドを指定することで、Gethの起動時に同時にコンソール立ち上げることが可能です。なお、consoleサブ・コマンドを付加せずに、Gethのプロセスをバックグラウンドで起動させておき、後からそのプロセスのコンソールを起動する事も可能です(下記TIP参照)。
Welcome to the Geth JavaScript console! ...

と出れば成功。以降はこのコンソールで操作。

ETH送金テスト

ジェネシスブロック確認
> eth.getBlock(0) { difficulty: 16384, extraData: "0x", gasLimit: 134217728, gasUsed: 0, hash: "0x7b2e8be699df0d329cc74a99271ff7720e2875cd2c4dd0b419ec60d1fe7e0432", logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", miner: "0x3333333333333333333333333333333333333333", mixHash: "0x0000000000000000000000000000000000000000000000000000000000000000", nonce: "0x0000000000000042", number: 0, parentHash: "0x0000000000000000000000000000000000000000000000000000000000000000", receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", size: 507, stateRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", timestamp: 0, totalDifficulty: 16384, transactions: [], transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", uncles: [] }

difficultygenesis.json に指定したものになっているはずです。(ただし16進表記から10進表記に変換されています。)

らしいです。

アカウント作成

とりあえずふたつ作成。本にもあったようにイーサはUTXOモデルではなくアカウントモデルなので、ワレットを作成するビットコインとは違い作成するのはアカウント。

> personal.newAccount("passs01") "0x101d15f8aecd0f52fad90163cefc004a28e83459"
引数はパスワード。pass のつもりが passs に誤字った。リターンはアカウントIDかな多分。
> personal.newAccount("passs02") "0x834ca6744325e350755adaad43d4828fe0ecf011"
せっかくなので二人目もpasssにしといた。ちなみにこのパスワード忘れるとガチで詰むらしい。
etherbase設定

etherbaseとは報酬が入るアカウントのこと。

> eth.coinbase "0x101d15f8aecd0f52fad90163cefc004a28e83459"

デフォルトでは最初に作成したアカウントになる。変更する際は以下。

> miner.setEtherbase(eth.accounts[1]) true > eth.coinbase "0x834ca6744325e350755adaad43d4828fe0ecf011"
採掘
> miner.start()

これで採掘が始まる。初回はDAGというデータセット生成のため時間がかかるらしい。

採掘中かどうかを確認するには

> eth.mining true

また、次のコマンドでハッシュレートを確認することでもわかる。

> eth.hashrate 92441

生成されたブロック数を確認するには

> eth.blockNumber 176

すでに176個ブロックが作られている。のでここら辺で採掘を終了させる。

> miner.stop() null

なぜか null が返ってくる。ほんとは true のはず。でも miningfalse だし止まってるはず...?

でもhashrateはなぜか 24196 が返ってくる。uuum.

ブロック内容を覗いてみる

仮に100,101個目のブロックの内容を確認

> eth.getBlock(100) { difficulty: 137514, extraData: "0xd983010a06846765746888676f312e31362e368664617277696e", gasLimit: 121724529, gasUsed: 0, hash: "0xded9810f6404b1d687c7189ea96e70d1c5350377070968f580c7e4614193f10c", logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", miner: "0x834ca6744325e350755adaad43d4828fe0ecf011", mixHash: "0x7bd00d6cb35d903eea07d51a00f8d2927ea3c23f03ff3f6f110ce719bdb38b20", nonce: "0x2ef3045d1e781cd3", number: 100, parentHash: "0x748819780b51e5271ae5dad783a3702c8ce79539c9abef8b1318d4639fde90e1", receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", size: 538, stateRoot: "0x0fe6a300d471c2e35a9be38efb7cfa1a3648a881ba8ca0d70ef0af7a554324f5", timestamp: 1628184946, totalDifficulty: 13443302, transactions: [], transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", uncles: [] } > eth.getBlock(101) { difficulty: 137581, extraData: "0xd983010a06846765746888676f312e31362e368664617277696e", gasLimit: 121605659, gasUsed: 0, hash: "0x32d1e864fd6eeaa6f7d8b62d06ef9e6b2282594e6250ebd891c0202152ba59b0", logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", miner: "0x834ca6744325e350755adaad43d4828fe0ecf011", mixHash: "0xe9c25c7806963959e1fa61ece2e5b96499a9e71ebfb59622b3559a1e1e21e7d0", nonce: "0x58d73d9f08b23ef9", number: 101, parentHash: "0xded9810f6404b1d687c7189ea96e70d1c5350377070968f580c7e4614193f10c", receiptsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", size: 538, stateRoot: "0x77ee470daaeb26e67a77690aa32473d5496e6883e55194845127126e26060a71", timestamp: 1628184947, totalDifficulty: 13580883, transactions: [], transactionsRoot: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", uncles: [] }
一部オレンジ着色

101ブロックの parentHash が100ブロックの hash に一致することがわかる。つまりハッシュチェーンになっている。最高!

報酬の確認

ぐへへ。

> eth.getBalance(eth.accounts[0]) 0 > eth.getBalance(eth.accounts[1]) 1.02e+21

うおお。etherbaseをふたつ目のアカウントに変更していたのでそっちに報酬が入っている。表記が見にくいときは

> web3.fromWei(eth.getBalance(eth.accounts[1]),"ether") 1020
eth.getBalance(address)は"wei"の単位 で持ち高が表示される。上記の変換用のコマンドを使うことで"ether"の単位で表示することが可能。
送金...その前に

前提確認。残高は以下。

> eth.getBalance(eth.accounts[0]) 0 > eth.getBalance(eth.accounts[1]) 1.02e+21

なので accounts[1] から accounts[0] に向けて送金をする。[1] が送金元で [0] が送信先。記事と逆なので注意。

アンロック

送金元について、アカウントのアンロックが必要になる。

> personal.unlockAccount(eth.accounts[1]) Unlock account 0x834ca6744325e350755adaad43d4828fe0ecf011 Passphrase: true
Passphraseでパスワード入力を求められる。「passs02」を入力。
送金

いざ送金。5etherを送ってみる。

> eth.sendTransaction({from: eth.accounts[1], to: eth.accounts[0], value: web3.toWei(5, "ether")}) Error: invalid sender at web3.js:6357:37(47) at web3.js:5091:62(37) at <eval>:1:20(21)

おや?

よく読むとトランザクションはバックグラウンドで採掘してないと処理されないらしい。罠じゃん。

ということで

> miner.start() null

満を辞して

> eth.sendTransaction({from: eth.accounts[1], to: eth.accounts[0], value: web3.toWei(5, "ether")}) Error: authentication needed: password or unlock at web3.js:6357:37(47) at web3.js:5091:62(37) at <eval>:1:20(21)

おんやぁ?

またロックされちゃったのかな?

三度目の正直

> personal.unlockAccount(eth.accounts[1]) Unlock account 0x834ca6744325e350755adaad43d4828fe0ecf011 Passphrase: true > eth.sendTransaction({from: eth.accounts[1], to: eth.accounts[0], value: web3.toWei(5, "ether")}) Error: invalid sender at web3.js:6357:37(47) at web3.js:5091:62(37) at <eval>:1:20(21)

ええ?

調べるとどうやらジェネシスファイルが悪さしてるそう。書き換えましょう。

一旦gethを終了

> miner.stop() null > exit
相変わらずnull

ジェネシスファイルを書き換え

{ "config": { "chainId": 15, "homesteadBlock": 0, "eip150Block": 0, "eip155Block": 0, "eip158Block": 0, "byzantiumBlock": 0, "constantinopleBlock": 0, "petersburgBlock": 0, "istanbulBlock": 0, "berlinBlock": 0 }, "nonce": "0x0000000000000042", "timestamp": "0x0", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "", "gasLimit": "0x8000000", "difficulty": "0x4000", "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x3333333333333333333333333333333333333333", "alloc": {} }
オレンジ太字が追記部分

ジェネシスブロック初期化

$ geth --datadir ./ init ./genesis.json INFO [08-06|03:09:55.308] Maximum peer count ETH=50 LES=0 total=50 INFO [08-06|03:09:55.309] Set global gas cap cap=50,000,000 INFO [08-06|03:09:55.309] Allocated cache and file handles database=/Users/shmn7iii/Documents/GitHub/Blockchain-Technical-Introduction/chap9/ERC721/eth_testnet/geth/chaindata cache=16.00MiB handles=16 INFO [08-06|03:09:55.324] Persisted trie from memory database nodes=0 size=0.00B time="8.605µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B INFO [08-06|03:09:55.325] Successfully wrote genesis state database=chaindata hash=7b2e8b..7e0432 INFO [08-06|03:09:55.325] Allocated cache and file handles database=/Users/shmn7iii/Documents/GitHub/Blockchain-Technical-Introduction/chap9/ERC721/eth_testnet/geth/lightchaindata cache=16.00MiB handles=16 INFO [08-06|03:09:55.343] Persisted trie from memory database nodes=0 size=0.00B time="2.003µs" gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B INFO [08-06|03:09:55.344] Successfully wrote genesis state database=lightchaindata hash=7b2e8b..7e0432

再度起動

geth --networkid "15" --nodiscover --datadir ./ console 2>> ./geth_err.log

採掘開始・アンロック・送金

> miner.start() null > personal.unlockAccount(eth.accounts[1]) Unlock account 0x834ca6744325e350755adaad43d4828fe0ecf011 Passphrase: true > eth.sendTransaction({from: eth.accounts[1], to: eth.accounts[0], value: web3.toWei(5, "ether")}) "0x6a5e91f6eeac029e7de6e7b0c5e76f4be2eb89be9de8aeaa42ee968a58c60b49"
送金のリターンはトランザクションID。valueはweiで指定する必要があるためetherを変換して指定。

完璧じゃん

残高照会
> web3.fromWei(eth.getBalance(eth.accounts[0]),"ether") 1176.000021 > web3.fromWei(eth.getBalance(eth.accounts[1]),"ether") 1014.999979

etherbaseaccounts[0] に変えてたせいで報酬もらいまくって大変なことになってるけど、とりあえず accont[1] に注目。

元は 1020ether だったのが -5.000021ether されて 1014.999979ether になってる。これは送金で -5ether 手数料で -0.000021ether されたためであることがわかる。

これでテストは完了!!!gethは miner.stop() して exit しておこう!!

ERC20-FungibleTokenを発行してみる

まずはERC20を発行してみよう。

rpcオプション付きでgethを起動

rpcオプションをつけることでRemixから接続できるようになる。eth_testnetディレクトリに戻って起動。

$ cd eth_testnet $ geth --networkid 15 --nodiscover --maxpeers 0 --datadir ./ --rpc -rpccorsdomain "*" --rpcaddr "0.0.0.0" console 2>> ./geth_err.log
Remix(browser-solidity)をインストール

Remix(旧称browser-solidity)とはEthereumのコントラクト開発用IDE。gethと連携してデプロイやらしてくれる優れもの。

今回はGitHubからクローンしてインストール。

eth_testnet以外のディレクトリに移動、クローン

$ cd .. $ git clone https://github.com/ethereum/browser-solidity.git

brower-solidity ディレクトリ内の index.html を開くとRemixが開く。

開いたら右側の run タブを開いて Environment を Web3 Provider に変更。二個くらい聞かれるけど両方OKでOK。

コントラクトを作成

Remixの一番左上にある+ボタンから新たにファイルを作る。名前はなんでも。作成したらサンプルコードをベタ貼り。

pragma solidity ^0.4.16; interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; } contract TokenERC20 { // Public variables of the token string public name; string public symbol; uint8 public decimals = 18; // 18 decimals is the strongly suggested default, avoid changing it uint256 public totalSupply; // This creates an array with all balances mapping (address => uint256) public balanceOf; mapping (address => mapping (address => uint256)) public allowance; // This generates a public event on the blockchain that will notify clients event Transfer(address indexed from, address indexed to, uint256 value); // This notifies clients about the amount burnt event Burn(address indexed from, uint256 value); /** * Constrctor function * * Initializes contract with initial supply tokens to the creator of the contract */ function TokenERC20( uint256 initialSupply, string tokenName, string tokenSymbol ) public { totalSupply = initialSupply * 10 ** uint256(decimals); // Update total supply with the decimal amount balanceOf[msg.sender] = totalSupply; // Give the creator all initial tokens name = tokenName; // Set the name for display purposes symbol = tokenSymbol; // Set the symbol for display purposes } /** * Internal transfer, only can be called by this contract */ function _transfer(address _from, address _to, uint _value) internal { // Prevent transfer to 0x0 address. Use burn() instead require(_to != 0x0); // Check if the sender has enough require(balanceOf[_from] >= _value); // Check for overflows require(balanceOf[_to] + _value > balanceOf[_to]); // Save this for an assertion in the future uint previousBalances = balanceOf[_from] + balanceOf[_to]; // Subtract from the sender balanceOf[_from] -= _value; // Add the same to the recipient balanceOf[_to] += _value; Transfer(_from, _to, _value); // Asserts are used to use static analysis to find bugs in your code. They should never fail assert(balanceOf[_from] + balanceOf[_to] == previousBalances); } /** * Transfer tokens * * Send `_value` tokens to `_to` from your account * * @param _to The address of the recipient * @param _value the amount to send */ function transfer(address _to, uint256 _value) public { _transfer(msg.sender, _to, _value); } /** * Transfer tokens from other address * * Send `_value` tokens to `_to` on behalf of `_from` * * @param _from The address of the sender * @param _to The address of the recipient * @param _value the amount to send */ function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { require(_value <= allowance[_from][msg.sender]); // Check allowance allowance[_from][msg.sender] -= _value; _transfer(_from, _to, _value); return true; } /** * Set allowance for other address * * Allows `_spender` to spend no more than `_value` tokens on your behalf * * @param _spender The address authorized to spend * @param _value the max amount they can spend */ function approve(address _spender, uint256 _value) public returns (bool success) { allowance[msg.sender][_spender] = _value; return true; } /** * Set allowance for other address and notify * * Allows `_spender` to spend no more than `_value` tokens on your behalf, and then ping the contract about it * * @param _spender The address authorized to spend * @param _value the max amount they can spend * @param _extraData some extra information to send to the approved contract */ function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns (bool success) { tokenRecipient spender = tokenRecipient(_spender); if (approve(_spender, _value)) { spender.receiveApproval(msg.sender, _value, this, _extraData); return true; } } /** * Destroy tokens * * Remove `_value` tokens from the system irreversibly * * @param _value the amount of money to burn */ function burn(uint256 _value) public returns (bool success) { require(balanceOf[msg.sender] >= _value); // Check if the sender has enough balanceOf[msg.sender] -= _value; // Subtract from the sender totalSupply -= _value; // Updates totalSupply Burn(msg.sender, _value); return true; } /** * Destroy tokens from other account * * Remove `_value` tokens from the system irreversibly on behalf of `_from`. * * @param _from the address of the sender * @param _value the amount of money to burn */ function burnFrom(address _from, uint256 _value) public returns (bool success) { require(balanceOf[_from] >= _value); // Check if the targeted balance is enough require(_value <= allowance[_from][msg.sender]); // Check allowance balanceOf[_from] -= _value; // Subtract from the targeted balance allowance[_from][msg.sender] -= _value; // Subtract from the sender's allowance totalSupply -= _value; // Update totalSupply Burn(_from, _value); return true; } }
言語はSolidity。Notionのコードブロックくん対応してないので元にされてるJavaScriptで代用。

コピペしたら次のステップ...なんだけどコンパイルしないと進めないらしい。でもコンパイル画面行くと「compiler not yet loaded」

仕方ないのでこっちで作業:https://remix.ethereum.org/

するとアンロックがうまくいかない。どうやらパラメーター不足。

こっちで再度起動

$ geth --networkid 15 --nodiscover --allow-insecure-unlock --maxpeers 0 --datadir ./ --rpc -rpccorsdomain "*" --rpcaddr "0.0.0.0" console 2>> ./geth_err.log

したらminer.start()して対象アカウントアンロック

画像のように設定

Image in a image block

transactするとトークンが発行される

Image in a image block

うおおおおおおおお

とりあえず以上。疲れたからまたまとめよう。もう4時だし。

ERC721-NonFungibleTokenを発行してみる

いよいよだ

Remixめんどそうなのでローカルでコントラクト作って実行します。

コントラクトの作成

基本はERC20の時と同じ。erc20.solerc721.sol として新しくコントラクトを作るイメージ。

記事のやつでもいいけど古そうなのでOpenZeppelin公式からサンプルコード拾って改変します。

// contracts/GameItem.sol // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import "@openzeppelin/contracts/utils/Counters.sol"; contract GameItem is ERC721URIStorage { using Counters for Counters.Counter; Counters.Counter private _tokenIds; constructor() ERC721("GameItem", "ITM") {} function awardItem(address player, string memory tokenURI) public returns (uint256) { _tokenIds.increment(); uint256 newItemId = _tokenIds.current(); _mint(player, newItemId); _setTokenURI(newItemId, tokenURI); return newItemId; } }
公式サンプルコード

トークンをアイテムに適用させる感じねこれ。なるほど。改変しよ。

pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import "@openzeppelin/contracts/utils/Counters.sol"; contract TestToken is ERC721URIStorage { using Counters for Counters.Counter; Counters.Counter private _tokenIds; constructor() ERC721("TestToken", "TK") {} function awardItem(address _address, string memory jsonURI) public returns (uint256) { _tokenIds.increment(); uint256 newTokenId = _tokenIds.current(); _mint(_address, newTokenId); _setTokenURI(newTokenId, jsonURI); return newTokenId; } }

名前変えただけ。

eth_testnet/contracts/erc721.solで保存

geth起動
$ geth --networkid 15 --nodiscover --allow-insecure-unlock --maxpeers 0 --datadir ./ --rpc -rpccorsdomain "*" --rpcaddr "0.0.0.0" console 2>> ./geth_err.log

したらRemixで接続も。

json用意
{ "name": "TestToken-1", "description": "This TOKEN is a test for ERC721. No-1.", "image": "https://file.shmn7iii.net/testtoken.png" }
Image in a image block

ファイル構成はこんな感じ

Solidityコンパイラ(solc)インストール

全部実行

$ brew tap ethereum/ethereum $ brew install solidity

確認

$ solc --version solc, the solidity compiler commandline interface Version: 0.8.6+commit.11564f7e.Darwin.appleclang

solcパスが後々必要なのでメモ

$ which solc /usr/local/bin/solc
OpenZeppelin/contracts インストール
$ npm install @openzeppelin/contracts

ライブラリ。

コンパイル
$ solc --abi --bin contracts/erc721.sol Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information. --> contracts/erc721.sol Error: Source "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol" not found: File outside of allowed directories. --> contracts/erc721.sol:3:1: | 3 | import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Error: Source "@openzeppelin/contracts/utils/Counters.sol" not found: File outside of allowed directories. --> contracts/erc721.sol:4:1: | 4 | import "@openzeppelin/contracts/utils/Counters.sol"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

なんでなん

warningはライセンス関係なので一旦無視。エラー内容はインポートがうまくいってなさげ。

contractディレクトリにopenzeppelinのcontractをgithubから拾ってきて置いたら動いた。けどその後のコントラクトアカウント生成がうまくいかん。もう知らん。