JavaScriptおよびWeb3を使用せずにイーサリアムスマートコントラクトを手動でテストするためのGolangの軽量「フロントエンド」

こんにちは







イーサリアムのスマート契約の手動テストのために、できればシンプルなソリューションを開発するというアイデアがありました。 Remixの[実行]タブの機能に似た操作を行うことは興味深いものになりました。







アプリケーションでできること:



Golangのシンプルなバックエンドであることが判明しました。









順番に:



Golangを選択したのは、 ゲスが構築されているgoethereumコードベースが本当に好きだったからです。







静的なHTMLを生成するには、標準のGolangパッケージ「html /テンプレート」が使用されます。 ここでは何もペイントしません。すべてのテンプレートはプロジェクトテンプレートパッケージにあります。

上記で書いたように、イーサリアムを使用するには、go-ethereumコードベースバージョン1.7.3を選択しました。

go-ethereumのモバイルパッケージを使用したかったのですが、モバイルはしばらく更新されておらず、現在のAbi形式では正しく動作しません。 データを処理するときに、同様のエラーが表示されます。







abi: cannot unmarshal *big.Int in to []interface {}
      
      





このエラーはすでに修正されていますが、メインブランチでは、これを書いた時点での修正はまだ追加されていません。







それにもかかわらず、私は別のソリューション、ラッパーレスを選択しました。 モバイルパッケージの関数は、基本的に主な機能の便利なラッパーです。







その結果、go-ethereumからabi(+ abiに依存する複数のパッケージ)を操作するためのパッケージをプロジェクトに取り込み、プルリクエストからコードを追加しました。







スマートコントラクトを使用する必要があるため、solファイルから特定のコントラクトを操作するためのgoパッケージを生成できるabigenユーティリティは、私には不向きでした。







