From 5391ac2c64741534c7bc7a3746a0bc1956765703 Mon Sep 17 00:00:00 2001
From: lengzhao <zgq_126@126.com>
Date: Sun, 20 Dec 2020 21:09:54 +0800
Subject: [PATCH] dynamic ui

---
 bin/gui/README.md             |   2 +-
 bin/gui/assets/zh.json        |   1 +
 bin/gui/build.sh              |   2 +
 bin/gui/conf.json             |   3 +-
 bin/gui/conf/config.go        |  15 +-
 bin/gui/dynamic_ui.json       | 246 +++++++++++
 bin/gui/screens/app.go        |   9 +-
 bin/gui/screens/common.go     | 146 +++++++
 bin/gui/screens/dynamic_ui.go | 791 ++++++++++++++++++++++++++++++++++
 bin/gui/screens/history.go    |  66 ---
 bin/gui/screens/master.go     |   1 +
 bin/gui/screens/task.go       |  50 +--
 bin/gui/usage.txt             | 124 ++++++
 13 files changed, 1342 insertions(+), 114 deletions(-)
 create mode 100644 bin/gui/dynamic_ui.json
 create mode 100644 bin/gui/screens/common.go
 create mode 100644 bin/gui/screens/dynamic_ui.go
 create mode 100644 bin/gui/usage.txt

diff --git a/bin/gui/README.md b/bin/gui/README.md
index 5b89f19..c7a6661 100644
--- a/bin/gui/README.md
+++ b/bin/gui/README.md
@@ -4,7 +4,7 @@
 
 1. fyne.exe package -os windows -name govm.net -icon ./assets/govm.png
 2. mv gui.exe govm.exe
-2. tar zcvf  govm_windows_$(date +'%Y%m%d_%H%M%S').tar.gz govm.exe assets conf.json
+3. tar zcvf  govm_windows_$(date +'%Y%m%d_%H%M%S').tar.gz govm.exe assets conf.json
 
 ## add local language
 
diff --git a/bin/gui/assets/zh.json b/bin/gui/assets/zh.json
index 4e8f122..cf5d6bf 100644
--- a/bin/gui/assets/zh.json
+++ b/bin/gui/assets/zh.json
@@ -81,6 +81,7 @@
 	"Reward":"奖励",
 	"Life":"有效期",
 	"Message":"信息",
+	"Customize":"自定义",
 	"":"",
 	"Number":"数量"
 }
\ No newline at end of file
diff --git a/bin/gui/build.sh b/bin/gui/build.sh
index 0429c84..5457d3c 100644
--- a/bin/gui/build.sh
+++ b/bin/gui/build.sh
@@ -8,6 +8,8 @@ mkdir $folder
 mv govm.exe $folder
 cp assets $folder -rf
 cp conf.json $folder -rf
+cp dynamic_ui.json $folder -rf
+cp usage.txt $folder -rf
 zip -r govm_windows_wallet_$(date +'%Y%m%d_%H%M%S').zip $folder
 echo Enter to exit
 read k
diff --git a/bin/gui/conf.json b/bin/gui/conf.json
index 58ac96b..e2003bd 100644
--- a/bin/gui/conf.json
+++ b/bin/gui/conf.json
@@ -11,5 +11,6 @@
   "chains": [
     "1",
     "2"
-  ]
+  ],
+  "dynamic_ui_file": "dynamic_ui.json"
 }
diff --git a/bin/gui/conf/config.go b/bin/gui/conf/config.go
index 9e2ea65..0c6a125 100644
--- a/bin/gui/conf/config.go
+++ b/bin/gui/conf/config.go
@@ -17,13 +17,14 @@ const Vserion = "v0.5.6"
 
 // Config config
 type Config struct {
-	APIServer    string   `json:"api_server,omitempty"`
-	WalletFile   string   `json:"wallet_file,omitempty"`
-	Langure      string   `json:"langure,omitempty"`
-	LangureList  []string `json:"langure_list,omitempty"`
-	CoinUnit     string   `json:"coin_unit,omitempty"`
-	DefaultChain string   `json:"default_chain,omitempty"`
-	Chains       []string `json:"chains,omitempty"`
+	APIServer     string   `json:"api_server,omitempty"`
+	WalletFile    string   `json:"wallet_file,omitempty"`
+	Langure       string   `json:"langure,omitempty"`
+	LangureList   []string `json:"langure_list,omitempty"`
+	CoinUnit      string   `json:"coin_unit,omitempty"`
+	DefaultChain  string   `json:"default_chain,omitempty"`
+	Chains        []string `json:"chains,omitempty"`
+	DynamicUIFile string   `json:"dynamic_ui_file,omitempty"`
 }
 
 // Public key of config
