実際のDAppがどのように機能するかを示すために、 公式の例に触発されたアプリケーションを検討してください。 投票と任意のトランザクションの実行を伴う民主主義の代わりに、誰でもお金(エーテル)の分配の提案を作成し、参加者が投票し、期限後に投票の結果に応じて提案が履行されるかどうかの簡易チャリティコントラクトを作成します。
この場合のスマートコントラクトのロジックはそれほど重要ではありません。私たちの目標は、ユーザーがブロックチェーンと対話するためのオプションを示すことです。
使用するツールをもう少し詳しく検討し、その後、アプリケーションに直接移動します。
使用済みツール
1.メタマスク
これはChromeブラウザ用のプラグインであり、開発者はFirefoxを計画していると書いていますが、これまでのところChromeのみです。 こちらからダウンロードできます。 プラグインは2つのことを行います。
- リモートホストを介して目的のイーサリアムネットワークへの接続を提供するため、ユーザーはホストを展開する必要がありません。
- ブロックチェーンを操作するための新しいアカウントを作成したり、既存のアカウントをインポートしたりできます。 これにより、ユーザーがローカルに保存する秘密鍵でトランザクションに署名できます。
プラグインの使用方法の詳細については、 公式ページまたは
ネタバレの下
プラグインでは、上部のネットワーク名をクリックして、実際のネットワークとテストネットワークに接続できます。
必要に応じて、既存のキーをインポートできますが、新しいキーを作成する方が便利です。
GethまたはMistで作成されたキーをインポートする場合は、インポート時にJSONファイルを選択し、ディレクトリ〜/ .ethereum / <network> / keystoreで目的のアドレスのファイルを見つけます。 少なくともこの記事の公開時点のUbuntuでは、JSONファイルを開く際にバグがあることに注意してください。ファイルを選択すると、MetaMaskウィンドウが閉じてキーがインポートされません。 この場合、拡張アドレスchrome-extensionを使用して別のタブでMetaMaskを開いてみてください://nkbihfbeogaeaoehlefnkodbefgpgknn/popup.html
ところで、テストネットワークを選択して[購入]を押すと、テストブロードキャストを配信するリソースへのリンクを取得できます。 Ropstenの場合でも(蛇口からエーテル1をクリックするだけで):
必要に応じて、既存のキーをインポートできますが、新しいキーを作成する方が便利です。
GethまたはMistで作成されたキーをインポートする場合は、インポート時にJSONファイルを選択し、ディレクトリ〜/ .ethereum / <network> / keystoreで目的のアドレスのファイルを見つけます。 少なくともこの記事の公開時点のUbuntuでは、JSONファイルを開く際にバグがあることに注意してください。ファイルを選択すると、MetaMaskウィンドウが閉じてキーがインポートされません。 この場合、拡張アドレスchrome-extensionを使用して別のタブでMetaMaskを開いてみてください://nkbihfbeogaeaoehlefnkodbefgpgknn/popup.html
ところで、テストネットワークを選択して[購入]を押すと、テストブロードキャストを配信するリソースへのリンクを取得できます。 Ropstenの場合でも(蛇口からエーテル1をクリックするだけで):
2. MetaMaskプラグインでWeb3.jsライブラリを使用する
Web3.jsはJavaScriptライブラリであり、 前の記事でGethで使用したものと同じです。 MetaMaskは開いているすべてのページにWeb3.jsを埋め込むため、Chrome Developer Toolsのjavascriptコンソールで簡単なコマンドを直接テストできます。 この記事の執筆時点で、Web3.jsの現在のバージョンは0.20.1であることに注意することが重要です。 バージョン0.xxのドキュメントはこちらから入手できます 。バージョン1.0のドキュメントと混同しないでください( リンク )。
2つのコマンドを実行します。1つはデータ(例:アカウント残高)を受信し、2つ目は変更します(たとえば、 前の記事の StringHolderスマートコントラクトで文字列を設定します)。 まず、MetaMaskでアカウントを作成し、目的のネットワーク(この場合はRopsten Test Network)に接続し、Developer Toolsコンソールに移動することを忘れないでください。
> web3.eth.getBalance(web3.eth.accounts[0], function(error, result) { console.log(web3.fromWei(result.toNumber())); } )
コントラクトメソッドを呼び出すときは、コールバック関数を最後の引数として呼び出すことを忘れないでください。 これを行うことができます:
> web3.eth.getBalance(web3.eth.accounts[0], console.log) null e {s: 1, e: 18, c: Array(2)} c:(2) [78950, 84540000000000] e:18 s:1 __proto__:Object
Web3.jsは、数値にBigNumberライブラリを使用します。 上記の例では、回答は変換されずに表示されます。
読み取りコマンドはすぐに実行され、スマートコントラクト(
constant
としてマークされてい
constant
)でデータを変更する関数を実行する場合、MetaMaskはトランザクション署名ウィンドウを表示します。 これについては、 前の記事の StringHolderコントラクトを開き、その中のstringメソッドを呼び出して説明します。
> var address = "0x65cA73D13a2cc1dB6B92fd04eb4EBE4cEB70c5eC"; > var abi = [ { "constant": false, "inputs": [ { "name": "newString", "type": "string" } ], "name": "setString", "outputs": [], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "getString", "outputs": [ { "name": "", "type": "string", "value": "Hello World!" } ], "payable": false, "type": "function" } ]; > var contract = web3.eth.contract(abi); > var stringHolder = contract.at(address) > stringHolder.getString(console.log) null "Hello Dolly!!! 22" > stringHolder.setString("Hello from the other side!", console.log)
setStringメソッドを呼び出した後、トランザクションとガスとエーテルの推定コストに関する情報を確認するよう求めるウィンドウが表示されます。 Submitをクリックします。 コンソールに、トランザクションのハッシュが表示されます。
しばらくして、行が変更されたことを確認します。
> stringHolder.setString("Hello from the other side!", console.log) "0x4252c00ff25b690846ec8ad3b4266047a75a1708153bcac24066da9cb01e6db5" > stringHolder.getString(console.log) null "Hello from the other side!"
すべてが正常に機能します。
3. Ethereum.rb
これは、RubyのEthereumブロックチェーン( githubへのリンク )を操作するためのライブラリです。現時点では、最も積極的にサポートされています。
irbコンソールからStringHolderコントラクトを開いてみましょう。
> require “ethereum.rb” > abi = '[ { "constant": false, "inputs": [ { "name": "newString", "type": "string" } ], "name": "setString", "outputs": [], "payable": false, "type": "function" }, { "constant": true, "inputs": [], "name": "getString", "outputs": [ { "name": "", "type": "string", "value": "Hello World!" } ], "payable": false, "type": "function" } ]' > address = "0x65cA73D13a2cc1dB6B92fd04eb4EBE4cEB70c5eC" > contract = Ethereum::Contract.create(name: "StringHolder", address: address, abi: abi) > contract.call.get_string() “Hello from the other side!”
キャメルケース(getString)は自動的にスネークケース(get_string)に変換されていることに注意してください
ここでのメソッド呼び出しの特徴は、データを取得する必要がある場合にのみ、getterが
call
を介して
call
れることです。 トランザクションでは、同期呼び出しの場合は
transact
が、非同期呼び出しの場合は
transact_and_wait
が必要です。
トランザクションが作成される
set_string
関数を呼び出してみましょう。 2つの方法があります:unlockAccount(非推奨)とトランザクションに署名(詳細は後述)。
> Ethereum::IpcClient.new.personal_unlock_account(Ethereum::IpcClient.new.eth_coinbase["result"], "<>")
トランザクションの送信者(現在、ブロックを解除しているもの)を割り当てる必要があります。
> contract.sender = Ethereum::IpcClient.new.eth_coinbase["result"]
次に、
transact_and_wait
または
transact
代入する
transact_and_wait
call
、
call
代わりにセッターを呼び出すことができます。
> contract.transact_and_wait.set_string(“Hello darkness, my old friend”)
私たちは終わりを待っています、私たちは電話します
> contract.call.get_string()
変更が表示されます-すべてが機能します。
DAppは何をすべきか
問題を定式化します。 アカウントを持つ慈善団体を表す契約が必要です。 この組織には、寄付の受領者と提案の投票者の両方として行動できる登録ユーザーがいる場合があります。 組織のアカウントから登録済みアカウントの1つにお金(エーテル)を転送するオファーを作成する方法が必要です。 一度にすべてのエーテルを取りたくはないように、制限を導入します-提供できるエーテルは1つまでです。 次に投票が行われ(「賛成」または「反対」投票できます)、特定の期限(プロポーザルが作成された瞬間から5分)までには完了できません。 締め切り後も投票は引き続き受け付けられますが、投票を完了する機会があり、「反対」よりも「賛成」で終わる場合は、組織のアカウントから受信者のアカウントに空気を移します。 反対票があれば-何もしません。
一般に、アプリケーションスキームは次のとおりです。
2つのjsモジュール(Blockchain.jsとBlockchainApi.js)は、ブロックチェーンの操作を担当します。 それらは同じことを行います。最初のものはWeb3.jsで動作し、MetaMaskノードを介してブロックチェーンに直接アクセスし、2つ目はRails APIにajaxリクエストを行います。 クライアントアプリケーション自体はReactで記述されており、使用する2つのjsモジュールに依存しません。
チャリティ基本契約
コントラクトは単一ファイルの形式であるため、特別な展開方法は使用しません。少なくともMistを介して 、少なくともGethコマンドラインを使用して、これを行うことができます。 ここからコードを使用できます。 一般的に、彼のスキームは次のように表すことができます。
論理ブロックのCharity.sol契約コードを検討してください。 まず、必要なすべての変数の説明があります。
uint public debatingPeriodInMinutes; // Proposal[] public proposals; // , uint public numProposals; // proposals uint public numMembers; // members mapping (address => uint) public memberId; // members address[] public members; //
マッピングを使用すると、ユーザーのアドレスの配列でインデックスを取得できます。 そのようなアドレスを持つユーザーが存在しない場合、このアドレスを持つユーザーが登録されているかどうかを判断する関数に基づいて、インデックス0が返されます。 しかし、これにより配列の要件が導入されます。ユーザーはインデックス1から始まる配列に格納する必要があります。このロジックを担当するコードについては後で説明します。 一方、オファーを保存するための構造があります。
struct Proposal { address recipient; // uint amount; // string description; // uint votingDeadline; // bool executed; // , bool proposalPassed; // , uint numberOfVotes; // int currentResult; // , ““ = +1, “” = -1 Vote[] votes; // , mapping (address => bool) voted; // , - }
投票の構造は、各文の配列に追加されます
struct Vote { bool inSupport; // address voter; // }
ユーザーが登録されている場合にのみ、追加されるメソッドの実行が可能であることを制御できる修飾子を検討してください。 既に述べたように、検証コードは、存在しないマッピング要素がインデックス
0
与えるという事実に基づいており、インデックス
1
から始まるユーザーを保存します。
modifier onlyMembers { require (memberId[msg.sender] != 0); // _; // }
msg
は、呼び出し元に関する情報を取得できる構造です。 この場合、
msg.sender
は、この修飾子を使用してメソッドを呼び出したアカウントのアドレスです。
デプロイ時に実行されるコントラクトのコンストラクターについて説明します。 設定する必要があるのは、各提案の投票に割り当てられた時間だけです。 さらに、メンバー配列のサイズを増やします。これは、サイズに基づいてユーザーを追加し、ゼロ要素が予約されたままになるためです。
function Charity( uint _minutesForDebate ) payable { // payable , , debatingPeriodInMinutes = _minutesForDebate; members.length++; }
ユーザーを追加する機能:
function addMember(address _targetMember) { if (memberId[_targetMember] == 0) { // 0 , uint id; memberId[_targetMember] = members.length; // , id = members.length++; // members[id] = _targetMember; // } }
require
関数に注意してください-古いバージョンのsolidityの
throw
を置き換えました。
require
に
true
または
false
が渡され
require
、
throw
と同様のハンドラーがトリガーされ、トランザクション全体がロールバックされます。
アドレスがユーザーのリストにあるかどうかを確認するには、次の関数を使用します。
function isMember( address _targetMember ) constant returns ( bool ) { return ( memberId[_targetMember] != 0 ); }
次の機能は、提案を作成することです。寄付の受取人の住所、weiのエーテルの量、および説明行を受け入れます。 onlyMembers修飾子がこの関数に適用されます。つまり、すべてのコードが実行される前に、呼び出し元アカウントが登録されているかどうかがチェックされます。 ここでは、
1 ether
や
1 minutes
などの変換が表示されます。 このようなサフィックスの完全なリストはここで見ることができます 。それらは便宜上作成されており、値には適用できますが、変数には適用できません。 しかし、変数に適用するには、接尾辞に1を追加するだけです。これは、この例では秒に変換するために行われます。
function newProposal( address _beneficiary, // uint _weiAmount, // wei string _description // ) onlyMembers returns (uint proposalID) { require( _weiAmount <= (1 ether) ); // 1 proposalID = proposals.length++; // 1 Proposal storage p = proposals[proposalID]; // p.recipient = _beneficiary; p.amount = _weiAmount; p.description = _description; p.votingDeadline = now + debatingPeriodInMinutes * 1 minutes; // p.executed = false; // p.proposalPassed = false; p.numberOfVotes = 0; numProposals = proposalID + 1; // return proposalID; }
ここで、
now
キーワードは現在の時刻ですが、トランザクションが呼び出された時点ではなく、ブロックが作成された時点に注意してください。 したがって、締め切りは、プロポーザルがブロックチェーンで既に作成された瞬間からカウントされます。
私たちの
proposals
は
public
ているという事実にもかかわらず、この方法では、配列の形式で最も単純なフィールドのみを取得できます。 つまり、コントラクトの
proposals(1)
などのメソッドを呼び出すと、インデックス
1
プロポーザルが配列
{ recipient, amount, description, votingDeadline, executed, proposalPassed, numberOfVotes, currentResult }
れ、構造内の
votes
および
voted
配列は返されません。 しかし、投票を表示するために、または投票する機会を与えるために、ユーザーが特定の提案に投票したかどうかについての情報が必要です。 そして、これを1回の呼び出しで行うことをお勧めします。したがって、音声ステータスとオファーIDを必要とするアカウントを受け入れる特別な関数
getProposal
を使用して、アプリケーションで表示するためにProposal構造を読み取るときにこの情報を取得します。
function getProposal( address _member, uint _proposalNumber ) constant returns ( address, // uint, string, uint, bool, bool, uint, int, int ) { Proposal memory proposal = proposals[ _proposalNumber ]; // int vote = getVoted( _member, _proposalNumber ); // ( ) return ( proposal.recipient, proposal.amount, proposal.description, proposal.votingDeadline, proposal.executed, proposal.proposalPassed, proposal.numberOfVotes, proposal.currentResult, vote ); // }
これは、特定のユーザーが特定の提案に投票した方法を探す補助機能です。 返される値:
0
ユーザーが投票しなかった場合、
1
ユーザーが「賛成」に投票した場合、
-1
投票が「反対」の場合。
function getVoted(address _member, uint _proposalNumber) constant returns(int) { Proposal storage p = proposals[_proposalNumber]; int result = 0; int true_int = 1; int false_int = -1; // for (uint i = 0; i < p.numberOfVotes; i++) { if (p.votes[i].voter == _member) // { result = p.votes[i].inSupport ? true_int : false_int; break; // } } return result; }
投票:特定の番号の提案については、真(賛成)または偽(反対)で投票します。
function vote( uint _proposalNumber, // , bool _supportsProposal // ) onlyMembers returns (uint voteID) { Proposal storage p = proposals[_proposalNumber]; // require (p.voted[msg.sender] != true); // p.voted[msg.sender] = true; // p.numberOfVotes++; // if (_supportsProposal) { // “” p.currentResult++; // 1 } else { // p.currentResult--; // 1 } voteID = p.votes.length++; // p.votes[voteID] = Vote({inSupport: _supportsProposal, voter: msg.sender}); // return p.numberOfVotes; }
最後の関数
executeProposal
は、投票を完了し、受信者のアドレスに
executeProposal
を送信する(または送信しない)機能を
executeProposal
ます。
function executeProposal(uint _proposalNumber) { // Proposal storage p = proposals[_proposalNumber]; require ( !(now < p.votingDeadline || p.executed) ); // 1) , 2) p.executed = true; // if (p.currentResult > 0) { // “” require ( p.recipient.send(p.amount) ); // p.proposalPassed = true; // , } else { // “” p.proposalPassed = false; // , } }
最後に、payable修飾子を持つ空の関数があります。
function () payable {}
これは、契約の住所に空気を送るために必要です。 一般に空の関数は、関数呼び出しではないすべてのメッセージを受信して処理する関数です。 私たちがする必要があるのは、支払い可能にすることです。そうすれば、送られたガスは追加のアクションなしで契約に入金されます。 ただし、この修飾子の他の関数は、この場合、たとえば
addMember
etherを送信できないことに注意してください。
Web3.jsを使用したアプリケーションのバリエーション
主なアプリケーションシナリオ:
- ユーザーはMetaMaskを介してRopstenネットワークに接続します
- アカウントにエーテルがない場合、単一のトランザクションを完了することは不可能です。 エーテルを受け取る機能を追加しました。これは、口座残高が0.1エーテル未満の場合に利用可能になります。 これは、ブロードキャストの転送先アドレスを使用してajaxリクエストが行われるサードパーティサービスを介して実装されます。
- スマートコントラクトを使用した基本アクションは、ユーザーが組織のメンバーになった後にのみ使用できます。 これを行うには、スマートコントラクトのaddMemberメソッドが呼び出されます。
- 組織のメンバーは、資金振替の提案(以下「提案」)を作成するか、既存の提案に投票できます。
- プロポーザルの時間が満了すると(作成時間+ 5分)、プロポーザルを完了することが可能になります。その結果、投票の分配に応じて、ブロードキャストが指定されたアドレスに転送されるかどうかが決まります。
アプリケーションのデモは、 リンク -MetaMaskバージョンで入手できます。
ソースコードはこちらです。
繰り返しますが、Web3.jsの現在のバージョンが0.20.1であるという事実に注目してください。 しかし、バージョン1.0はリリースの準備がすでに整っており、その変更は非常に重要です。 前述したように、MetaMaskはweb3をページに埋め込み、すぐに使用できます。 しかし、ライブラリが活発に開発されており、ユーザーのアプリケーションの操作性を保証する必要があるという事実を考えると、ロックバージョンを使用し、MetaMaskが埋め込むweb3オブジェクトを再定義する必要があります。 ここでは、次の方法でこれを行います。
initializeWeb3() { if (typeof web3 !== 'undefined') { // MetaMask const defaultAccount = web3.eth.defaultAccount; // window.web3 = new Web3(web3.currentProvider); // window.web3.eth.defaultAccount = defaultAccount; // } }
window.onload
イベントの後にこれを行う必要があります。
このコードで解決される非自明な問題の1つは、公式ドキュメントで提案されているように
window.web3 = new Web3(web3.currentProvider)
を単純に作成すると、デフォルトアカウントが選択されないことです。
既に述べたように、MetaMaskでも、リストからネットワークを選択できます。 Ropstenネットワーク上の契約アドレスを使用します。他のネットワーク上のこれらのアドレスに接続しようとすると、結果は予測できません。 したがって、アプリケーションへのアクセスを提供する前に、ユーザーが同じネットワーク上にいるかどうかを確認する必要があります。 次のコマンドを使用して、ネットワークIDを取得できます。
web3.version.getNetwork(function (err, netId) {});
ここでこのチェックを行い、結果をRopstenネットワークのidと比較します-これは3です。
すべてのネットワークのIDのリストは、たとえば、net_versionの説明で見ることができます。
ブロックチェーンを操作するためのすべてのロジックは、 blockchain.jsファイルにあります。
ここには、ブロックチェーンからデータを受信するための関数とブロックチェーン内のデータを変更する関数の2種類の関数があります。 web3.jsのほとんどのメソッドは非同期に実行され、最後のパラメーターとしてコールバックを受け入れます。 多くの場合、データを取得するためにいくつかのメソッドを呼び出す必要があり、一部のメソッドの呼び出しは他のメソッドの結果に依存するため、Promiseを使用すると便利です。 web3.jsのバージョン1.0では、非同期メソッドはデフォルトでプロミスを返します。
ブロックチェーンから情報を取得する1つの例を次に示します。
getCurrentAccountInfo
関数は、現在のアカウントのアドレス、残高、およびこのアカウントが組織のメンバーであるかどうかのフラグを返します。
Blockchain.prototype.getCurrentAccountInfo = function() { const address = this.address; if (address == undefined) { return Promise.resolve({}); } const balancePromise = new Promise(function(resolve, reject) { web3.eth.getBalance(address, function(err, res) { err ? reject(err) : resolve(web3.fromWei(res).toNumber()); }); }); const authorizedPromise = new Promise(function(resolve, reject) { this.contractInstance.isMember(address, function(err, res) { err ? reject(err) : resolve(res); }); }.bind(this)); return new Promise(function(resolve, reject) { Promise.all([balancePromise, authorizedPromise]).then(function(data) { resolve({ address: address, balance: data[0], isMember: data[1] }); }); }); };
次に、ブロックチェーン内のデータを変更する機能、たとえば、参加者を組織に追加する機能を検討します。
Blockchain.prototype.becomeMember = function() { return new Promise(function(resolve, reject) { this.contractInstance.addMember(this.address, function(err, res) { err ? reject(err) : resolve(res); }); }.bind(this)); };
ご覧のとおり、構文は前の例と変わりません。この関数を実行するだけで、ブロックチェーン内のデータを変更するトランザクションが作成されます。
スマートコントラクトの関数を呼び出すと、その結果としてトランザクションが作成され、MetaMaskはこのトランザクションを確認するか拒否するかをユーザーに求めます。 ユーザーがトランザクションを確認すると、関数はトランザクションのハッシュを返します。
明らかでないポイントの1つは、トランザクションが成功したかどうかを確認する方法です。
使用されたガスの量に基づいて、トランザクションのステータスを判断できます。 使用可能な最大量のガスが使用されている場合、実行中にエラーが発生したか、トランザクションを完了するのに十分なガスがありませんでした。 次のようにステータスを確認します。
Blockchain.prototype.checkTransaction = function(transaction) { const txPromise = new Promise(function(resolve, reject) { web3.eth.getTransaction(transaction.transactionHash, function(err, res) { err ? reject(err) : resolve(res); }); }); const txReceiptPromise = new Promise(function(resolve, reject) { web3.eth.getTransactionReceipt(transaction.transactionHash, function(err, res) { err ? reject(err) : resolve(res); }); }); return new Promise(function(resolve, reject) { Promise.all([txPromise, txReceiptPromise]).then(function(res) { const tx = res[0]; const txReceipt = res[1]; const succeeded = txReceipt && txReceipt.blockNumber && txReceipt.gasUsed < tx.gas; const failed = txReceipt && txReceipt.blockNumber && txReceipt.gasUsed == tx.gas; let state = transactionStates.STATE_PENDING; if (succeeded) { state = transactionStates.STATE_SUCCEEDED; } else if (failed) { state = transactionStates.STATE_FAILED; } resolve(state); }); }); };
, localStorage , . — transactions-storage.js .
Ruby on Rails ethereum.rb
- , . , . , , , . - , . . , web3.js, , . Ruby on Rails, ethereum.rb, , web3.js.
— Rails API .
ソースコードはこちらです。
ethereum.rb, , blockchain.js. , , , .
— , . , , LocalStorage , API.
:
- Create Account, , , .
- API auth token .
- .
js , — , . — .
def proposals(address=nil) count = @contract_instance.call.num_proposals to = count - 1 from = count > PROPOSALS_LIMIT ? count - PROPOSALS_LIMIT : 0 res = (from..to).map do |idx| proposal = if address @contract_instance.call.get_proposal(address, idx) else @contract_instance.call.proposals(idx) end Proposal.new(proposal, idx) end res.sort_by(&:index).reverse end
, — , . Eth . .
def self.new_account new_key = Eth::Key.new return new_key.address.downcase, new_key.private_hex end def signed_transactions(private_key_hex) key = Eth::Key.new priv: private_key_hex # @contract_instance.key = key # res = yield(@contract_instance) # @contract_instance.key = nil # res end
signed_transactions
— , .
, Eth.
key
. ethereum.rb
key
. , , , .
signed_transactions
Proposal,
executeProposal
:
def finish_proposal(proposal_index, options={}) tx = signed_transactions(options[:private_key]) do |contract_instance| contract_instance.transact.execute_proposal(proposal_index) end tx.id end
. , js .
def transaction_status(tx_hash) tx = @client.eth_get_transaction_by_hash(tx_hash)['result'] tx_receipt = @client.eth_get_transaction_receipt(tx_hash)['result'] if tx_receipt block_number = tx_receipt['blockNumber'] gas_used = tx_receipt['gasUsed'].to_i(16) gas = tx['gas'].to_i(16) { succeeded: block_number && gas_used < gas, failed: block_number && gas_used == gas } end end
.
- , —
0xe79d8738f0769ec69f09cef4cd497c9cc477733e
— Ropsten.
結果は何ですか?
, . Ruby MetaMask . , , , javascript ruby. .
. !
Ethereum:
1:
2: Web3.js