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" }
ジェネシスファイル初期化
$ 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"
> 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.json
の difficulty
を "0x4000"
にして再生成します。
いや最初からやろう
ここから記事変えましたこっち→「Ethereum入門」
ジェネシスファイル作成
ジェネシスファイルとはジェネシスブロックの情報を書いたjson形式ファイル。
vimでもGUIでも。内容は以下。
{ "config": { "chainId": 15 }, "nonce": "0x0000000000000042", "timestamp": "0x0", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "", "gasLimit": "0x8000000", "difficulty": "0x4000", "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x3333333333333333333333333333333333333333", "alloc": {} }
ジェネシスブロック初期化
$ 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
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: [] }
difficulty
が genesis.json
に指定したものになっているはずです。(ただし16進表記から10進表記に変換されています。)
らしいです。
アカウント作成
とりあえずふたつ作成。本にもあったようにイーサはUTXOモデルではなくアカウントモデルなので、ワレットを作成するビットコインとは違い作成するのはアカウント。
> personal.newAccount("passs01") "0x101d15f8aecd0f52fad90163cefc004a28e83459"
> personal.newAccount("passs02") "0x834ca6744325e350755adaad43d4828fe0ecf011"
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
のはず。でも mining
は false
だし止まってるはず...?
でも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(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
送金
いざ送金。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
ジェネシスファイルを書き換え
{ "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"
完璧じゃん
残高照会
> web3.fromWei(eth.getBalance(eth.accounts[0]),"ether") 1176.000021 > web3.fromWei(eth.getBalance(eth.accounts[1]),"ether") 1014.999979
etherbase
を accounts[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; } }
コピペしたら次のステップ...なんだけどコンパイルしないと進めないらしい。でもコンパイル画面行くと「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()して対象アカウントアンロック
画像のように設定
transactするとトークンが発行される
うおおおおおおおお
とりあえず以上。疲れたからまたまとめよう。もう4時だし。
ERC721-NonFungibleTokenを発行してみる
いよいよだ
Remixめんどそうなのでローカルでコントラクト作って実行します。
コントラクトの作成
基本はERC20の時と同じ。erc20.sol
を erc721.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" }
ファイル構成はこんな感じ
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から拾ってきて置いたら動いた。けどその後のコントラクトアカウント生成がうまくいかん。もう知らん。