構造と、この構造がレシーバーであるメソッドを作成しました(Golangの用語に間違いがない場合):







 type EthWorker struct { Container string //   sol,     Contract string //  Endpoint string //   Key string //   ContractAddress string //  FormValues url.Values //map    ,  POST form New bool //    }
      
      





完全なインターフェースは次のようになります。







 type ReadWriterEth interface { Transact() (string, error) //    Call() (string, error) //    Deploy() (string, string, error) //    Info() (*Info, error) //   ,      ParseInput() ([]interface{}, error) //  POST         ParseOutput([]interface{}) (string, error) //      }
      
      





契約に情報を書き込むための関数:







取引
 func (w *EthWorker) Transact() (string, error) { // POST ,      inputs, err := w.ParseInput() if err != nil { return "", errors.Wrap(err, "parse input") } //    EthWorker       pk := strings.TrimPrefix(w.Key, "0x") key, err := crypto.HexToECDSA(pk) if err != nil { return "", errors.Wrap(err, "hex to ECDSA") } auth := bind.NewKeyedTransactor(key) if !common.IsHexAddress(w.ContractAddress) { return "", errors.New("New Address From Hex") } addr := common.HexToAddress(w.ContractAddress) //    contract := bind.NewBoundContract( addr, Containers.Containers[w.Container].Contracts[w.Contract].Abi, Client, Client, ) //    Gas gasprice, err := Client.SuggestGasPrice(context.Background()) if err != nil { return "", errors.Wrap(err, "suggest gas price") } //      opt := &bind.TransactOpts{ From: auth.From, Signer: auth.Signer, GasPrice: gasprice, GasLimit: GasLimit, Value: auth.Value, } //   tr, err := contract.Transact(opt, w.Endpoint, inputs...) if err != nil { return "", errors.Wrap(err, "transact") } var receipt *types.Receipt //    ,       ,       switch v := Client.(type) { case *backends.SimulatedBackend: v.Commit() receipt, err = v.TransactionReceipt(context.Background(), tr.Hash()) if err != nil { return "", errors.Wrap(err, "transaction receipt") } case *ethclient.Client: receipt, err = bind.WaitMined(context.Background(), v, tr) if err != nil { return "", errors.Wrap(err, "transaction receipt") } } if err != nil { return "", errors.Errorf("error transact %s: %s", tr.Hash().String(), err.Error(), ) } //     responce := fmt.Sprintf(templates.WriteResult, tr.Nonce(), auth.From.String(), tr.To().String(), tr.Value().String(), tr.GasPrice().String(), receipt.GasUsed.String(), new(big.Int).Mul(receipt.GasUsed, tr.GasPrice()), receipt.Status, receipt.TxHash.String(), ) return responce, nil }
      
      





契約から情報を読み取るための機能:







電話する
 func (w *EthWorker) Call() (string, error) { inputs, err := w.ParseInput() if err != nil { return "", errors.Wrap(err, "parse input") } key, _ := crypto.GenerateKey() auth := bind.NewKeyedTransactor(key) contract := bind.NewBoundContract( common.HexToAddress(w.ContractAddress), Containers.Containers[w.Container].Contracts[w.Contract].Abi, Client, Client, ) opt := &bind.CallOpts{ Pending: true, From: auth.From, } outputs := Containers.Containers[w.Container].Contracts[w.Contract].OutputsInterfaces[w.Endpoint] if err := contract.Call( opt, &outputs, w.Endpoint, inputs..., ); err != nil { return "", errors.Wrap(err, "call contract") } result, err := w.ParseOutput(outputs) if err != nil { return "", errors.Wrap(err, "parse output") } return result, err }
      
      





契約を展開するための機能:







展開する
 func (w *EthWorker) Deploy() (string, string, error) { inputs, err := w.ParseInput() if err != nil { return "", "", errors.Wrap(err, "parse input") } pk := strings.TrimPrefix(w.Key, "0x") key, err := crypto.HexToECDSA(pk) if err != nil { return "", "", errors.Wrap(err, "hex to ECDSA") } auth := bind.NewKeyedTransactor(key) current_bytecode := Containers.Containers[w.Container].Contracts[w.Contract].Bin current_abi := Containers.Containers[w.Container].Contracts[w.Contract].Abi addr, tr, _, err := bind.DeployContract(auth, current_abi, common.FromHex(current_bytecode), Client, inputs...) if err != nil { log.Printf("error %s", err.Error()) return "", "", errors.Wrap(err, "deploy contract") } var receipt *types.Receipt switch v := Client.(type) { case *backends.SimulatedBackend: v.Commit() receipt, err = v.TransactionReceipt(context.Background(), tr.Hash()) if err != nil { return "", "", errors.Wrap(err, "transaction receipt") } case *ethclient.Client: receipt, err = bind.WaitMined(context.Background(), v, tr) if err != nil { return "", "", errors.Wrap(err, "transaction receipt") } } if err != nil { return "", "", errors.Errorf("error transact %s: %s", tr.Hash().String(), err.Error(), ) } responce := fmt.Sprintf(templates.DeployResult, tr.Nonce(), auth.From.String(), addr.String(), tr.GasPrice().String(), receipt.GasUsed.String(), new(big.Int).Mul(receipt.GasUsed, tr.GasPrice()).String(), receipt.Status, receipt.TxHash.String(), ) return responce, addr.String(), nil }
      
      





ユーザーがWebページのフォームに入力したデータからCall and Transact機能に転送できるデータを取得する方法の問題を解決する必要がありました。







契約のabiメソッドから特定のフィールドに必要なデータ型を学習し、ユーザーがWebページのフォームに入力したものをもたらすことほど良いものは思いつきませんでした。 すなわち データ型を忘れた場合、このソリューションはこのデータ型では機能しません。 コードを変更する必要があります。 ParseInput関数に実装されます







解析入力
 func (w *EthWorker) ParseInput() ([]interface{}, error) { //           ,        if w.New && len(Containers.Containers[w.Container].Contracts[w.Contract].Abi.Constructor.Inputs) == 0 { return nil, nil } //           ,        if !w.New && len(Containers.Containers[w.Container].Contracts[w.Contract].Abi.Methods[w.Endpoint].Inputs) == 0 { return nil, nil } //  Form Values inputsMap := make(map[int]string) var inputsArray []int var inputsSort []string for k, v := range w.FormValues { if k == "endpoint" { continue } if len(v) != 1 { return nil, errors.Errorf("incorrect %s field", k) } i, err := strconv.Atoi(k) if err != nil { continue //return nil, errors.Wrap(err, "incorrect inputs: strconv.Atoi") } inputsMap[i] = v[0] } //    ,   ,    if Containers.Containers[w.Container] == nil || Containers.Containers[w.Container].Contracts[w.Contract] == nil { return nil, errors.New("input values incorrect") } //  , ..  Containers  .      if !w.New && len(Containers.Containers[w.Container].Contracts[w.Contract].Abi.Methods[w.Endpoint].Inputs) != 0 && Containers.Containers[w.Container].Contracts[w.Contract].InputsInterfaces[w.Endpoint] == nil { return nil, errors.New("input values incorrect") } //       .     ABI var inputs_args []abi.Argument if w.New { inputs_args = Containers.Containers[w.Container].Contracts[w.Contract].Abi.Constructor.Inputs } else { inputs_args = Containers.Containers[w.Container].Contracts[w.Contract].Abi.Methods[w.Endpoint].Inputs } if len(inputsMap) != len(inputs_args) { return nil, errors.New("len inputs_args != inputsMap: incorrect inputs") } for k := range inputsMap { inputsArray = append(inputsArray, k) } sort.Ints(inputsArray) for k := range inputsArray { inputsSort = append(inputsSort, inputsMap[k]) } var inputs_interfaces []interface{} for i := 0; i < len(inputs_args); i++ { arg_value := inputsMap[i] switch inputs_args[i].Type.Type.String() { case "bool": var result bool result, err := strconv.ParseBool(arg_value) if err != nil { return nil, errors.New("incorrect inputs") } inputs_interfaces = append(inputs_interfaces, result) case "[]bool": var result []bool result_array := strings.Split(arg_value, ",") for _, bool_value := range result_array { item, err := strconv.ParseBool(bool_value) if err != nil { return nil, errors.Wrap(err, "incorrect inputs") } result = append(result, item) } inputs_interfaces = append(inputs_interfaces, result) case "string": inputs_interfaces = append(inputs_interfaces, arg_value) case "[]string": result_array := strings.Split(arg_value, ",") //TODO: NEED REF inputs_interfaces = append(inputs_interfaces, result_array) case "[]byte": inputs_interfaces = append(inputs_interfaces, []byte(arg_value)) case "[][]byte": var result [][]byte result_array := strings.Split(arg_value, ",") for _, byte_value := range result_array { result = append(result, []byte(byte_value)) } inputs_interfaces = append(inputs_interfaces, result) case "common.Address": if !common.IsHexAddress(arg_value) { return nil, errors.New("incorrect inputs: arg_value is not address") } inputs_interfaces = append(inputs_interfaces, common.HexToAddress(arg_value)) case "[]common.Address": var result []common.Address result_array := strings.Split(arg_value, ",") for _, addr_value := range result_array { if !common.IsHexAddress(arg_value) { return nil, errors.New("incorrect inputs: arg_value is not address") } addr := common.HexToAddress(addr_value) result = append(result, addr) } inputs_interfaces = append(inputs_interfaces, result) case "common.Hash": if !common.IsHex(arg_value) { return nil, errors.New("incorrect inputs: arg_value is not hex") } inputs_interfaces = append(inputs_interfaces, common.HexToHash(arg_value)) case "[]common.Hash": var result []common.Hash result_array := strings.Split(arg_value, ",") for _, addr_value := range result_array { if !common.IsHex(arg_value) { return nil, errors.New("incorrect inputs: arg_value is not hex") } hash := common.HexToHash(addr_value) result = append(result, hash) } inputs_interfaces = append(inputs_interfaces, result) case "int8": i, err := strconv.ParseInt(arg_value, 10, 8) if err != nil { return nil, errors.New("incorrect inputs: arg_value is not int8") } inputs_interfaces = append(inputs_interfaces, int8(i)) case "int16": i, err := strconv.ParseInt(arg_value, 10, 16) if err != nil { return nil, errors.New("incorrect inputs: arg_value is not int16") } inputs_interfaces = append(inputs_interfaces, int16(i)) case "int32": i, err := strconv.ParseInt(arg_value, 10, 32) if err != nil { return nil, errors.New("incorrect inputs: arg_value is not int32") } inputs_interfaces = append(inputs_interfaces, int32(i)) case "int64": i, err := strconv.ParseInt(arg_value, 10, 64) if err != nil { return nil, errors.New("incorrect inputs: arg_value is not int64") } inputs_interfaces = append(inputs_interfaces, int64(i)) case "uint8": i, err := strconv.ParseInt(arg_value, 10, 8) if err != nil { return nil, errors.New("incorrect inputs: arg_value is not uint8") } inputs_interfaces = append(inputs_interfaces, big.NewInt(i)) case "uint16": i, err := strconv.ParseInt(arg_value, 10, 16) if err != nil { return nil, errors.New("incorrect inputs: arg_value is not uint16") } inputs_interfaces = append(inputs_interfaces, big.NewInt(i)) case "uint32": i, err := strconv.ParseInt(arg_value, 10, 32) if err != nil { return nil, errors.New("incorrect inputs: arg_value is not uint32") } inputs_interfaces = append(inputs_interfaces, big.NewInt(i)) case "uint64": i, err := strconv.ParseInt(arg_value, 10, 64) if err != nil { return nil, errors.New("incorrect inputs: arg_value is not uint64") } inputs_interfaces = append(inputs_interfaces, big.NewInt(i)) case "*big.Int": bi := new(big.Int) bi, _ = bi.SetString(arg_value, 10) if bi == nil { return nil, errors.New("incorrect inputs: " + arg_value + " not " + inputs_args[i].Type.String()) } inputs_interfaces = append(inputs_interfaces, bi) case "[]*big.Int": var result []*big.Int result_array := strings.Split(arg_value, ",") for _, big_value := range result_array { bi := new(big.Int) bi, _ = bi.SetString(big_value, 10) if bi == nil { return nil, errors.New("incorrect inputs: " + arg_value + " not " + inputs_args[i].Type.String()) } result = append(result, bi) } inputs_interfaces = append(inputs_interfaces, result) } } //    return inputs_interfaces, nil }
      
      





ParseOutput関数でイーサリアムから取得したデータに対して同様の変換を行いました







解析出力
 func (w *EthWorker) ParseOutput(outputs []interface{}) (string, error) { if len(Containers.Containers[w.Container].Contracts[w.Contract].Abi.Methods[w.Endpoint].Outputs) == 0 { return "", nil } if Containers.Containers[w.Container] == nil || Containers.Containers[w.Container].Contracts[w.Contract] == nil { return "", errors.New("input values incorrect") } if len(Containers.Containers[w.Container].Contracts[w.Contract].Abi.Methods[w.Endpoint].Outputs) != 0 && Containers.Containers[w.Container].Contracts[w.Contract].OutputsInterfaces[w.Endpoint] == nil { return "", errors.New("input values incorrect") } output_args := Containers.Containers[w.Container].Contracts[w.Contract].Abi.Methods[w.Endpoint].Outputs if len(outputs) != len(output_args) { return "", errors.New("incorrect inputs") } var item_array []string for i := 0; i < len(outputs); i++ { switch output_args[i].Type.Type.String() { case "bool": item := strconv.FormatBool(*outputs[i].(*bool)) item_array = append(item_array, item) case "[]bool": boolArray := *outputs[i].(*[]bool) var boolItems []string for _, bool_value := range boolArray { item := strconv.FormatBool(bool_value) boolItems = append(boolItems, item) } item := "[ " + strings.Join(boolItems, ",") + " ]" item_array = append(item_array, item) case "string": item_array = append(item_array, *outputs[i].(*string)) case "[]string": array := *outputs[i].(*[]string) var items []string for _, value := range array { items = append(items, value) } item := "[ " + strings.Join(items, ",") + " ]" item_array = append(item_array, item) case "[]byte": array := *outputs[i].(*[]byte) var items []string for _, value := range array { items = append(items, string(value)) } item := "[ " + strings.Join(items, ",") + " ]" item_array = append(item_array, item) case "[][]byte": array := *outputs[i].(*[][]byte) var items string for _, array2 := range array { var items2 []string for _, value := range array2 { items2 = append(items2, string(value)) } item2 := "[ " + strings.Join(items2, ",") + " ]" items = items + "," + item2 } item_array = append(item_array, items) case "common.Address": item := *outputs[i].(*common.Address) item_array = append(item_array, item.String()) case "[]common.Address": addrArray := *outputs[i].(*[]common.Address) var addrItems []string for _, value := range addrArray { addrItems = append(addrItems, value.String()) } item := "[ " + strings.Join(addrItems, ",") + " ]" item_array = append(item_array, item) case "common.Hash": item := *outputs[i].(*common.Hash) item_array = append(item_array, item.String()) case "[]common.Hash": hashArray := *outputs[i].(*[]common.Hash) var hashItems []string for _, value := range hashArray { hashItems = append(hashItems, value.String()) } item := "[ " + strings.Join(hashItems, ",") + " ]" item_array = append(item_array, item) case "int8": item := *outputs[i].(*int8) str := strconv.FormatInt(int64(item), 10) item_array = append(item_array, str) case "int16": item := *outputs[i].(*int16) str := strconv.FormatInt(int64(item), 10) item_array = append(item_array, str) case "int32": item := *outputs[i].(*int32) str := strconv.FormatInt(int64(item), 10) item_array = append(item_array, str) case "int64": item := *outputs[i].(*int64) str := strconv.FormatInt(item, 10) item_array = append(item_array, str) case "uint8": item := *outputs[i].(*uint8) str := strconv.FormatInt(int64(item), 10) item_array = append(item_array, str) case "uint16": item := *outputs[i].(*uint16) str := strconv.FormatInt(int64(item), 10) item_array = append(item_array, str) case "uint32": item := *outputs[i].(*uint32) str := strconv.FormatInt(int64(item), 10) item_array = append(item_array, str) case "uint64": item := *outputs[i].(*uint64) str := strconv.FormatInt(int64(item), 10) item_array = append(item_array, str) case "*big.Int": item := *outputs[i].(**big.Int) item_array = append(item_array, item.String()) case "[]*big.Int": bigArray := *outputs[i].(*[]*big.Int) var items []string for _, v := range bigArray { items = append(items, v.String()) } item := "[ " + strings.Join(items, ",") + " ]" item_array = append(item_array, item) } } return strings.Join(item_array, " , "), nil }
      
      





前述のabigenユーティリティのコードベースから、Solidityコンパイラを操作するための機能を引き裂きました。 最終的に、私はほぼすべての契約でabiとバイトコードを取得しました。 Bind関数に実装されています。







バインド
 func Bind(dirname, solcfile string) (*ContractContainers, error) { result := &ContractContainers{ Containers: make(map[string]*ContractContainer), } allfiles, err := ioutil.ReadDir(dirname) if err != nil { return nil, errors.Wrap(err, "error ioutil.ReadDir") } for _, v := range allfiles { if v.IsDir() { continue } if hasSuffixCaseInsensitive(v.Name(), ".sol") { contracts, err := compiler.CompileSolidity(solcfile, dirname+string(os.PathSeparator)+v.Name()) if err != nil { return nil, errors.Wrap(err, "CompileSolidity") } c := &ContractContainer{ ContainerName: v.Name(), Contracts: make(map[string]*Contract), } for name, contract := range contracts { a, _ := json.Marshal(contract.Info.AbiDefinition) ab, err := abi.JSON(strings.NewReader(string(a))) if err != nil { return nil, errors.Wrap(err, "abi.JSON") } nameParts := strings.Split(name, ":") var ab_keys []string ouputs_map := make(map[string][]interface{}) inputs_map := make(map[string][]interface{}) for key, method := range ab.Methods { ab_keys = append(ab_keys, key) var o []interface{} var i []interface{} for _, v := range method.Outputs { var ar interface{} switch v.Type.Type.String() { case "bool": ar = new(bool) case "[]bool": ar = new([]bool) case "string": ar = new(string) case "[]string": ar = new([]string) case "[]byte": ar = new([]byte) case "[][]byte": ar = new([][]byte) case "common.Address": ar = new(common.Address) case "[]common.Address": ar = new([]common.Address) case "common.Hash": ar = new(common.Hash) case "[]common.Hash": ar = new([]common.Hash) case "int8": ar = new(int8) case "int16": ar = new(int16) case "int32": ar = new(int32) case "int64": ar = new(int64) case "uint8": ar = new(uint8) case "uint16": ar = new(uint16) case "uint32": ar = new(uint32) case "uint64": ar = new(uint64) case "*big.Int": ar = new(*big.Int) case "[]*big.Int": ar = new([]*big.Int) default: return nil, errors.Errorf("unsupported type: %s", v.Type.Type.String()) } o = append(o, ar) } ouputs_map[method.Name] = o for _, v := range method.Inputs { var ar interface{} switch v.Type.Type.String() { case "bool": ar = new(bool) case "[]bool": ar = new([]bool) case "string": ar = new(string) case "[]string": ar = new([]string) case "[]byte": ar = new([]byte) case "[][]byte": ar = new([][]byte) case "common.Address": ar = new(common.Address) case "[]common.Address": ar = new([]common.Address) case "common.Hash": ar = new(common.Hash) case "[]common.Hash": ar = new([]common.Hash) case "int8": ar = new(int8) case "int16": ar = new(int16) case "int32": ar = new(int32) case "int64": ar = new(int64) case "uint8": ar = new(uint8) case "uint16": ar = new(uint16) case "uint32": ar = new(uint32) case "uint64": ar = new(uint64) case "*big.Int": ar = new(*big.Int) case "[]*big.Int": ar = new([]*big.Int) default: return nil, errors.Errorf("unsupported type: %s", v.Type.Type.String()) } i = append(i, ar) } inputs_map[method.Name] = i } sort.Strings(ab_keys) con := &Contract{ Name: nameParts[len(nameParts)-1], Abi: ab, AbiJson: string(a), Bin: contract.Code, SortKeys: ab_keys, OutputsInterfaces: ouputs_map, InputsInterfaces: inputs_map, } c.ContractNames = append(c.ContractNames, nameParts[len(nameParts)-1]) c.Contracts[nameParts[len(nameParts)-1]] = con } sort.Strings(c.ContractNames) result.ContainerNames = append(result.ContainerNames, c.ContainerName) result.Containers[c.ContainerName] = c } } sort.Strings(result.ContainerNames) return result, err }
      
      





この関数は、モバイルパッケージでの実験から大きなコードブロックを残しましたが、まだ削除していませんが、単にリファクタリングしました。







かなり大きなContractContainers構造を作成し、そこに現在の契約に関するすべての情報を配置しました。将来、アプリケーションはそこからすべての情報を取得します。







最後に、その仕組みを説明します。



Linuxでのみプログラムを実行しました。 近くに他のオペレーティングシステムはありません。

WindowsおよびMac用の実行可能ファイルをコンパイルしましたが。







まず、プラットフォーム用のSolidityコンパイラが必要です。 これがおそらく最も難しい点です。







ここでコンパイル済みのバイナリまたはソースを取得するか、 ここで詳細を確認できます 。 LinuxおよびWindows用のバージョン0.4.18および0.4.19プロジェクトのsolcディレクトリに配置しました。 システムにすでにインストールされているコンパイラを使用することもできます。 システムにSolidityコンパイラがあるかどうかを確認するには、コマンドプロンプトで次のように入力します。







 solc —version
      
      





答えがこれである場合:







 solc, the solidity compiler commandline interface Version: 0.4.18+commit.9cf6e910.Linux.g++
      
      





その後、すべてが順調です。

いくつかのライブラリが必要になる場合は、Ubuntuがこれを要求した場合などに、それらをインストールするだけです。







 ./solc: error while loading shared libraries: libz3.so.4: cannot open shared object file: No such file or directory
      
      





、次にlibz3-devを配置します







次に、イーサリアムを使用するモードを決定する必要があります。 2つの方法があります。









確かにもっと美しくすることはできますが、例としては、既存のソリューションが非常に適しています。 これらのファイルから、アプリケーションはイーサリアムアドレスを取得し、ゼロ以外のバランスを取ります。

例として、キーストアディレクトリに5つのファイルを配置します。 これらはテスト環境で使用できます。







config.yaml configを入力します。









アプリケーションを起動します。 構成ファイルのあるディレクトリへのパスは、-configフラグを介して指定できます







 ./efront-v0.0.1-linux-amd64 -config $GOPATH/src/ethereum-front/
      
      





ブラウザのリンクをたどります。デフォルトではhttp:// localhost:8085です。

秘密鍵を入力する必要があります。 5つのテストアドレスの秘密キーは、keys.txtにあります。 この秘密キーは、ブラウザのCookieに15分間保存されます。 次は新しいリクエストです。 現在、何も暗号化されていません。









選択して、コンテナ(.solファイル)およびアプリケーションがその中に見つけたコントラクトを選択します。









さらに、対応するチェックボックスをオンにして、一度展開した契約のアドレスを入力するか、新しい契約を展開できます。 [展開]チェックボックスがオンの場合、アドレスフィールドは無視されます。







すべてがうまくいった場合、ブラウザに同様の画像が表示されます。









エラーがある場合、それらはインターフェースの上部のテキストエリアに表示されます。

ページの上部には、2つのログインリンクとアップロードリンクがあります。







ログインがリダイレクトされ、新しい秘密鍵が入力されます。 選択した契約へのリダイレクトをアップロードします。







以下は、現在のセッションに関する情報です。











次は2つのテーブルです。

現在のコントラクトのメソッドを操作するための左の表。 選択した契約に応じて、動的に変化します。







右の表は、イーサリアムを操作するための一般的な機能です。









注:トランザクション(ブロックチェーンへの書き込み操作)を実行するとき、ページがロードされるまで待機します。これには数秒かかる場合があります。 .







, textarea ( ):













.

C OS. bin.







:





ソースコード

どうもありがとう。








All Articles