diff --git a/bin/gui/dynamic_ui.json b/bin/gui/dynamic_ui.json
new file mode 100644
index 0000000..7fe304c
--- /dev/null
+++ b/bin/gui/dynamic_ui.json
@@ -0,0 +1,246 @@
+{
+    "view_ui": [
+        {
+            "name": "Category",
+            "app": "cdc09c276df3af32f8f60c33d57710374fe126f482fa50db61271bac0886e348",
+            "description": "category of flag",
+            "struct": "tCategory",
+            "chain": 1,
+            "show_expiration": true,
+            "hide": false,
+            "input": [
+                {
+                    "mode": "string",
+                    "title": "key"
+                }
+            ],
+            "view": [
+                {
+                    "mode": "map",
+                    "title": "",
+                    "sub": [
+                        {
+                            "mode": "string",
+                            "title": "ID",
+                            "key": "id"
+                        },
+                        {
+                            "mode": "string",
+                            "title": "Description",
+                            "key": "desc"
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "Flag",
+            "app": "cdc09c276df3af32f8f60c33d57710374fe126f482fa50db61271bac0886e348",
+            "description": "flag of address",
+            "struct": "tUser",
+            "chain": 1,
+            "show_key": true,
+            "show_expiration": true,
+            "input": [
+                {
+                    "mode": "address",
+                    "title": "Address",
+                    "value": "01ccaf415a3a6dc8964bf935a1f40e55654a4243ae99c709"
+                },
+                {
+                    "mode": "string",
+                    "title": "Category",
+                    "value": "discord"
+                },
+                {
+                    "mode": "hash",
+                    "hide": true
+                }
+            ],
+            "view": [
+                {
+                    "mode": "map",
+                    "title": "",
+                    "sub": [
+                        {
+                            "mode": "hex",
+                            "title": "Address",
+                            "key": "addr"
+                        },
+                        {
+                            "mode": "string",
+                            "title": "Category",
+                            "key": "category"
+                        },
+                        {
+                            "mode": "string",
+                            "title": "ID",
+                            "key": "id"
+                        },
+                        {
+                            "mode": "string",
+                            "title": "Desc",
+                            "key": "desc"
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "LastAction",
+            "app": "c11b3b8aa630a7fbfccec9e023c363749e9c60db43d7678f43c96075e5c2ddc0",
+            "description": "accept one task to new one action",
+            "struct": "tActionStatus",
+            "chain": 1,
+            "show_key": true,
+            "show_expiration": true,
+            "input": [
+                {
+                    "mode": "address",
+                    "title": "Address"
+                }
+            ],
+            "view": [
+                {
+                    "mode": "number",
+                    "title": "Action ID"
+                }
+            ]
+        }
+    ],
+    "run_ui": [
+        {
+            "name": "Flag",
+            "app": "cdc09c276df3af32f8f60c33d57710374fe126f482fa50db61271bac0886e348",
+            "description": "Tag yourself and let others know you",
+            "chains": [
+                1
+            ],
+            "energy": 0.1,
+            "hide": false,
+            "input": [
+                {
+                    "mode": "hex",
+                    "title": "Prefix",
+                    "value": "02",
+                    "hide": true
+                },
+                {
+                    "mode": "map",
+                    "sub": [
+                        {
+                            "mode": "string",
+                            "title": "Category",
+                            "key": "category",
+                            "value":"discord"
+                        },
+                        {
+                            "mode": "string",
+                            "title": "ID",
+                            "key": "id",
+                            "value":""
+                        },
+                        {
+                            "mode": "string",
+                            "title": "Description",
+                            "key": "desc",
+                            "value": "",
+                            "empty_enable": true
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "Category",
+            "app": "cdc09c276df3af32f8f60c33d57710374fe126f482fa50db61271bac0886e348",
+            "description": "add new category of flag",
+            "chains": [
+                1
+            ],
+            "energy": 0.1,
+            "hide": false,
+            "input": [
+                {
+                    "mode": "hex",
+                    "title": "Prefix",
+                    "value": "00",
+                    "hide": true
+                },
+                {
+                    "mode": "map",
+                    "sub": [
+                        {
+                            "mode": "string",
+                            "title": "Category",
+                            "key": "id"
+                        },
+                        {
+                            "mode": "string",
+                            "title": "Desc",
+                            "key": "desc",
+                            "empty_enable": true
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "AcceptTask",
+            "app": "c11b3b8aa630a7fbfccec9e023c363749e9c60db43d7678f43c96075e5c2ddc0",
+            "description": "accept task",
+            "chains": [
+                1
+            ],
+            "energy": 0.1,
+            "hide": false,
+            "input": [
+                {
+                    "mode": "hex",
+                    "title": "Option",
+                    "value": "02",
+                    "hide":true
+                },
+                {
+                    "mode": "uint64",
+                    "title": "TaskID",
+                    "value": "1"
+                }
+            ]
+        },
+        {
+            "name": "CommitAction",
+            "app": "c11b3b8aa630a7fbfccec9e023c363749e9c60db43d7678f43c96075e5c2ddc0",
+            "description": "add new category of flag",
+            "chains": [
+                1
+            ],
+            "energy": 0.1,
+            "hide": false,
+            "input": [
+                {
+                    "mode": "hex",
+                    "title": "Prefix",
+                    "value": "03",
+                    "hide": true
+                },
+                {
+                    "mode": "map",
+                    "sub": [
+                        {
+                            "mode": "string",
+                            "title": "ActionID",
+                            "key": "id"
+                        },
+                        {
+                            "mode": "string",
+                            "title": "Proof",
+                            "key": "msg",
+                            "empty_enable": true
+                        }
+                    ]
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/bin/gui/screens/app.go b/bin/gui/screens/app.go
index 5b74800..2167956 100644
--- a/bin/gui/screens/app.go
+++ b/bin/gui/screens/app.go
@@ -262,6 +262,11 @@ func makeNewAPPTab(w fyne.Window) fyne.Widget {
 		fd.SetFilter(storage.NewExtensionFileFilter([]string{".go", ".govm"}))
 		fd.Show()
 	})
+	var chainID uint64 = 1
+	chain := widget.NewSelect(c.Chains, func(s string) {
+		chainID, _ = strconv.ParseUint(s, 10, 64)
+	})
+	chain.SetSelected(c.DefaultChain)
 	energy := widget.NewEntry()
 	energy.SetText("1")
 	unit := widget.NewLabel(c.CoinUnit)
@@ -311,7 +316,7 @@ func makeNewAPPTab(w fyne.Window) fyne.Widget {
 			base := res.GetBaseOfUnit(c.CoinUnit)
 
 			myWlt := conf.GetWallet()
-			ts := trans.NewTransaction(1, myWlt.Address, 0)
+			ts := trans.NewTransaction(chainID, myWlt.Address, 0)
 			ts.Energy = uint64(engF * float64(base))
 			ts.Ops = trans.OpsNewApp
 			ts.Data = code
@@ -332,7 +337,7 @@ func makeNewAPPTab(w fyne.Window) fyne.Widget {
 			result.SetText(fmt.Sprintf("%x", key))
 		},
 	}
-
+	form.Append(res.GetLocalString("Chain"), chain)
 	form.Append(res.GetLocalString("Description"), desc)
 	borderLayout := layout.NewBorderLayout(nil, nil, nil, btnOpen)
 	form.Append(res.GetLocalString("Code"), fyne.NewContainerWithLayout(borderLayout, btnOpen, codeEntry))
diff --git a/bin/gui/screens/common.go b/bin/gui/screens/common.go
new file mode 100644
index 0000000..2ca7e51
--- /dev/null
+++ b/bin/gui/screens/common.go
@@ -0,0 +1,146 @@
+package screens
+
+import (
+	"encoding/hex"
+	"encoding/json"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"strconv"
+
+	"fyne.io/fyne"
+	"fyne.io/fyne/layout"
+	"fyne.io/fyne/widget"
+	"github.com/lengzhao/wallet/bin/gui/conf"
+	"github.com/lengzhao/wallet/trans"
+)
+
+//statCoinLock
+//statCoinUnlock
+//statMiningCount
+//statMinerHit
+//statMinerReg
+//statTransferIn
+//statTransferOut
+//statMove
+//statAPPRun
+
+const coreName = "ff0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
+
+func newFormItemWithUnit(in fyne.CanvasObject, right string) fyne.CanvasObject {
+	unit := widget.NewLabel(right)
+	borderLayout := layout.NewBorderLayout(nil, nil, nil, unit)
+	return fyne.NewContainerWithLayout(borderLayout, unit, in)
+}
+func newFormItemWithObj(in, right fyne.CanvasObject) fyne.CanvasObject {
+	borderLayout := layout.NewBorderLayout(nil, nil, nil, right)
+	return fyne.NewContainerWithLayout(borderLayout, right, in)
+}
+
+func runApp(chain, cost, energy uint64, app, prefix string, param []byte) (string, error) {
+	myWlt := conf.GetWallet()
+	trans := trans.NewTransaction(chain, myWlt.Address, cost)
+	var body []byte
+	var err error
+	if prefix != "" {
+		body, err = hex.DecodeString(prefix)
+		if err != nil {
+			return "", err
+		}
+	}
+	if len(param) > 0 {
+		body = append(body, param...)
+	}
+	trans.Energy = energy
+	err = trans.RunApp(app, body)
+	if err != nil {
+		return "", err
+	}
+	td := trans.GetSignData()
+	sign := myWlt.Sign(td)
+	trans.SetTheSign(sign)
+	td = trans.Output()
+	key := trans.Key[:]
+
+	err = postTrans(chain, td)
+	if err != nil {
+		// result.SetText(fmt.Sprintf("%s", err))
+		log.Println("fail to run app:", err)
+		return "", err
+	}
+	return hex.EncodeToString(key), nil
+}
+
+type dataInfo struct {
+	AppName    string `json:"app_name,omitempty"`
+	StructName string `json:"struct_name,omitempty"`
+	IsDBData   bool   `json:"is_db_data,omitempty"`
+	Key        string `json:"key,omitempty"`
+	Value      string `json:"value,omitempty"`
+	Life       uint64 `json:"life,omitempty"`
+}
+
+func getNextKeyOfDB(chain, appName, structName, preKey string) string {
+	if appName == "" {
+		appName = coreName
+	}
+	urlStr := conf.Get().APIServer
+	urlStr += "/api/v1/" + chain + "/data/visit?app_name=" + appName
+	urlStr += "&is_db_data=true&struct_name=" + structName
+	urlStr += "&pre_key=" + preKey
+	resp, err := http.Get(urlStr)
+	if err != nil {
+		log.Println("fail to get db.", urlStr, err)
+		return ""
+	}
+	defer resp.Body.Close()
+	data, err := ioutil.ReadAll(resp.Body)
+	if resp.StatusCode != http.StatusOK || len(data) == 0 {
+		log.Println("fail to get db.", urlStr, resp.Status, string(data))
+		return ""
+	}
+	info := dataInfo{}
+	json.Unmarshal(data, &info)
+	return info.Key
+}
+func getIntOfDB(chain, appName, structName, address string) uint64 {
+	value, _ := getStringOfDB(chain, appName, structName, address)
+	if value == "" {
+		return 0
+	}
+	out, err := strconv.ParseUint(value, 16, 64)
+	if err != nil {
+		log.Println("parse error.", value, err)
+	}
+
+	return out
+}
+
+func getStringOfDB(chain, appName, structName, address string) (string, uint64) {
+	if appName == "" {
+		appName = coreName
+	}
+	urlStr := conf.Get().APIServer
+	urlStr += "/api/v1/" + chain + "/data?app_name=" + appName
+	urlStr += "&is_db_data=true&struct_name=" + structName
+	urlStr += "&key=" + address
+	resp, err := http.Get(urlStr)
+	if err != nil {
+		log.Println("fail to get db.", urlStr, err)
+		return "", 0
+	}
+	defer resp.Body.Close()
+	data, err := ioutil.ReadAll(resp.Body)
+	if resp.StatusCode != http.StatusOK || len(data) == 0 {
+		log.Println("fail to get db.", urlStr, resp.Status, string(data))
+		return "", 0
+	}
+	info := dataInfo{}
+	err = json.Unmarshal(data, &info)
+	if err != nil || info.Value == "" {
+		log.Println("not value.", urlStr)
+		return "", 0
+	}
+	// log.Println("success to get:", urlStr, info.Value)
+	return info.Value, info.Life
+}
diff --git a/bin/gui/screens/dynamic_ui.go b/bin/gui/screens/dynamic_ui.go
new file mode 100644
index 0000000..8088072
--- /dev/null
+++ b/bin/gui/screens/dynamic_ui.go
@@ -0,0 +1,791 @@
+package screens
+
+import (
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"math/big"
+	"strconv"
+	"strings"
+	"time"
+
+	"fyne.io/fyne"
+	"fyne.io/fyne/dialog"
+	"fyne.io/fyne/widget"
+	"github.com/govm-net/govm/wallet"
+	"github.com/lengzhao/wallet/bin/gui/conf"
+	"github.com/lengzhao/wallet/bin/gui/res"
+	"github.com/lengzhao/wallet/trans"
+)
+
+type dataInMode int
+type dataOutMode int
+
+const (
+	diHex = dataInMode(iota + 1)
+	diAddress
+	diString
+	diUint64
+	diUint32
+	diUint16
+	diUint8
+	diFloat64
+	diHash
+	diMap
+)
+const (
+	doHex = dataOutMode(iota + 100)
+	doMap
+	doList
+	doString
+	doNumber
+)
+
+var enableEmptyType = map[dataInMode]bool{
+	diHash: true, diMap: true,
+}
+
+func (a *dataInMode) UnmarshalJSON(b []byte) error {
+	var s string
+	if err := json.Unmarshal(b, &s); err != nil {
+		return err
+	}
+	switch strings.ToLower(s) {
+	default:
+		return fmt.Errorf("unknow input mode:%s", s)
+	case "hex":
+		*a = diHex
+	case "address":
+		*a = diAddress
+	case "string":
+		*a = diString
+	case "uint64":
+		*a = diUint64
+	case "uint32":
+		*a = diUint32
+	case "uint16":
+		*a = diUint16
+	case "uint8":
+		*a = diUint8
+	case "float":
+		*a = diFloat64
+	case "hash":
+		*a = diHash
+	case "map":
+		*a = diMap
+	}
+	return nil
+}
+
+func (a *dataOutMode) UnmarshalJSON(b []byte) error {
+	var s string
+	if err := json.Unmarshal(b, &s); err != nil {
+		return err
+	}
+	switch strings.ToLower(s) {
+	default:
+		return fmt.Errorf("unknow view mode:%s", s)
+	case "hex":
+		*a = doHex
+	case "string":
+		*a = doString
+	case "number":
+		*a = doNumber
+	case "map":
+		*a = doMap
+	case "list":
+		*a = doList
+	}
+	return nil
+}
+
+type uiInput struct {
+	Mode        dataInMode `json:"mode,omitempty"`
+	Title       string     `json:"title,omitempty"`
+	Key         string     `json:"key,omitempty"`
+	Value       string     `json:"value,omitempty"`
+	Hide        bool       `json:"hide,omitempty"`
+	MultiLine   bool       `json:"multi_line,omitempty"`
+	EmptyEnable bool       `json:"empty_enable,omitempty"`
+	Sub         []uiInput  `json:"sub,omitempty"`
+	win         *widget.Entry
+}
+
+type viewOutput struct {
+	Mode      dataOutMode  `json:"mode,omitempty"`
+	Title     string       `json:"title,omitempty"`
+	Length    int          `json:"length,omitempty"`
+	Key       string       `json:"key,omitempty"`
+	MultiLine bool         `json:"multi_line,omitempty"`
+	Sub       []viewOutput `json:"sub,omitempty"`
+	win       *widget.Entry
+}
+
+type dReadUI struct {
+	Name           string       `json:"name,omitempty"`
+	App            string       `json:"app,omitempty"`
+	Description    string       `json:"description,omitempty"`
+	Struct         string       `json:"struct,omitempty"`
+	IsLog          bool         `json:"is_log,omitempty"`
+	Chain          uint64       `json:"chain,omitempty"`
+	ShowExpiration bool         `json:"show_expiration,omitempty"`
+	ShowKey        bool         `json:"show_key,omitempty"`
+	Hide           bool         `json:"hide,omitempty"`
+	Input          []uiInput    `json:"input,omitempty"`
+	View           []viewOutput `json:"view,omitempty"`
+	winExpiration  *widget.Entry
+	winKey         *widget.Entry
+}
+
+func addInputItem(form *widget.Form, lst []uiInput) {
+	for i, it := range lst {
+		if it.Title != "" {
+			ent := widget.NewEntry()
+			if it.MultiLine {
+				ent = widget.NewMultiLineEntry()
+			}
+			if it.Value != "" {
+				ent.SetText(it.Value)
+			} else if it.Mode == diAddress {
+				ent.SetText(conf.GetWallet().AddressStr)
+			}
+			it.win = ent
+			lst[i] = it
+			if it.Hide {
+				continue
+			}
+			form.Append(it.Title, ent)
+		}
+		if len(it.Sub) > 0 {
+			addInputItem(form, it.Sub)
+		}
+	}
+}
+
+func addViewItem(form *widget.Form, lst []viewOutput) {
+	for i, it := range lst {
+		if it.Title != "" {
+			ent := widget.NewEntry()
+			if it.MultiLine {
+				ent = widget.NewMultiLineEntry()
+			}
+			ent.Disable()
+			form.Append(it.Title, ent)
+			it.win = ent
+			lst[i] = it
+		}
+		if len(it.Sub) > 0 {
+			addViewItem(form, it.Sub)
+		}
+	}
+}
+func showViewItemFromMap(lst []viewOutput, data map[string]interface{}) error {
+	for _, it := range lst {
+		switch it.Mode {
+		case doHex:
+			v := data[it.Key]
+			var in []byte
+			if v != nil {
+				d, _ := json.Marshal(v)
+				err := json.Unmarshal(d, &in)
+				if err != nil {
+					return err
+				}
+			}
+
+			if it.win != nil {
+				it.win.SetText(hex.EncodeToString(in))
+			}
+			if len(it.Sub) > 0 {
+				err := showViewItem(it.Sub, in)
+				if err != nil {
+					return err
+				}
+			}
+		case doMap:
+			v := data[it.Key]
+			var in map[string]interface{}
+			if v != nil {
+				d, _ := json.Marshal(v)
+				err := json.Unmarshal(d, &in)
+				if err != nil {
+					return err
+				}
+			}
+
+			if it.win != nil {
+				d, _ := json.MarshalIndent(in, "", "  ")
+				it.win.SetText(string(d))
+			}
+			if len(it.Sub) > 0 {
+				err := showViewItemFromMap(it.Sub, in)
+				if err != nil {
+					return err
+				}
+			}
+		case doList:
+			v := data[it.Key]
+			var in []interface{}
+			if v != nil {
+				d, _ := json.Marshal(v)
+				err := json.Unmarshal(d, &in)
+				if err != nil {
+					return err
+				}
+			}
+
+			if it.win != nil {
+				d, _ := json.MarshalIndent(in, "", "  ")
+				it.win.SetText(string(d))
+			}
+			if len(it.Sub) > 0 {
+				return fmt.Errorf("not support sub of list")
+			}
+		case doString:
+			v := data[it.Key]
+			var in string
+			if v != nil {
+				d, _ := json.Marshal(v)
+				err := json.Unmarshal(d, &in)
+				if err != nil {
+					return err
+				}
+			}
+			if it.win != nil {
+				it.win.SetText(in)
+			}
+			if len(it.Sub) > 0 {
+				err := showViewItem(it.Sub, []byte(in))
+				if err != nil {
+					return err
+				}
+			}
+		case doNumber:
+			v := data[it.Key]
+			var in int64
+			if v != nil {
+				d, _ := json.Marshal(v)
+				err := json.Unmarshal(d, &in)
+				if err != nil {
+					return err
+				}
+			}
+			if it.win != nil {
+				it.win.SetText(fmt.Sprintf("%d", in))
+			}
+			if len(it.Sub) > 0 {
+				return fmt.Errorf("not support")
+			}
+		}
+	}
+	return nil
+}
+
+func showViewItem(lst []viewOutput, data []byte) error {
+	for _, it := range lst {
+		switch it.Mode {
+		case doHex:
+			ld := data
+			if it.Length > 0 {
+				ld = data[:it.Length]
+				data = data[it.Length:]
+			}
+			if it.win != nil {
+				it.win.SetText(hex.EncodeToString(ld))
+			}
+			if len(it.Sub) > 0 {
+				err := showViewItem(it.Sub, ld)
+				if err != nil {
+					return err
+				}
+			}
+		case doMap:
+			ld := data
+			if it.Length > 0 {
+				ld = data[:it.Length]
+				data = data[it.Length:]
+			}
+			var info map[string]interface{}
+			err := json.Unmarshal(ld, &info)
+			if err != nil {
+				return err
+			}
+			if it.win != nil {
+				d, _ := json.MarshalIndent(info, "", "  ")
+				it.win.SetText(string(d))
+			}
+			if len(it.Sub) > 0 {
+				err = showViewItemFromMap(it.Sub, info)
+				if err != nil {
+					return err
+				}
+			}
+		case doList:
+			ld := data
+			if it.Length > 0 {
+				ld = data[:it.Length]
+				data = data[it.Length:]
+			}
+			var info []interface{}
+			err := json.Unmarshal(ld, &info)
+			if err != nil {
+				return err
+			}
+			if it.win != nil {
+				d, _ := json.MarshalIndent(info, "", "  ")
+				it.win.SetText(string(d))
+			}
+			if len(it.Sub) > 0 {
+				return fmt.Errorf("not support")
+			}
+		case doString:
+			ld := data
+			if it.Length > 0 {
+				ld = data[:it.Length]
+				data = data[it.Length:]
+			}
+			if it.win != nil {
+				it.win.SetText(string(ld))
+			}
+			if len(it.Sub) > 0 {
+				err := showViewItem(it.Sub, ld)
+				if err != nil {
+					return err
+				}
+			}
+		case doNumber:
+			ld := data
+			if it.Length > 0 {
+				ld = data[:it.Length]
+				data = data[it.Length:]
+			}
+			if it.win != nil {
+				var num big.Int
+				num.SetBytes(ld)
+				it.win.SetText(num.String())
+			}
+			if len(it.Sub) > 0 {
+				err := showViewItem(it.Sub, ld)
+				if err != nil {
+					return err
+				}
+			}
+		}
+	}
+	return nil
+}
+
+func newReadUI(w fyne.Window, in dReadUI) fyne.Widget {
+	inputForm := &widget.Form{}
+	viewForm := &widget.Form{}
+	var lastKey string
+	chainStr := fmt.Sprintf("%d", in.Chain)
+	if in.ShowKey {
+		in.winKey = widget.NewEntry()
+		viewForm.Append(res.GetLocalString("Key:"), in.winKey)
+	}
+	if in.ShowExpiration {
+		in.winExpiration = widget.NewEntry()
+		viewForm.Append(res.GetLocalString("Expiration:"), in.winExpiration)
+	}
+	if in.Description != "" {
+		inputForm.Append(res.GetLocalString("Description"),
+			widget.NewLabel(in.Description))
+	}
+
+	addInputItem(inputForm, in.Input)
+
+	empty := widget.NewLabel("")
+	viewData := func() {
+		// var localKey []byte
+		localKey, err := encodeRunInput(in.Input)
+		if err != nil {
+			dialog.ShowError(err, w)
+			return
+		}
+		if len(localKey) == 0 {
+			dialog.ShowError(fmt.Errorf("request key"), w)
+			return
+		}
+		k := hex.EncodeToString(localKey)
+		data, life := getStringOfDB(chainStr, in.App, in.Struct, k)
+		if life == 0 {
+			dialog.ShowError(fmt.Errorf("not found"), w)
+			return
+		}
+
+		log.Println("key:", localKey, data, life)
+		if in.ShowExpiration {
+			t := time.Unix(int64(life/1000), 0)
+			in.winExpiration.SetText(t.Local().String())
+		}
+		if in.ShowKey {
+			in.winKey.SetText(k)
+		}
+
+		hexData, _ := hex.DecodeString(data)
+		showViewItem(in.View, hexData)
+		lastKey = k
+	}
+	btnSearch := widget.NewButton(res.GetLocalString("Search"), viewData)
+	btnNext := widget.NewButton(res.GetLocalString("Next"), func() {
+		key := getNextKeyOfDB(chainStr, in.App, in.Struct, lastKey)
+		lastKey = key
+		if in.ShowKey {
+			in.winKey.SetText(key)
+		}
+		data, life := getStringOfDB(chainStr, in.App, in.Struct, key)
+		if life == 0 {
+			dialog.ShowError(fmt.Errorf("not found"), w)
+			return
+		}
+
+		log.Println("key:", key, data, life)
+		if in.ShowExpiration {
+			t := time.Unix(int64(life/1000), 0)
+			in.winExpiration.SetText(t.Local().String())
+		}
+		hexData, _ := hex.DecodeString(data)
+		showViewItem(in.View, hexData)
+	})
+	btns := widget.NewHBox(btnNext, empty, btnSearch)
+	searchItem := newFormItemWithObj(empty, btns)
+
+	addViewItem(viewForm, in.View)
+
+	return widget.NewVBox(inputForm, searchItem, viewForm)
+}
+
+type appRunUI struct {
+	Name        string    `json:"name,omitempty"`
+	App         string    `json:"app,omitempty"`
+	Description string    `json:"description,omitempty"`
+	Chains      []uint64  `json:"chains,omitempty"`
+	Hide        bool      `json:"hide,omitempty"`
+	Cost        float64   `json:"cost,omitempty"`
+	Energy      float64   `json:"energy,omitempty"`
+	Input       []uiInput `json:"input,omitempty"`
+}
+
+type uiConf struct {
+	ViewUI []dReadUI  `json:"view_ui,omitempty"`
+	RunUI  []appRunUI `json:"run_ui,omitempty"`
+}
+
+func encodeRunInputMap(in []uiInput, param map[string]interface{}) error {
+	for _, it := range in {
+		var data string
+		if it.win != nil {
+			data = it.win.Text
+		}
+		switch it.Mode {
+		case diHex:
+			if data == "" {
+				if it.EmptyEnable || enableEmptyType[it.Mode] {
+					continue
+				}
+				return fmt.Errorf("empty value:%s", it.Title)
+			}
+			d, err := hex.DecodeString(data)
+			if err != nil {
+				return err
+			}
+			param[it.Key] = d
+		case diAddress:
+			fallthrough
+		case diString:
+			if data == "" {
+				if it.EmptyEnable || enableEmptyType[it.Mode] {
+					continue
+				}
+				return fmt.Errorf("empty value:%s", it.Title)
+			}
+			param[it.Key] = data
+		case diFloat64:
+			if data == "" {
+				if it.EmptyEnable || enableEmptyType[it.Mode] {
+					continue
+				}
+				return fmt.Errorf("empty value:%s", it.Title)
+			}
+			number, err := strconv.ParseFloat(data, 64)
+			if err != nil {
+				return err
+			}
+			param[it.Key] = number
+		case diUint64:
+			if data == "" {
+				if it.EmptyEnable || enableEmptyType[it.Mode] {
+					continue
+				}
+				return fmt.Errorf("empty value:%s", it.Title)
+			}
+			number, err := strconv.ParseUint(data, 10, 64)
+			if err != nil {
+				return err
+			}
+			param[it.Key] = number
+		case diUint32:
+			if data == "" {
+				if it.EmptyEnable || enableEmptyType[it.Mode] {
+					continue
+				}
+				return fmt.Errorf("empty value:%s", it.Title)
+			}
+			number, err := strconv.ParseUint(data, 10, 32)
+			if err != nil {
+				return err
+			}
+			param[it.Key] = number
+		case diUint16:
+			if data == "" {
+				if it.EmptyEnable || enableEmptyType[it.Mode] {
+					continue
+				}
+				return fmt.Errorf("empty value:%s", it.Title)
+			}
+			number, err := strconv.ParseUint(data, 10, 16)
+			if err != nil {
+				return err
+			}
+			param[it.Key] = number
+		case diUint8:
+			if data == "" {
+				if it.EmptyEnable || enableEmptyType[it.Mode] {
+					continue
+				}
+				return fmt.Errorf("empty value:%s", it.Title)
+			}
+			number, err := strconv.ParseUint(data, 10, 8)
+			if err != nil {
+				return err
+			}
+			param[it.Key] = number
+		case diMap:
+			info := make(map[string]interface{})
+			err := encodeRunInputMap(it.Sub, info)
+			if err != nil {
+				return err
+			}
+			param[it.Key] = info
+		default:
+			return fmt.Errorf("unsupport:%d", it.Mode)
+		}
+	}
+	return nil
+}
+
+func encodeRunInput(in []uiInput) ([]byte, error) {
+	var out []byte
+	for _, it := range in {
+		var lv []byte
+		var data string
+		if it.win != nil {
+			data = it.win.Text
+		}
+		switch it.Mode {
+		case diAddress:
+			fallthrough
+		case diHex:
+			if data == "" {
+				if it.EmptyEnable || enableEmptyType[it.Mode] {
+					continue
+				}
+				return nil, fmt.Errorf("empty value:%s", it.Title)
+			}
+			d, err := hex.DecodeString(data)
+			if err != nil {
+				return nil, err
+			}
+			lv = d
+		case diString:
+			lv = []byte(data)
+		case diFloat64:
+			if data == "" {
+				if it.EmptyEnable || enableEmptyType[it.Mode] {
+					continue
+				}
+				return nil, fmt.Errorf("empty value:%s", it.Title)
+			}
+			number, err := strconv.ParseFloat(data, 64)
+			if err != nil {
+				return nil, err
+			}
+			lv = trans.Encode(number)
+		case diUint64:
+			if data == "" {
+				if it.EmptyEnable || enableEmptyType[it.Mode] {
+					continue
+				}
+				return nil, fmt.Errorf("empty value:%s", it.Title)
+			}
+			number, err := strconv.ParseUint(data, 10, 64)
+			if err != nil {
+				return nil, err
+			}
+			lv = trans.Encode(number)
+		case diUint32:
+			if data == "" {
+				if it.EmptyEnable || enableEmptyType[it.Mode] {
+					continue
+				}
+				return nil, fmt.Errorf("empty value:%s", it.Title)
+			}
+			number, err := strconv.ParseUint(data, 10, 32)
+			if err != nil {
+				return nil, err
+			}
+			lv = trans.Encode(uint32(number))
+		case diUint16:
+			if data == "" {
+				if it.EmptyEnable || enableEmptyType[it.Mode] {
+					continue
+				}
+				return nil, fmt.Errorf("empty value:%s", it.Title)
+			}
+			number, err := strconv.ParseUint(data, 10, 16)
+			if err != nil {
+				return nil, err
+			}
+			lv = trans.Encode(uint16(number))
+		case diUint8:
+			if data == "" {
+				if it.EmptyEnable || enableEmptyType[it.Mode] {
+					continue
+				}
+				return nil, fmt.Errorf("empty value:%s", it.Title)
+			}
+			number, err := strconv.ParseUint(data, 10, 8)
+			if err != nil {
+				return nil, err
+			}
+			lv = trans.Encode(uint8(number))
+		case diMap:
+			info := make(map[string]interface{})
+			err := encodeRunInputMap(it.Sub, info)
+			if err != nil {
+				return nil, err
+			}
+			lv, _ = json.Marshal(info)
+		case diHash:
+			lv = wallet.GetHash(out)
+			out = nil
+		default:
+			return nil, fmt.Errorf("unsupport:%d", it.Mode)
+		}
+		out = append(out, lv...)
+	}
+	return out, nil
+}
+
+func newRunUI(w fyne.Window, in appRunUI) fyne.Widget {
+	infoForm := &widget.Form{}
+	inputForm := &widget.Form{}
+	result := widget.NewEntry()
+	result.Disable()
+
+	// chainStr := fmt.Sprintf("%d", in.Chain)
+	if len(in.Chains) == 0 {
+		in.Chains = append(in.Chains, 1)
+	}
+	var chainArr []string
+	for _, c := range in.Chains {
+		chainArr = append(chainArr, fmt.Sprintf("%d", c))
+	}
+	chainID := in.Chains[0]
+	chain := widget.NewSelect(chainArr, func(s string) {
+		chainID, _ = strconv.ParseUint(s, 10, 64)
+	})
+	chain.SetSelected(chainArr[0])
+
+	amount := widget.NewEntry()
+	amount.SetText("0")
+	eng := widget.NewEntry()
+	eng.SetText("0")
+	infoForm.Append(res.GetLocalString("Description"),
+		widget.NewLabel(in.Description))
+	infoForm.Append(res.GetLocalString("Chain"), chain)
+
+	if in.Cost > 0 {
+		amount.SetText(fmt.Sprintf("%f", in.Cost))
+		infoForm.Append(res.GetLocalString("Cost"),
+			newFormItemWithUnit(amount, "govm"))
+	}
+	if in.Energy > 0 {
+		eng.SetText(fmt.Sprintf("%f", in.Energy))
+		infoForm.Append(res.GetLocalString("Energy"),
+			newFormItemWithUnit(eng, "govm"))
+	}
+	addInputItem(inputForm, in.Input)
+
+	inputForm.SubmitText = res.GetLocalString("Run")
+	inputForm.OnSubmit = func() {
+		result.SetText("")
+		var cost, energy uint64
+		if amount.Text != "" {
+			costF, err := strconv.ParseFloat(amount.Text, 10)
+			if err != nil {
+				dialog.ShowError(fmt.Errorf("error cost"), w)
+				return
+			}
+			amount.SetPlaceHolder(amount.Text)
+			amount.SetText("")
+			cost = uint64(costF * 1e9)
+		}
+		if eng.Text != "" {
+			engF, err := strconv.ParseFloat(eng.Text, 10)
+			if err != nil {
+				dialog.ShowError(fmt.Errorf("error cost"), w)
+				return
+			}
+			energy = uint64(engF * 1e9)
+		}
+
+		data, err := encodeRunInput(in.Input)
+		if err != nil {
+			dialog.ShowError(err, w)
+			return
+		}
+		log.Printf("run. chain:%d,app:%s,msg:%x\n", chainID, in.App, data)
+		key, err := runApp(chainID, cost, energy, in.App, "", data)
+		if err != nil {
+			dialog.ShowError(err, w)
+			return
+		}
+		result.SetText(key)
+	}
+	inputForm.Refresh()
+
+	return widget.NewVBox(infoForm, inputForm, result)
+}
+
+// CustomizeScreen Customize dApp ui
+func CustomizeScreen(w fyne.Window) fyne.CanvasObject {
+	c := conf.Get()
+	tb := widget.NewTabContainer()
+	d, _ := ioutil.ReadFile(c.DynamicUIFile)
+	var info uiConf
+	err := json.Unmarshal(d, &info)
+	if err != nil {
+		// fmt.Println("dReadUI:", err)
+		tb.Append(widget.NewTabItem("error", widget.NewLabel(fmt.Sprint(err))))
+		tb.SelectTabIndex(0)
+		return tb
+	}
+	for _, it := range info.RunUI {
+		if it.Hide {
+			continue
+		}
+		tb.Append(widget.NewTabItem("R_"+it.Name, newRunUI(w, it)))
+	}
+	for _, it := range info.ViewUI {
+		if it.Hide {
+			continue
+		}
+		tb.Append(widget.NewTabItem("V_"+it.Name, newReadUI(w, it)))
+	}
+	tb.SelectTabIndex(0)
+	return tb
+}
diff --git a/bin/gui/screens/history.go b/bin/gui/screens/history.go
index 0d66568..58fa4d4 100644
--- a/bin/gui/screens/history.go
+++ b/bin/gui/screens/history.go
@@ -1,12 +1,8 @@
 package screens
 
 import (
-	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"log"
-	"net/http"
-	"strconv"
 	"time"
 
 	"fyne.io/fyne"
@@ -16,68 +12,6 @@ import (
 	"github.com/lengzhao/wallet/bin/gui/res"
 )
 
-//statCoinLock
-//statCoinUnlock
-//statMiningCount
-//statMinerHit
-//statMinerReg
-//statTransferIn
-//statTransferOut
-//statMove
-//statAPPRun
-
-const coreName = "ff0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
-
-type dataInfo struct {
-	AppName    string `json:"app_name,omitempty"`
-	StructName string `json:"struct_name,omitempty"`
-	IsDBData   bool   `json:"is_db_data,omitempty"`
-	Key        string `json:"key,omitempty"`
-	Value      string `json:"value,omitempty"`
-	Life       uint64 `json:"life,omitempty"`
-}
-
-func getIntOfDB(chain, appName, structName, address string) uint64 {
-	value, _ := getStringOfDB(chain, appName, structName, address)
-	if value == "" {
-		return 0
-	}
-	out, err := strconv.ParseUint(value, 16, 64)
-	if err != nil {
-		log.Println("parse error.", value, err)
-	}
-
-	return out
-}
-func getStringOfDB(chain, appName, structName, address string) (string, uint64) {
-	if appName == "" {
-		appName = coreName
-	}
-	urlStr := conf.Get().APIServer
-	urlStr += "/api/v1/" + chain + "/data?app_name=" + appName
-	urlStr += "&is_db_data=true&struct_name=" + structName
-	urlStr += "&key=" + address
-	resp, err := http.Get(urlStr)
-	if err != nil {
-		log.Println("fail to get db.", urlStr, err)
-		return "", 0
-	}
-	defer resp.Body.Close()
-	data, err := ioutil.ReadAll(resp.Body)
-	if resp.StatusCode != http.StatusOK || len(data) == 0 {
-		log.Println("fail to get db.", urlStr, resp.Status, string(data))
-		return "", 0
-	}
-	info := dataInfo{}
-	err = json.Unmarshal(data, &info)
-	if err != nil || info.Value == "" {
-		log.Println("not value.", urlStr)
-		return "", 0
-	}
-	// log.Println("success to get:", urlStr, info.Value)
-	return info.Value, info.Life
-}
-
 func makeTransferOutList(w fyne.Window) fyne.Widget {
 	var his = make(map[string]string)
 	var numbers = make(map[string]uint64)
diff --git a/bin/gui/screens/master.go b/bin/gui/screens/master.go
index 9d7b2bc..2fecf96 100644
--- a/bin/gui/screens/master.go
+++ b/bin/gui/screens/master.go
@@ -24,6 +24,7 @@ func Master(a fyne.App) fyne.Window {
 		widget.NewTabItemWithIcon(res.GetLocalString("Transaction"), theme.MailSendIcon(), TransactionScreen(w)),
 		widget.NewTabItemWithIcon(res.GetLocalString("dAPP"), theme.ComputerIcon(), AppScreen(w)),
 		widget.NewTabItemWithIcon(res.GetLocalString("Task"), theme.FileTextIcon(), TaskScreen(w)),
+		widget.NewTabItemWithIcon(res.GetLocalString("Customize"), theme.FileAudioIcon(), CustomizeScreen(w)),
 		widget.NewTabItemWithIcon(res.GetLocalString("Search"), theme.SearchIcon(), SearchScreen(w)),
 		widget.NewTabItemWithIcon(res.GetLocalString("History"), theme.ContentPasteIcon(), HistoryScreen(w)),
 		widget.NewTabItemWithIcon(res.GetLocalString("Setting"), theme.SettingsIcon(), SettingScreen(w)))
diff --git a/bin/gui/screens/task.go b/bin/gui/screens/task.go
index 600c235..5006b6c 100644
--- a/bin/gui/screens/task.go
+++ b/bin/gui/screens/task.go
@@ -75,40 +75,6 @@ func statusToString(in int) string {
 	}
 }
 
-func runApp(chain, cost, energy uint64, app, prefix string, param []byte) (string, error) {
-	myWlt := conf.GetWallet()
-	trans := trans.NewTransaction(chain, myWlt.Address, cost)
-	var body []byte
-	var err error
-	if prefix != "" {
-		body, err = hex.DecodeString(prefix)
-		if err != nil {
-			return "", err
-		}
-	}
-	if len(param) > 0 {
-		body = append(body, param...)
-	}
-	trans.Energy = energy
-	err = trans.RunApp(app, body)
-	if err != nil {
-		return "", err
-	}
-	td := trans.GetSignData()
-	sign := myWlt.Sign(td)
-	trans.SetTheSign(sign)
-	td = trans.Output()
-	key := trans.Key[:]
-
-	err = postTrans(chain, td)
-	if err != nil {
-		// result.SetText(fmt.Sprintf("%s", err))
-		log.Println("fail to run app:", err)
-		return "", err
-	}
-	return hex.EncodeToString(key), nil
-}
-
 func makeTaskInfoTab(w fyne.Window) fyne.Widget {
 	c := conf.Get()
 	chain := widget.NewSelect(c.Chains, nil)
@@ -390,7 +356,13 @@ func makeActionTab(w fyne.Window) fyne.Widget {
 
 	clickEvent := func() {
 		result.SetText("")
-		showForm.Hide()
+		// showForm.Hide()
+		actionID.SetText("")
+		actionUser.SetText("")
+		actionStatus.SetText("")
+		actionReward.SetText("")
+		actionMsg.SetText("")
+
 		id, err := strconv.ParseUint(taskIndex.Text, 10, 64)
 		if err != nil || id == 0 {
 			result.SetText("error task id")
@@ -520,7 +492,7 @@ func makeActionTab(w fyne.Window) fyne.Widget {
 				}
 			}
 		}
-		showForm.Show()
+		// showForm.Show()
 		showForm.Refresh()
 	}
 	NextEvent := func() {
@@ -539,7 +511,11 @@ func makeActionTab(w fyne.Window) fyne.Widget {
 	form := &widget.Form{}
 	form.Append(res.GetLocalString("Task ID"), taskIndex)
 	form.Append(res.GetLocalString("Offset"), searchItem)
-	go clickEvent()
+	go func() {
+		num := getIntOfDB(chain.Selected, taskApp, "tApp", "0000")
+		taskIndex.SetText(fmt.Sprintf("%d", num))
+		clickEvent()
+	}()
 
 	return widget.NewVBox(form,
 		widget.NewGroup(res.GetLocalString("Action Info"), showForm), result)
diff --git a/bin/gui/usage.txt b/bin/gui/usage.txt
new file mode 100644
index 0000000..008a0d8
--- /dev/null
+++ b/bin/gui/usage.txt
@@ -0,0 +1,124 @@
+1. webset: https://govm.net/
+2. default password: govm_pwd@2019
+3. Important: Please back up the private key file wallet.key
+4. How to copy content: select content -> right click -> Copy
+5. How to create a dApp:
+    1. Sample code: https://github.com/lengzhao/dapp
+    2. Open the GUI wallet: govm.exe
+    3. dApp->New APP->Select chain->Code: Open->Select the app code->Submit
+    4. Copy the transaction ID, you can go to "Search" -> Transaction -> Paste that transaction ID -> Submit, you can query the specific information of the transaction
+    5. Wait a few minutes until the transaction is packaged
+6. How to customize the UI of a dApp
+    1. You can edit the custom ui file:dynamic_ui.json
+    2. All custom UIs are in the "Customize" page
+        1. Tab tabs beginning with V indicate the query UI
+        2. The tabs beginning with R represent the UI to run App
+        3. Code reference: screens/dynamic_ui.go
+    3. Next, we will introduce the configuration of custom UI "dynamic_ui.json"
+    4. View UI: view_ui, only query data, no cost
+        1. name: the name of the label
+        2. app: the name of the dAPP
+        3. description: introduction information, will be displayed on the tab page
+        4. struct: Each data is stored in the struct of the app, and the name of the struct is required for query
+        5. chain: The chain where the data is stored
+        6. show_expiration: Whether to display the expiration time of the data
+        7. hide: Whether to hide the tab, if set to true, the tab will not be displayed
+        8. input: Query parameter information list
+            1. mode: data entry mode
+                1. hex: The input is a hex string, such as "a101"
+                2. address: The input is a address, which will automatically fill in the user's wallet address
+                3. string: The input is a string
+                4. uint64/uint32/uint16/uint8: The input is a number
+                5. float: The input is a float
+                6. hash: Hash of the previous input data
+                7. map: The input is map[string]interface{}, which can include sub
+            2. title: The title of the data, which will be displayed on the left side of the input box
+            3. value: default value, can be empty
+            4. hide: Whether to hide the input, if it is a fixed input, it can be hidden
+            5. multi_line: whether the input box needs multiple lines
+            6. empty_enable: whether the input can be empty
+            7. sub: the sub-parameter of the loop, used when mode=map, the sample can check the input in run_ui
+        9. view: how to display the results of the query
+            1. mode: data display mode
+                1. hex: convert data to hex string. E.g. address
+                2. string: directly output data in string format
+                3. number: Data is parsed into digital
+                4. map: The data is parsed into map[string]interface{} data, which can carry sub to display specific information
+            2. title: The title of the data, which will be displayed on the left side of the input box
+            3. length: The length of the data, it is optional, if configured, the specified length data will be intercepted and processed
+            4. key: the key of the data, used when the data is in json format
+            5. multi_line: Whether to display in multiple lines
+            6. sub: cycle sub-parameter, used when mode=map
+    3. Run UI: run_ui, execution of dApp need energy
+        1. name: The name of the label
+        2. app: the name of the dAPP
+        3. description: Introduction information, will be displayed on the tab
+        4. chains: List of supported chains
+        5. hide: Whether to hide the label, if set to true, the label will not be displayed
+        6. cost:The cost required by the dAPP, the dAPP is free without setting
+        7. energy: The energy for executing the dAPP (to the miner), if not set, the default energy will be used
+        8. input: List of parameter information for executing the contract
+            Specific configuration refer to view_ui->input
+
+Simplified Chinese, zh: 中文
+1. 官网:https://govm.net/
+2. 默认密码:govm_pwd@2019
+3. 重要:请备份好私钥文件wallet.key
+4. 如何复制内容:选择内容->右键->Copy
+5. 如何创建dApp:
+    1. 样例代码:https://github.com/lengzhao/dapp
+    2. 打开GUI钱包:govm.exe
+    3. dApp->New APP->Select chain->Code: Open->Select the app code->Submit
+    4. 复制交易ID,可以在“查询”->交易->粘贴那个交易ID->Submit,可以查询交易的具体信息
+    5. 等几分钟,直到交易被打包
+6. 如何自定义dApp的UI
+    1. 请编辑自定义ui的文件:dynamic_ui.json
+    2. 所有的自定义UI在“自定义”页面中
+        1. 以V开头的标签页表示查询UI
+        2. 以R开头的标签页表示运行App的UI
+        3. 代码参考:screens/dynamic_ui.go
+    3. 接下来将介绍自定义UI的配置dynamic_ui.json
+    4. 查询UI:view_ui,只查询数据,不需要费用
+        1. name:标签的名字
+        2. app:dAPP的名字
+        3. description:介绍信息,将显示在标签页上
+        4. struct:每个数据都是存储在app的struct中,查询时需要struct的名字
+        5. chain:数据所在的chain
+        6. show_expiration:是否显示数据的过期时间
+        7. hide:是否隐藏标签,如果设置为true,该标签将不显示
+        8. input:查询的参数信息列表
+            1. mode:数据的入参模式
+                1. hex:输入的为hex字符串,如“a101”
+                2. address:入参为地址,将自动填充用户的钱包地址
+                3. string:输入的就是字符串
+                4. uint64/uint32/uint16/uint8:输入的为数字
+                5. float:输入的为浮点数
+                6. hash:对前面的输入的数据进行哈希计算
+                7. map:输入的是map[string]interface{},需要携带sub
+            2. title:数据的标题,将显示在输入框的左侧
+            3. value:默认值,可以为空
+            4. hide:是否隐藏该配置,如果是固定的输入,可以隐藏
+            5. multi_line:输入框是否需要多行
+            6. empty_enable:输入是否可以为空
+            7. sub:循环的子参数,mode=map时使用,样例可以查看run_ui中的input
+        9. view:查询得到的结果的显示方式
+            1. mode:数据的显示模式
+                1. hex:将数据转为hex string
+                2. string:直接将数据以string格式输出
+                3. number:数据直接解析为数字显示
+                4. map:数据解析为map[string]interface{}数据,可以携带sub进一步显示具体信息
+            2. title:数据的标题,将显示在输入框的左侧
+            3. length:数据的长度,可以不配置,如果配置,将截取指定长度,进行处理
+            4. key:数据的key,当数据为json格式时使用
+            5. multi_line:是否多行显示
+            6. sub:循环的子参数,mode=map时使用
+    3. 执行UI:run_ui,执行dApp需要费用
+        1. name:标签的名字
+        2. app:dAPP的名字
+        3. description:介绍信息,将显示在标签页上
+        4. chains:支持的chain列表
+        5. hide:是否隐藏标签,如果设置为true,该标签将不显示
+        6. cost:合约要求的费用,没有设置表示合约免费
+        7. energy:执行合约的手续费(给矿工的),不设置将使用默认手续费
+        8. input:执行合约的参数信息列表
+            具体的配置参考view_ui->input