こんにちは
イーサリアムのスマート契約の手動テストのために、できればシンプルなソリューションを開発するというアイデアがありました。 Remixの[実行]タブの機能に似た操作を行うことは興味深いものになりました。
アプリケーションでできること:
Golangのシンプルなバックエンドであることが判明しました。
- エンドポイントで静的なHTMLページを生成し、ブラウザーに送信します。
- toml configから設定を取得します。
- RPC経由でイーサリアムノードに接続します。
- イーサリアムシミュレーターになります。
- .solファイルをコンパイルします。
- 契約を展開します。
- 契約に書き込み、契約から情報を読み取ります。
- イーサリアムアドレスへのETH転送を行います。
- Ethereumネットワークに関する情報、最後のブロックからの情報を受信します。
- 作業用に1つのディレクトリから複数のコントラクトをロードしてから、作業する特定のコントラクトを選択できます。
- 暗号化されていない情報をクッキーに保存します。
- 15分ごとに秘密鍵を要求し、このユーザーに代わって操作が実行されます。
- 現在のセッションに関する情報を表示します。現在のアドレス、現在の残高、選択したsolファイル、その中の契約。
- すべてのコントラクトメソッドのテーブルを作成します。
順番に:
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つの方法があります。
- RPCを介してイーサリアムノードに接続し、ノードが同期されているネットワークで作業します。 これは、プライベートイーサリアムネットワークがある場合、または既に同期ノードがある場合に便利です。
- イーサリアムブロックチェーンエミュレーター。 エミュレーターモードで作業する場合は、UTC JSONファイルをキーストアファイル形式でキーストアディレクトリに配置する必要があります。これらのファイルを復号化するためのパスワードは空になります。
確かにもっと美しくすることはできますが、例としては、既存のソリューションが非常に適しています。 これらのファイルから、アプリケーションはイーサリアムアドレスを取得し、ゼロ以外のバランスを取ります。
例として、キーストアディレクトリに5つのファイルを配置します。 これらはテスト環境で使用できます。
config.yaml configを入力します。
- connect_url-rpcサーバーイーサリアムノードに接続するためのURL。 このフィールドを空白のままにすると、アプリケーションはイーサリアムエミュレーションモードで起動します。これは上記で説明したとおりです。
- sol_pathは、アプリケーションが検索するスマートコントラクトを含むフォルダーです。 アプリケーションは、ルートディレクトリにある.solファイルを探します。 サブディレクトリは無視されます。 ただし、作業する契約がサブディレクトリ内の契約を参照している場合は、大丈夫です。最上位の契約を通じて追加されます。
- keystore_path-UTC JSONファイルを含むディレクトリキーストアファイル。 復号化のためのパスワードは空でなければならないことを思い出させてください。
- gaslimit-トランザクションまたは契約の展開のガス制限。
- port-ローカルhttpサーバーのポート。
- solc-Solidityコンパイラへのパス;空のままにすると、アプリケーションはシステムにインストールされたコンパイラを使用します。
アプリケーションを起動します。 構成ファイルのあるディレクトリへのパスは、-configフラグを介して指定できます
./efront-v0.0.1-linux-amd64 -config $GOPATH/src/ethereum-front/
ブラウザのリンクをたどります。デフォルトではhttp:// localhost:8085です。
秘密鍵を入力する必要があります。 5つのテストアドレスの秘密キーは、keys.txtにあります。 この秘密キーは、ブラウザのCookieに15分間保存されます。 次は新しいリクエストです。 現在、何も暗号化されていません。

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

さらに、対応するチェックボックスをオンにして、一度展開した契約のアドレスを入力するか、新しい契約を展開できます。 [展開]チェックボックスがオンの場合、アドレスフィールドは無視されます。
すべてがうまくいった場合、ブラウザに同様の画像が表示されます。

エラーがある場合、それらはインターフェースの上部のテキストエリアに表示されます。
ページの上部には、2つのログインリンクとアップロードリンクがあります。
ログインがリダイレクトされ、新しい秘密鍵が入力されます。 選択した契約へのリダイレクトをアップロードします。
以下は、現在のセッションに関する情報です。

- アドレス:-現在の秘密鍵に一致するイーサリアムアドレス
- balance-現在のネットワークのこのアドレスでのEth残高。 ページが更新されるたびに要求されます
- ファイルと契約はそれぞれ選択されたsolファイルとその中の契約です。 クッキーに保存され、そこから取得
- 契約アドレスは、現在のネットワークに展開された契約のアドレスです。 クッキーに保存され、そこから取得
次は2つのテーブルです。
現在のコントラクトのメソッドを操作するための左の表。 選択した契約に応じて、動的に変化します。
右の表は、イーサリアムを操作するための一般的な機能です。
- バランス-現在のネットワークで選択したイーサリアムアドレスのバランスを確認します。
- ガス価格-iの現在のガス価格。
- 最後のブロック-現在のブロックの番号。 シミュレーターでは機能しません。
- イーサリアムネットワークのガス制限-最後のブロックのガス制限。 シミュレーターでは機能しません。
- イーサリアムネットワーク時間-最後のブロックのマイニング時間。 シミュレーターでは機能しません。
- イーサリアムネットワークの難易度-最後のブロックの複雑さ。 シミュレーターでは機能しません。
- 転送-誰に、どのくらいのワイを転送するか。
- 時間の調整-シミュレーターの時間管理。 正数を入力する必要があります。 そして、ほんの数秒間、シミュレーターの時間が長くなります。
注:トランザクション(ブロックチェーンへの書き込み操作)を実行するとき、ページがロードされるまで待機します。これには数秒かかる場合があります。 .
, textarea ( ):

- Nonce —
- From —
- Contract Address —
- Gas price — Gas
- Gas Used — Gas
- Cost/Fee: Wai
- Status — 1, , 0, .
- Transaction Hash —
.
C OS. bin.
:
- html 5.
- front Ethereum, ether,
- , -
ソースコード
どうもありがとう。