forked from mirror/go-ethereum
commit
0adfa489de
After Width: | Height: | Size: 2.8 KiB |
@ -0,0 +1,35 @@ |
||||
import QtQuick 2.0 |
||||
import QtQuick.Controls 1.0; |
||||
import QtQuick.Layouts 1.0; |
||||
import Ethereum 1.0 |
||||
|
||||
ApplicationWindow { |
||||
minimumWidth: 500 |
||||
maximumWidth: 500 |
||||
maximumHeight: 100 |
||||
minimumHeight: 100 |
||||
|
||||
title: "Ethereum Dice" |
||||
|
||||
TextField { |
||||
id: textField |
||||
anchors.verticalCenter: parent.verticalCenter |
||||
anchors.horizontalCenter: parent.horizontalCenter |
||||
placeholderText: "Amount" |
||||
} |
||||
Label { |
||||
id: txHash |
||||
anchors.bottom: textField.top |
||||
anchors.bottomMargin: 5 |
||||
anchors.horizontalCenter: parent.horizontalCenter |
||||
} |
||||
Button { |
||||
anchors.top: textField.bottom |
||||
anchors.horizontalCenter: parent.horizontalCenter |
||||
anchors.topMargin: 5 |
||||
text: "Place bet" |
||||
onClicked: { |
||||
txHash.text = eth.createTx("e6716f9544a56c530d868e4bfbacb172315bdead", textField.text) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,9 @@ |
||||
import QtQuick 2.0 |
||||
import QtQuick.Controls 1.0; |
||||
import QtQuick.Layouts 1.0; |
||||
|
||||
Rectangle { |
||||
id: transactionView |
||||
visible: false |
||||
Text { text: "TX VIEW" } |
||||
} |
@ -0,0 +1,217 @@ |
||||
package ethui |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/hex" |
||||
"fmt" |
||||
"github.com/ethereum/eth-go" |
||||
"github.com/ethereum/eth-go/ethchain" |
||||
"github.com/ethereum/eth-go/ethdb" |
||||
"github.com/ethereum/eth-go/ethutil" |
||||
"github.com/niemeyer/qml" |
||||
"math/big" |
||||
"strings" |
||||
) |
||||
|
||||
// Block interface exposed to QML
|
||||
type Block struct { |
||||
Number int |
||||
Hash string |
||||
} |
||||
|
||||
type Tx struct { |
||||
Value, Hash, Address string |
||||
} |
||||
|
||||
func NewTxFromTransaction(tx *ethchain.Transaction) *Tx { |
||||
hash := hex.EncodeToString(tx.Hash()) |
||||
sender := hex.EncodeToString(tx.Recipient) |
||||
|
||||
return &Tx{Hash: hash, Value: ethutil.CurrencyToString(tx.Value), Address: sender} |
||||
} |
||||
|
||||
// Creates a new QML Block from a chain block
|
||||
func NewBlockFromBlock(block *ethchain.Block) *Block { |
||||
info := block.BlockInfo() |
||||
hash := hex.EncodeToString(block.Hash()) |
||||
|
||||
return &Block{Number: int(info.Number), Hash: hash} |
||||
} |
||||
|
||||
type Gui struct { |
||||
// The main application window
|
||||
win *qml.Window |
||||
// QML Engine
|
||||
engine *qml.Engine |
||||
component *qml.Common |
||||
// The ethereum interface
|
||||
eth *eth.Ethereum |
||||
|
||||
// The public Ethereum library
|
||||
lib *EthLib |
||||
|
||||
txDb *ethdb.LDBDatabase |
||||
|
||||
addr []byte |
||||
} |
||||
|
||||
// Create GUI, but doesn't start it
|
||||
func New(ethereum *eth.Ethereum) *Gui { |
||||
lib := &EthLib{blockManager: ethereum.BlockManager, blockChain: ethereum.BlockManager.BlockChain(), txPool: ethereum.TxPool} |
||||
db, err := ethdb.NewLDBDatabase("tx_database") |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
|
||||
key := ethutil.Config.Db.GetKeys()[0] |
||||
addr := key.Address() |
||||
|
||||
ethereum.BlockManager.WatchAddr(addr) |
||||
|
||||
return &Gui{eth: ethereum, lib: lib, txDb: db, addr: addr} |
||||
} |
||||
|
||||
func (ui *Gui) Start() { |
||||
defer ui.txDb.Close() |
||||
|
||||
// Register ethereum functions
|
||||
qml.RegisterTypes("Ethereum", 1, 0, []qml.TypeSpec{{ |
||||
Init: func(p *Block, obj qml.Object) { p.Number = 0; p.Hash = "" }, |
||||
}, { |
||||
Init: func(p *Tx, obj qml.Object) { p.Value = ""; p.Hash = ""; p.Address = "" }, |
||||
}}) |
||||
|
||||
ethutil.Config.Log.Infoln("[GUI] Starting GUI") |
||||
// Create a new QML engine
|
||||
ui.engine = qml.NewEngine() |
||||
// Load the main QML interface
|
||||
component, err := ui.engine.LoadFile("wallet.qml") |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
ui.engine.LoadFile("transactions.qml") |
||||
|
||||
ui.win = component.CreateWindow(nil) |
||||
|
||||
context := ui.engine.Context() |
||||
|
||||
// Expose the eth library and the ui library to QML
|
||||
context.SetVar("eth", ui.lib) |
||||
context.SetVar("ui", &UiLib{engine: ui.engine, eth: ui.eth}) |
||||
|
||||
// Register the ui as a block processor
|
||||
ui.eth.BlockManager.SecondaryBlockProcessor = ui |
||||
//ui.eth.TxPool.SecondaryProcessor = ui
|
||||
|
||||
// Add the ui as a log system so we can log directly to the UGI
|
||||
ethutil.Config.Log.AddLogSystem(ui) |
||||
|
||||
// Loads previous blocks
|
||||
go ui.setInitialBlockChain() |
||||
go ui.readPreviousTransactions() |
||||
go ui.update() |
||||
|
||||
ui.win.Show() |
||||
ui.win.Wait() |
||||
|
||||
ui.eth.Stop() |
||||
} |
||||
|
||||
func (ui *Gui) setInitialBlockChain() { |
||||
// Load previous 10 blocks
|
||||
chain := ui.eth.BlockManager.BlockChain().GetChain(ui.eth.BlockManager.BlockChain().CurrentBlock.Hash(), 10) |
||||
for _, block := range chain { |
||||
ui.ProcessBlock(block) |
||||
} |
||||
|
||||
} |
||||
|
||||
func (ui *Gui) readPreviousTransactions() { |
||||
it := ui.txDb.Db().NewIterator(nil, nil) |
||||
for it.Next() { |
||||
tx := ethchain.NewTransactionFromBytes(it.Value()) |
||||
|
||||
ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) |
||||
} |
||||
it.Release() |
||||
} |
||||
|
||||
func (ui *Gui) ProcessBlock(block *ethchain.Block) { |
||||
ui.win.Root().Call("addBlock", NewBlockFromBlock(block)) |
||||
} |
||||
|
||||
// Simple go routine function that updates the list of peers in the GUI
|
||||
func (ui *Gui) update() { |
||||
txChan := make(chan ethchain.TxMsg, 1) |
||||
ui.eth.TxPool.Subscribe(txChan) |
||||
|
||||
account := ui.eth.BlockManager.GetAddrState(ui.addr).Account |
||||
unconfirmedFunds := new(big.Int) |
||||
ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(account.Amount))) |
||||
for { |
||||
select { |
||||
case txMsg := <-txChan: |
||||
tx := txMsg.Tx |
||||
|
||||
if txMsg.Type == ethchain.TxPre { |
||||
if bytes.Compare(tx.Sender(), ui.addr) == 0 { |
||||
ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) |
||||
ui.txDb.Put(tx.Hash(), tx.RlpEncode()) |
||||
|
||||
ui.eth.BlockManager.GetAddrState(ui.addr).Nonce += 1 |
||||
unconfirmedFunds.Sub(unconfirmedFunds, tx.Value) |
||||
} else if bytes.Compare(tx.Recipient, ui.addr) == 0 { |
||||
ui.win.Root().Call("addTx", NewTxFromTransaction(tx)) |
||||
ui.txDb.Put(tx.Hash(), tx.RlpEncode()) |
||||
|
||||
unconfirmedFunds.Add(unconfirmedFunds, tx.Value) |
||||
} |
||||
|
||||
pos := "+" |
||||
if unconfirmedFunds.Cmp(big.NewInt(0)) >= 0 { |
||||
pos = "-" |
||||
} |
||||
val := ethutil.CurrencyToString(new(big.Int).Abs(ethutil.BigCopy(unconfirmedFunds))) |
||||
str := fmt.Sprintf("%v (%s %v)", ethutil.CurrencyToString(account.Amount), pos, val) |
||||
|
||||
ui.win.Root().Call("setWalletValue", str) |
||||
} else { |
||||
amount := account.Amount |
||||
if bytes.Compare(tx.Sender(), ui.addr) == 0 { |
||||
amount.Sub(account.Amount, tx.Value) |
||||
} else if bytes.Compare(tx.Recipient, ui.addr) == 0 { |
||||
amount.Add(account.Amount, tx.Value) |
||||
} |
||||
|
||||
ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", ethutil.CurrencyToString(amount))) |
||||
} |
||||
} |
||||
|
||||
/* |
||||
accountAmount := ui.eth.BlockManager.GetAddrState(ui.addr).Account.Amount |
||||
ui.win.Root().Call("setWalletValue", fmt.Sprintf("%v", accountAmount)) |
||||
|
||||
ui.win.Root().Call("setPeers", fmt.Sprintf("%d / %d", ui.eth.Peers().Len(), ui.eth.MaxPeers)) |
||||
|
||||
time.Sleep(1 * time.Second) |
||||
*/ |
||||
|
||||
} |
||||
} |
||||
|
||||
// Logging functions that log directly to the GUI interface
|
||||
func (ui *Gui) Println(v ...interface{}) { |
||||
str := strings.TrimRight(fmt.Sprintln(v...), "\n") |
||||
lines := strings.Split(str, "\n") |
||||
for _, line := range lines { |
||||
ui.win.Root().Call("addLog", line) |
||||
} |
||||
} |
||||
|
||||
func (ui *Gui) Printf(format string, v ...interface{}) { |
||||
str := strings.TrimRight(fmt.Sprintf(format, v...), "\n") |
||||
lines := strings.Split(str, "\n") |
||||
for _, line := range lines { |
||||
ui.win.Root().Call("addLog", line) |
||||
} |
||||
} |
@ -0,0 +1,60 @@ |
||||
package ethui |
||||
|
||||
import ( |
||||
"encoding/hex" |
||||
"fmt" |
||||
"github.com/ethereum/eth-go/ethchain" |
||||
"github.com/ethereum/eth-go/ethutil" |
||||
"strings" |
||||
) |
||||
|
||||
type EthLib struct { |
||||
blockManager *ethchain.BlockManager |
||||
blockChain *ethchain.BlockChain |
||||
txPool *ethchain.TxPool |
||||
} |
||||
|
||||
func (lib *EthLib) CreateTx(receiver, a, data string) string { |
||||
var hash []byte |
||||
if len(receiver) == 0 { |
||||
hash = ethchain.ContractAddr |
||||
} else { |
||||
var err error |
||||
hash, err = hex.DecodeString(receiver) |
||||
if err != nil { |
||||
return err.Error() |
||||
} |
||||
} |
||||
|
||||
k, _ := ethutil.Config.Db.Get([]byte("KeyRing")) |
||||
keyRing := ethutil.NewValueFromBytes(k) |
||||
|
||||
amount := ethutil.Big(a) |
||||
code := ethchain.Compile(strings.Split(data, "\n")) |
||||
tx := ethchain.NewTransaction(hash, amount, code) |
||||
tx.Nonce = lib.blockManager.GetAddrState(keyRing.Get(1).Bytes()).Nonce |
||||
|
||||
tx.Sign(keyRing.Get(0).Bytes()) |
||||
|
||||
lib.txPool.QueueTransaction(tx) |
||||
|
||||
if len(receiver) == 0 { |
||||
ethutil.Config.Log.Infof("Contract addr %x", tx.Hash()[12:]) |
||||
} else { |
||||
ethutil.Config.Log.Infof("Tx hash %x", tx.Hash()) |
||||
} |
||||
|
||||
return ethutil.Hex(tx.Hash()) |
||||
} |
||||
|
||||
func (lib *EthLib) GetBlock(hexHash string) *Block { |
||||
hash, err := hex.DecodeString(hexHash) |
||||
if err != nil { |
||||
return nil |
||||
} |
||||
|
||||
block := lib.blockChain.GetBlock(hash) |
||||
fmt.Println(block) |
||||
|
||||
return &Block{Number: int(block.BlockInfo().Number), Hash: ethutil.Hex(block.Hash())} |
||||
} |
@ -0,0 +1,40 @@ |
||||
package ethui |
||||
|
||||
import ( |
||||
"github.com/ethereum/eth-go" |
||||
"github.com/ethereum/eth-go/ethutil" |
||||
"github.com/niemeyer/qml" |
||||
) |
||||
|
||||
// UI Library that has some basic functionality exposed
|
||||
type UiLib struct { |
||||
engine *qml.Engine |
||||
eth *eth.Ethereum |
||||
connected bool |
||||
} |
||||
|
||||
// Opens a QML file (external application)
|
||||
func (ui *UiLib) Open(path string) { |
||||
component, err := ui.engine.LoadFile(path[7:]) |
||||
if err != nil { |
||||
ethutil.Config.Log.Debugln(err) |
||||
} |
||||
win := component.CreateWindow(nil) |
||||
|
||||
go func() { |
||||
win.Show() |
||||
win.Wait() |
||||
}() |
||||
} |
||||
|
||||
func (ui *UiLib) Connect(button qml.Object) { |
||||
if !ui.connected { |
||||
ui.eth.Start() |
||||
ui.connected = true |
||||
button.Set("enabled", false) |
||||
} |
||||
} |
||||
|
||||
func (ui *UiLib) ConnectToPeer(addr string) { |
||||
ui.eth.ConnectToPeer(addr) |
||||
} |
@ -0,0 +1,357 @@ |
||||
import QtQuick 2.0 |
||||
import QtQuick.Controls 1.0; |
||||
import QtQuick.Layouts 1.0; |
||||
import QtQuick.Dialogs 1.0; |
||||
import QtQuick.Window 2.1; |
||||
import QtQuick.Controls.Styles 1.1 |
||||
import Ethereum 1.0 |
||||
|
||||
ApplicationWindow { |
||||
id: root |
||||
|
||||
width: 900 |
||||
height: 600 |
||||
minimumHeight: 300 |
||||
|
||||
title: "Ethereal" |
||||
|
||||
MenuBar { |
||||
Menu { |
||||
title: "File" |
||||
MenuItem { |
||||
text: "Import App" |
||||
shortcut: "Ctrl+o" |
||||
onTriggered: openAppDialog.open() |
||||
} |
||||
} |
||||
|
||||
Menu { |
||||
title: "Network" |
||||
MenuItem { |
||||
text: "Add Peer" |
||||
shortcut: "Ctrl+p" |
||||
onTriggered: { |
||||
addPeerWin.visible = true |
||||
} |
||||
} |
||||
|
||||
MenuItem { |
||||
text: "Start" |
||||
onTriggered: ui.connect() |
||||
} |
||||
} |
||||
|
||||
Menu { |
||||
title: "Help" |
||||
MenuItem { |
||||
text: "About" |
||||
onTriggered: { |
||||
aboutWin.visible = true |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
property var blockModel: ListModel { |
||||
id: blockModel |
||||
} |
||||
|
||||
function setView(view) { |
||||
networkView.visible = false |
||||
historyView.visible = false |
||||
newTxView.visible = false |
||||
view.visible = true |
||||
//root.title = "Ethereal - " = view.title |
||||
} |
||||
|
||||
SplitView { |
||||
anchors.fill: parent |
||||
resizing: false |
||||
|
||||
Rectangle { |
||||
id: menu |
||||
Layout.minimumWidth: 80 |
||||
Layout.maximumWidth: 80 |
||||
anchors.bottom: parent.bottom |
||||
anchors.top: parent.top |
||||
//color: "#D9DDE7" |
||||
color: "#252525" |
||||
|
||||
ColumnLayout { |
||||
y: 50 |
||||
anchors.left: parent.left |
||||
anchors.right: parent.right |
||||
height: 200 |
||||
Image { |
||||
source: "tx.png" |
||||
anchors.horizontalCenter: parent.horizontalCenter |
||||
MouseArea { |
||||
anchors.fill: parent |
||||
onClicked: { |
||||
setView(historyView) |
||||
} |
||||
} |
||||
} |
||||
Image { |
||||
source: "new.png" |
||||
anchors.horizontalCenter: parent.horizontalCenter |
||||
MouseArea { |
||||
anchors.fill: parent |
||||
onClicked: { |
||||
setView(newTxView) |
||||
} |
||||
} |
||||
} |
||||
Image { |
||||
source: "net.png" |
||||
anchors.horizontalCenter: parent.horizontalCenter |
||||
MouseArea { |
||||
anchors.fill: parent |
||||
onClicked: { |
||||
setView(networkView) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
property var txModel: ListModel { |
||||
id: txModel |
||||
} |
||||
|
||||
Rectangle { |
||||
id: historyView |
||||
property var title: "Transactions" |
||||
anchors.right: parent.right |
||||
anchors.left: menu.right |
||||
anchors.bottom: parent.bottom |
||||
anchors.top: parent.top |
||||
TableView { |
||||
id: txTableView |
||||
anchors.fill: parent |
||||
TableViewColumn{ role: "value" ; title: "Value" ; width: 100 } |
||||
TableViewColumn{ role: "address" ; title: "Address" ; width: 430 } |
||||
|
||||
model: txModel |
||||
} |
||||
} |
||||
|
||||
Rectangle { |
||||
id: newTxView |
||||
property var title: "New transaction" |
||||
visible: false |
||||
anchors.right: parent.right |
||||
anchors.left: menu.right |
||||
anchors.bottom: parent.bottom |
||||
anchors.top: parent.top |
||||
color: "#00000000" |
||||
|
||||
ColumnLayout { |
||||
width: 400 |
||||
anchors.left: parent.left |
||||
anchors.top: parent.top |
||||
anchors.leftMargin: 5 |
||||
anchors.topMargin: 5 |
||||
TextField { |
||||
id: txAmount |
||||
width: 200 |
||||
placeholderText: "Amount" |
||||
} |
||||
|
||||
TextField { |
||||
id: txReceiver |
||||
placeholderText: "Receiver Address (or empty for contract)" |
||||
Layout.fillWidth: true |
||||
} |
||||
|
||||
Label { |
||||
text: "Transaction data" |
||||
} |
||||
TextArea { |
||||
id: codeView |
||||
anchors.topMargin: 5 |
||||
Layout.fillWidth: true |
||||
width: parent.width /2 |
||||
} |
||||
|
||||
Button { |
||||
text: "Send" |
||||
onClicked: { |
||||
console.log(eth.createTx(txReceiver.text, txAmount.text, codeView.text)) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
Rectangle { |
||||
id: networkView |
||||
property var title: "Network" |
||||
visible: false |
||||
anchors.right: parent.right |
||||
anchors.bottom: parent.bottom |
||||
anchors.top: parent.top |
||||
|
||||
TableView { |
||||
id: blockTable |
||||
width: parent.width |
||||
anchors.top: parent.top |
||||
anchors.bottom: logView.top |
||||
TableViewColumn{ role: "number" ; title: "#" ; width: 100 } |
||||
TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } |
||||
|
||||
model: blockModel |
||||
|
||||
onDoubleClicked: { |
||||
popup.visible = true |
||||
popup.block = eth.getBlock(blockModel.get(row).hash) |
||||
popup.hashLabel.text = popup.block.hash |
||||
} |
||||
} |
||||
|
||||
property var logModel: ListModel { |
||||
id: logModel |
||||
} |
||||
|
||||
TableView { |
||||
id: logView |
||||
width: parent.width |
||||
height: 150 |
||||
anchors.bottom: parent.bottom |
||||
TableViewColumn{ role: "description" ; title: "log" } |
||||
|
||||
model: logModel |
||||
} |
||||
} |
||||
} |
||||
|
||||
FileDialog { |
||||
id: openAppDialog |
||||
title: "Open QML Application" |
||||
onAccepted: { |
||||
ui.open(openAppDialog.fileUrl.toString()) |
||||
} |
||||
} |
||||
|
||||
statusBar: StatusBar { |
||||
RowLayout { |
||||
anchors.fill: parent |
||||
Button { |
||||
property var enabled: true |
||||
id: connectButton |
||||
onClicked: { |
||||
if(this.enabled) { |
||||
ui.connect(this) |
||||
} |
||||
} |
||||
text: "Connect" |
||||
} |
||||
Button { |
||||
id: importAppButton |
||||
anchors.left: connectButton.right |
||||
anchors.leftMargin: 5 |
||||
onClicked: openAppDialog.open() |
||||
text: "Import App" |
||||
} |
||||
|
||||
Label { |
||||
anchors.left: importAppButton.right |
||||
anchors.leftMargin: 5 |
||||
id: walletValueLabel |
||||
} |
||||
|
||||
Label { |
||||
anchors.right: peerImage.left |
||||
anchors.rightMargin: 5 |
||||
id: peerLabel |
||||
font.pixelSize: 8 |
||||
text: "0 / 0" |
||||
} |
||||
Image { |
||||
id: peerImage |
||||
anchors.right: parent.right |
||||
width: 10; height: 10 |
||||
source: "network.png" |
||||
} |
||||
} |
||||
} |
||||
|
||||
Window { |
||||
id: popup |
||||
visible: false |
||||
property var block |
||||
Label { |
||||
id: hashLabel |
||||
anchors.horizontalCenter: parent.horizontalCenter |
||||
anchors.verticalCenter: parent.verticalCenter |
||||
} |
||||
} |
||||
|
||||
Window { |
||||
id: addPeerWin |
||||
visible: false |
||||
minimumWidth: 230 |
||||
maximumWidth: 230 |
||||
maximumHeight: 50 |
||||
minimumHeight: 50 |
||||
|
||||
TextField { |
||||
id: addrField |
||||
anchors.verticalCenter: parent.verticalCenter |
||||
anchors.left: parent.left |
||||
anchors.leftMargin: 10 |
||||
placeholderText: "address:port" |
||||
} |
||||
Button { |
||||
anchors.left: addrField.right |
||||
anchors.verticalCenter: parent.verticalCenter |
||||
anchors.leftMargin: 5 |
||||
text: "Add" |
||||
onClicked: { |
||||
ui.connectToPeer(addrField.text) |
||||
addPeerWin.visible = false |
||||
} |
||||
} |
||||
} |
||||
|
||||
Window { |
||||
id: aboutWin |
||||
visible: false |
||||
title: "About" |
||||
minimumWidth: 300 |
||||
maximumWidth: 300 |
||||
maximumHeight: 200 |
||||
minimumHeight: 200 |
||||
|
||||
Text { |
||||
font.pointSize: 18 |
||||
text: "Eth Go" |
||||
} |
||||
|
||||
} |
||||
|
||||
function setWalletValue(value) { |
||||
walletValueLabel.text = value |
||||
} |
||||
|
||||
function addTx(tx) { |
||||
txModel.insert(0, {hash: tx.hash, address: tx.address, value: tx.value}) |
||||
} |
||||
|
||||
function addBlock(block) { |
||||
blockModel.insert(0, {number: block.number, hash: block.hash}) |
||||
} |
||||
|
||||
function addLog(str) { |
||||
if(str.len != 0) { |
||||
logModel.append({description: str}) |
||||
} |
||||
} |
||||
|
||||
function setPeers(text) { |
||||
peerLabel.text = text |
||||
} |
||||
} |
Loading…
Reference in new issue