diff --git a/README.md b/README.md index 2d4b128fc..8cdcfe2a6 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Status](http://cpt-obvious.ethercasts.com:8010/buildstatusimage?builder=go-ether Ethereum Go Client © 2014 Jeffrey Wilcke. -Current state: Proof of Concept 0.6.4. +Current state: Proof of Concept 0.6.5. For the development package please see the [eth-go package](https://github.com/ethereum/eth-go). diff --git a/ethereal/assets/debugger/debugger.qml b/ethereal/assets/debugger/debugger.qml deleted file mode 100644 index 34fe01253..000000000 --- a/ethereal/assets/debugger/debugger.qml +++ /dev/null @@ -1,377 +0,0 @@ -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: win - visible: false - title: "IceCREAM" - minimumWidth: 1280 - minimumHeight: 700 - width: 1290 - height: 750 - - property alias codeText: codeEditor.text - property alias dataText: rawDataField.text - - onClosing: { - //compileTimer.stop() - } - - MenuBar { - Menu { - title: "Debugger" - MenuItem { - text: "Run" - shortcut: "Ctrl+r" - onTriggered: debugCurrent() - } - - MenuItem { - text: "Next" - shortcut: "Ctrl+n" - onTriggered: dbg.next() - } - - MenuItem { - text: "Continue" - shortcut: "Ctrl+g" - onTriggered: dbg.continue() - } - MenuItem { - text: "Command" - shortcut: "Ctrl+l" - onTriggered: { - dbgCommand.focus = true - } - } - MenuItem { - text: "Focus code" - shortcut: "Ctrl+1" - onTriggered: { - codeEditor.focus = true - } - } - MenuItem { - text: "Focus data" - shortcut: "Ctrl+2" - onTriggered: { - rawDataField.focus = true - } - } - - /* - MenuItem { - text: "Close window" - shortcut: "Ctrl+w" - onTriggered: { - win.close() - } - } - */ - } - } - - - SplitView { - anchors.fill: parent - property var asmModel: ListModel { - id: asmModel - } - - TableView { - id: asmTableView - width: 200 - TableViewColumn{ role: "value" ; title: "" ; width: asmTableView.width - 2 } - model: asmModel - } - - Rectangle { - color: "#00000000" - anchors.left: asmTableView.right - anchors.right: parent.right - SplitView { - orientation: Qt.Vertical - anchors.fill: parent - - Rectangle { - color: "#00000000" - height: 330 - anchors.left: parent.left - anchors.right: parent.right - - TextArea { - id: codeEditor - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: settings.left - focus: true - - /* - Timer { - id: compileTimer - interval: 500 ; running: true ; repeat: true - onTriggered: { - dbg.autoComp(codeEditor.text) - } - } - */ - } - - Column { - id: settings - spacing: 5 - width: 300 - height: parent.height - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - - Label { - text: "Arbitrary data" - } - TextArea { - id: rawDataField - anchors.left: parent.left - anchors.right: parent.right - height: 150 - } - - Label { - text: "Amount" - } - TextField { - id: txValue - width: 200 - placeholderText: "Amount" - validator: RegExpValidator { regExp: /\d*/ } - } - Label { - text: "Amount of gas" - } - TextField { - id: txGas - width: 200 - validator: RegExpValidator { regExp: /\d*/ } - text: "10000" - placeholderText: "Gas" - } - Label { - text: "Gas price" - } - TextField { - id: txGasPrice - width: 200 - placeholderText: "Gas price" - text: "1000000000000" - validator: RegExpValidator { regExp: /\d*/ } - } - } - } - - SplitView { - orientation: Qt.Vertical - id: inspectorPane - height: 500 - - SplitView { - orientation: Qt.Horizontal - height: 150 - - TableView { - id: stackTableView - property var stackModel: ListModel { - id: stackModel - } - height: parent.height - width: 300 - TableViewColumn{ role: "value" ; title: "Temp" ; width: 200 } - model: stackModel - } - - TableView { - id: memoryTableView - property var memModel: ListModel { - id: memModel - } - height: parent.height - width: parent.width - stackTableView.width - TableViewColumn{ id:mnumColmn ; role: "num" ; title: "#" ; width: 50} - TableViewColumn{ role: "value" ; title: "Memory" ; width: 750} - model: memModel - } - } - - Rectangle { - height: 100 - width: parent.width - TableView { - id: storageTableView - property var memModel: ListModel { - id: storageModel - } - height: parent.height - width: parent.width - TableViewColumn{ id: key ; role: "key" ; title: "#" ; width: storageTableView.width / 2} - TableViewColumn{ role: "value" ; title: "Storage" ; width: storageTableView.width / 2} - model: storageModel - } - } - - Rectangle { - height: 200 - width: parent.width - TableView { - id: logTableView - property var logModel: ListModel { - id: logModel - } - height: parent.height - width: parent.width - TableViewColumn{ id: message ; role: "message" ; title: "log" ; width: logTableView.width - 2 } - model: logModel - } - } - } - } - } - } - - function exec() { - dbg.execCommand(dbgCommand.text); - dbgCommand.text = ""; - } - statusBar: StatusBar { - height: 30 - - - TextField { - id: dbgCommand - y: 1 - x: asmTableView.width - width: 500 - placeholderText: "Debugger (type 'help')" - Keys.onReturnPressed: { - exec() - } - } - } - - toolBar: ToolBar { - height: 30 - RowLayout { - spacing: 5 - - Button { - property var enabled: true - id: debugStart - onClicked: { - debugCurrent() - } - text: "Debug" - } - - Button { - property var enabled: true - id: debugNextButton - onClicked: { - dbg.next() - } - text: "Next" - } - - Button { - id: debugContinueButton - onClicked: { - dbg.continue() - } - text: "Continue" - } - } - - - ComboBox { - id: snippets - anchors.right: parent.right - model: ListModel { - ListElement { text: "Snippets" ; value: "" } - ListElement { text: "Call Contract" ; value: "var[2] in;\nvar ret;\n\nin[0] = \"arg1\"\nin[1] = 0xdeadbeef\n\nvar success = call(0x0c542ddea93dae0c2fcb2cf175f03ad80d6be9a0, 0, 7000, in, ret)\n\nreturn ret" } - } - onCurrentIndexChanged: { - if(currentIndex != 0) { - var code = snippets.model.get(currentIndex).value; - codeEditor.insert(codeEditor.cursorPosition, code) - } - } - } - - } - - function debugCurrent() { - dbg.debug(txValue.text, txGas.text, txGasPrice.text, codeEditor.text, rawDataField.text) - } - - function setAsm(asm) { - asmModel.append({asm: asm}) - } - - function clearAsm() { - asmModel.clear() - } - - function setInstruction(num) { - asmTableView.selection.clear() - asmTableView.selection.select(num) - } - - function setMem(mem) { - memModel.append({num: mem.num, value: mem.value}) - } - function clearMem(){ - memModel.clear() - } - - function setStack(stack) { - stackModel.append({value: stack}) - } - function addDebugMessage(message){ - debuggerLog.append({value: message}) - } - - function clearStack() { - stackModel.clear() - } - - function clearStorage() { - storageModel.clear() - } - - function setStorage(storage) { - storageModel.append({key: storage.key, value: storage.value}) - } - - function setLog(msg) { - // Remove first item once we've reached max log items - if(logModel.count > 250) { - logModel.remove(0) - } - - if(msg.len != 0) { - if(logTableView.flickableItem.atYEnd) { - logModel.append({message: msg}) - logTableView.positionViewAtRow(logTableView.rowCount - 1, ListView.Contain) - } else { - logModel.append({message: msg}) - } - } - } - - function clearLog() { - logModel.clear() - } -} diff --git a/ethereal/assets/ext/filter.js b/ethereal/assets/ext/filter.js deleted file mode 100644 index 5c1c03aad..000000000 --- a/ethereal/assets/ext/filter.js +++ /dev/null @@ -1,31 +0,0 @@ -var Filter = function(options) { - this.callbacks = {}; - this.seed = Math.floor(Math.random() * 1000000); - this.options = options; - - if(options == "chain") { - eth.registerFilterString(options, this.seed); - } else if(typeof options === "object") { - eth.registerFilter(options, this.seed); - } -}; - -Filter.prototype.changed = function(callback) { - var cbseed = Math.floor(Math.random() * 1000000); - eth.registerFilterCallback(this.seed, cbseed); - - var self = this; - message.connect(function(messages, seed, callbackSeed) { - if(seed == self.seed && callbackSeed == cbseed) { - callback.call(self, messages); - } - }); -}; - -Filter.prototype.uninstall = function() { - eth.uninstallFilter(this.seed) -} - -Filter.prototype.messages = function() { - return JSON.parse(eth.messages(this.options)) -} diff --git a/ethereal/assets/ext/pre.js b/ethereal/assets/ext/pre.js deleted file mode 100644 index 3e8a534e9..000000000 --- a/ethereal/assets/ext/pre.js +++ /dev/null @@ -1,37 +0,0 @@ -// Helper function for generating pseudo callbacks and sending data to the QML part of the application -function postData(data, cb) { - data._seed = Math.floor(Math.random() * 1000000) - if(cb) { - eth._callbacks[data._seed] = cb; - } - - if(data.args === undefined) { - data.args = []; - } - - navigator.qt.postMessage(JSON.stringify(data)); -} - -navigator.qt.onmessage = function(ev) { - var data = JSON.parse(ev.data) - - if(data._event !== undefined) { - eth.trigger(data._event, data.data); - } else { - if(data._seed) { - var cb = eth._callbacks[data._seed]; - if(cb) { - // Figure out whether the returned data was an array - // array means multiple return arguments (multiple params) - if(data.data instanceof Array) { - cb.apply(this, data.data) - } else { - cb.call(this, data.data) - } - - // Remove the "trigger" callback - delete eth._callbacks[ev._seed]; - } - } - } -} diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml deleted file mode 100644 index 90cc42a1f..000000000 --- a/ethereal/assets/qml/wallet.qml +++ /dev/null @@ -1,753 +0,0 @@ -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 - -import "../ext/filter.js" as Eth - -ApplicationWindow { - id: root - - property alias miningButtonText: miningButton.text - - - width: 900 - height: 600 - minimumHeight: 300 - - title: "Ether browser" - - // This signal is used by the filter API. The filter API connects using this signal handler from - // the different QML files and plugins. - signal message(var callback, int seed, int seedCallback); - function invokeFilterCallback(data, receiverSeed, callbackSeed) { - var messages = JSON.parse(data) - // Signal handler - message(messages, receiverSeed, callbackSeed); - } - - TextField { - id: copyElementHax - visible: false - } - - function copyToClipboard(text) { - copyElementHax.text = text - copyElementHax.selectAll() - copyElementHax.copy() - } - - // Takes care of loading all default plugins - Component.onCompleted: { - var walletView = addPlugin("./views/wallet.qml", {noAdd: true, section: "ethereum", active: true}) - var historyView = addPlugin("./views/history.qml", {noAdd: true, section: "legacy"}) - var newTxView = addPlugin("./views/transaction.qml", {noAdd: true, section: "legacy"}) - var chainView = addPlugin("./views/chain.qml", {noAdd: true, section: "legacy"}) - var infoView = addPlugin("./views/info.qml", {noAdd: true, section: "legacy"}) - var pendingTxView = addPlugin("./views/pending_tx.qml", {noAdd: true, section: "legacy"}) - var pendingTxView = addPlugin("./views/javascript.qml", {noAdd: true, section: "legacy"}) - - // Call the ready handler - gui.done() - - } - - function addPlugin(path, options) { - var component = Qt.createComponent(path); - if(component.status != Component.Ready) { - if(component.status == Component.Error) { - console.debug("Error:"+ component.errorString()); - } - - return - } - - var views = mainSplit.addComponent(component, options) - views.menuItem.path = path - - mainSplit.views.push(views); - - if(!options.noAdd) { - gui.addPlugin(path) - } - - return views.view - } - - MenuBar { - Menu { - title: "File" - MenuItem { - text: "Import App" - shortcut: "Ctrl+o" - onTriggered: { - generalFileDialog.show(true, importApp) - } - } - - MenuItem { - text: "Browser" - onTriggered: eth.openBrowser() - } - - MenuItem { - text: "Add plugin" - onTriggered: { - generalFileDialog.show(true, function(path) { - addPlugin(path, {canClose: true, section: "apps"}) - }) - } - } - - MenuSeparator {} - - MenuItem { - text: "Import key" - shortcut: "Ctrl+i" - onTriggered: { - generalFileDialog.show(true, function(path) { - gui.importKey(path) - }) - } - } - - MenuItem { - text: "Export keys" - shortcut: "Ctrl+e" - onTriggered: { - generalFileDialog.show(false, function(path) { - }) - } - } - } - - Menu { - title: "Developer" - MenuItem { - text: "Debugger" - shortcut: "Ctrl+d" - onTriggered: eth.startDebugger() - } - - MenuItem { - text: "Import Tx" - onTriggered: { - txImportDialog.visible = true - } - } - - MenuItem { - text: "Run JS file" - onTriggered: { - generalFileDialog.show(true, function(path) { - eth.evalJavascriptFile(path) - }) - } - } - - MenuItem { - text: "Dump state" - onTriggered: { - generalFileDialog.show(false, function(path) { - // Empty hash for latest - gui.dumpState("", path) - }) - } - } - - MenuSeparator {} - - MenuItem { - id: miningSpeed - text: "Mining: Turbo" - onTriggered: { - gui.toggleTurboMining() - if(text == "Mining: Turbo") { - text = "Mining: Normal"; - } else { - text = "Mining: Turbo"; - } - } - } - } - - Menu { - title: "Network" - MenuItem { - text: "Add Peer" - shortcut: "Ctrl+p" - onTriggered: { - addPeerWin.visible = true - } - } - MenuItem { - text: "Show Peers" - shortcut: "Ctrl+e" - onTriggered: { - peerWindow.visible = true - } - } - } - - Menu { - title: "Help" - MenuItem { - text: "About" - onTriggered: { - aboutWin.visible = true - } - } - } - - } - - statusBar: StatusBar { - height: 32 - RowLayout { - Button { - id: miningButton - text: "Start Mining" - onClicked: { - gui.toggleMining() - } - } - - Button { - id: importAppButton - text: "Browser" - onClicked: { - eth.openBrowser() - } - } - - RowLayout { - Label { - id: walletValueLabel - - font.pixelSize: 10 - styleColor: "#797979" - } - } - } - - Label { - y: 6 - objectName: "miningLabel" - visible: true - font.pixelSize: 10 - anchors.right: lastBlockLabel.left - anchors.rightMargin: 5 - } - - Label { - y: 6 - id: lastBlockLabel - objectName: "lastBlockLabel" - visible: true - text: "" - font.pixelSize: 10 - anchors.right: peerGroup.left - anchors.rightMargin: 5 - } - - ProgressBar { - id: syncProgressIndicator - visible: false - objectName: "syncProgressIndicator" - y: 3 - width: 140 - indeterminate: true - anchors.right: peerGroup.left - anchors.rightMargin: 5 - } - - RowLayout { - id: peerGroup - y: 7 - anchors.right: parent.right - MouseArea { - onDoubleClicked: peerWindow.visible = true - anchors.fill: parent - } - - Label { - id: peerLabel - font.pixelSize: 8 - text: "0 / 0" - } - Image { - id: peerImage - width: 10; height: 10 - source: "../network.png" - } - } - } - - - property var blockModel: ListModel { - id: blockModel - } - - SplitView { - property var views: []; - - id: mainSplit - anchors.fill: parent - resizing: false - - function setView(view, menu) { - for(var i = 0; i < views.length; i++) { - views[i].view.visible = false - - views[i].menuItem.border.color = "#00000000" - views[i].menuItem.color = "#00000000" - } - view.visible = true - - menu.border.color = "#CCCCCC" - menu.color = "#FFFFFFFF" - } - - function addComponent(component, options) { - var view = mainView.createView(component, options) - view.visible = false - view.anchors.fill = mainView - - if( !view.hasOwnProperty("iconSource") ) { - console.log("Could not load plugin. Property 'iconSourc' not found on view."); - return; - } - - var menuItem = menu.createMenuItem(view.iconSource, view, options); - if( view.hasOwnProperty("menuItem") ) { - view.menuItem = menuItem; - } - - if( view.hasOwnProperty("onReady") ) { - view.onReady.call(view) - } - - if( options.active ) { - setView(view, menuItem) - } - - - return {view: view, menuItem: menuItem} - } - - /********************* - * Main menu. - ********************/ - Rectangle { - id: menu - Layout.minimumWidth: 180 - Layout.maximumWidth: 180 - anchors.top: parent.top - color: "#ececec" - - Component { - id: menuItemTemplate - Rectangle { - id: menuItem - property var view; - property var path; - - property alias title: label.text - property alias icon: icon.source - property alias secondaryTitle: secondary.text - - width: 180 - height: 28 - border.color: "#00000000" - border.width: 1 - radius: 5 - color: "#00000000" - - anchors { - left: parent.left - leftMargin: 4 - } - - MouseArea { - anchors.fill: parent - onClicked: { - mainSplit.setView(view, menuItem) - } - } - - Image { - id: icon - height: 20 - width: 20 - anchors { - left: parent.left - verticalCenter: parent.verticalCenter - leftMargin: 3 - } - MouseArea { - anchors.fill: parent - onClicked: { - menuItem.closeApp() - } - } - } - - Text { - id: label - anchors { - left: icon.right - verticalCenter: parent.verticalCenter - leftMargin: 3 - } - - color: "#0D0A01" - font.pixelSize: 12 - } - - Text { - id: secondary - anchors { - right: parent.right - rightMargin: 8 - verticalCenter: parent.verticalCenter - } - color: "#AEADBE" - font.pixelSize: 12 - } - - - function closeApp() { - if(this.view.hasOwnProperty("onDestroy")) { - this.view.onDestroy.call(this.view) - } - - this.view.destroy() - this.destroy() - gui.removePlugin(this.path) - } - } - } - - function createMenuItem(icon, view, options) { - if(options === undefined) { - options = {}; - } - - var section; - switch(options.section) { - case "ethereum": - section = menuDefault; - break; - case "legacy": - section = menuLegacy; - break; - default: - section = menuApps; - break; - } - - var comp = menuItemTemplate.createObject(section) - - comp.view = view - comp.title = view.title - comp.icon = view.iconSource - /* - if(view.secondary !== undefined) { - comp.secondary = view.secondary - } - */ - - return comp - - /* - if(options.canClose) { - //comp.closeButton.visible = options.canClose - } - */ - } - - ColumnLayout { - id: menuColumn - y: 10 - width: parent.width - anchors.left: parent.left - anchors.right: parent.right - spacing: 3 - - Text { - text: "ETHEREUM" - font.bold: true - anchors { - left: parent.left - leftMargin: 5 - } - color: "#888888" - } - - ColumnLayout { - id: menuDefault - spacing: 3 - anchors { - left: parent.left - right: parent.right - } - } - - - Text { - text: "APPS" - font.bold: true - anchors { - left: parent.left - leftMargin: 5 - } - color: "#888888" - } - - ColumnLayout { - id: menuApps - spacing: 3 - anchors { - left: parent.left - right: parent.right - } - } - - Text { - text: "DEBUG" - font.bold: true - anchors { - left: parent.left - leftMargin: 5 - } - color: "#888888" - } - - ColumnLayout { - id: menuLegacy - spacing: 3 - anchors { - left: parent.left - right: parent.right - } - } - } - } - - /********************* - * Main view - ********************/ - Rectangle { - id: mainView - color: "#00000000" - - anchors.right: parent.right - anchors.left: menu.right - anchors.bottom: parent.bottom - anchors.top: parent.top - - function createView(component) { - var view = component.createObject(mainView) - - return view; - } - } - - - } - - - /****************** - * Dialogs - *****************/ - FileDialog { - id: generalFileDialog - property var callback; - onAccepted: { - var path = this.fileUrl.toString(); - callback.call(this, path); - } - - function show(selectExisting, callback) { - generalFileDialog.callback = callback; - generalFileDialog.selectExisting = selectExisting; - - this.open(); - } - } - - - /****************** - * Wallet functions - *****************/ - function importApp(path) { - var ext = path.split('.').pop() - if(ext == "html" || ext == "htm") { - eth.openHtml(path) - }else if(ext == "qml"){ - addPlugin(path, {canClose: true, section: "apps"}) - } - } - - - function setWalletValue(value) { - walletValueLabel.text = value - } - - function loadPlugin(name) { - console.log("Loading plugin" + name) - var view = mainView.addPlugin(name) - } - - function setPeers(text) { - peerLabel.text = text - } - - function addPeer(peer) { - // We could just append the whole peer object but it cries if you try to alter them - peerModel.append({ip: peer.ip, port: peer.port, lastResponse:timeAgo(peer.lastSend), latency: peer.latency, version: peer.version}) - } - - function resetPeers(){ - peerModel.clear() - } - - function timeAgo(unixTs){ - var lapsed = (Date.now() - new Date(unixTs*1000)) / 1000 - return (lapsed + " seconds ago") - } - - function convertToPretty(unixTs){ - var a = new Date(unixTs*1000); - var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; - var year = a.getFullYear(); - var month = months[a.getMonth()]; - var date = a.getDate(); - var hour = a.getHours(); - var min = a.getMinutes(); - var sec = a.getSeconds(); - var time = date+' '+month+' '+year+' '+hour+':'+min+':'+sec ; - return time; - } - - /********************** - * Windows - *********************/ - Window { - id: peerWindow - //flags: Qt.CustomizeWindowHint | Qt.Tool | Qt.WindowCloseButtonHint - height: 200 - width: 700 - Rectangle { - anchors.fill: parent - property var peerModel: ListModel { - id: peerModel - } - TableView { - anchors.fill: parent - id: peerTable - model: peerModel - TableViewColumn{width: 100; role: "ip" ; title: "IP" } - TableViewColumn{width: 60; role: "port" ; title: "Port" } - TableViewColumn{width: 140; role: "lastResponse"; title: "Last event" } - TableViewColumn{width: 100; role: "latency"; title: "Latency" } - TableViewColumn{width: 260; role: "version" ; title: "Version" } - } - } - } - - Window { - id: aboutWin - visible: false - title: "About" - minimumWidth: 350 - maximumWidth: 350 - maximumHeight: 200 - minimumHeight: 200 - - Image { - id: aboutIcon - height: 150 - width: 150 - fillMode: Image.PreserveAspectFit - smooth: true - source: "../facet.png" - x: 10 - y: 10 - } - - Text { - anchors.left: aboutIcon.right - anchors.leftMargin: 10 - font.pointSize: 12 - text: "

Ethereal - Aitne


Development

Jeffrey Wilcke
Maran Hidskes
Viktor Trón
" - } - } - - Window { - id: txImportDialog - minimumWidth: 270 - maximumWidth: 270 - maximumHeight: 50 - minimumHeight: 50 - TextField { - id: txImportField - width: 170 - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: 10 - onAccepted: { - } - } - Button { - anchors.left: txImportField.right - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 5 - text: "Import" - onClicked: { - eth.importTx(txImportField.text) - txImportField.visible = false - } - } - Component.onCompleted: { - addrField.focus = true - } - } - - 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" - onAccepted: { - eth.connectToPeer(addrField.text) - addPeerWin.visible = false - } - } - Button { - anchors.left: addrField.right - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 5 - text: "Add" - onClicked: { - eth.connectToPeer(addrField.text) - addPeerWin.visible = false - } - } - Component.onCompleted: { - addrField.focus = true - } - } - } diff --git a/ethereal/assets/qml/webapp.qml b/ethereal/assets/qml/webapp.qml deleted file mode 100644 index ca6860036..000000000 --- a/ethereal/assets/qml/webapp.qml +++ /dev/null @@ -1,347 +0,0 @@ -import QtQuick 2.0 -import QtWebKit 3.0 -import QtWebKit.experimental 1.0 -import QtQuick.Controls 1.0; -import QtQuick.Controls.Styles 1.0 -import QtQuick.Layouts 1.0; -import QtQuick.Window 2.1; -import Ethereum 1.0 - -ApplicationWindow { - id: window - title: "Ethereum" - width: 1000 - height: 800 - minimumHeight: 300 - - property alias url: webview.url - property alias webView: webview - - Item { - objectName: "root" - id: root - anchors.fill: parent - state: "inspectorShown" - - RowLayout { - id: navBar - height: 40 - anchors { - left: parent.left - right: parent.right - leftMargin: 7 - } - - Button { - id: back - onClicked: { - webview.goBack() - } - style: ButtonStyle { - background: Image { - source: "../back.png" - width: 30 - height: 30 - } - } - } - - TextField { - anchors { - left: back.right - right: toggleInspector.left - leftMargin: 5 - rightMargin: 5 - } - id: uriNav - y: parent.height / 2 - this.height / 2 - - Keys.onReturnPressed: { - webview.url = this.text; - } - } - - Button { - id: toggleInspector - anchors { - right: parent.right - } - iconSource: "../bug.png" - onClicked: { - if(inspector.visible == true){ - inspector.visible = false - }else{ - inspector.visible = true - inspector.url = webview.experimental.remoteInspectorUrl - } - } - } - } - - - WebView { - objectName: "webView" - id: webview - anchors { - left: parent.left - right: parent.right - bottom: parent.bottom - top: navBar.bottom - } - onTitleChanged: { window.title = title } - - property var cleanPath: false - onNavigationRequested: { - if(!this.cleanPath) { - var uri = request.url.toString(); - if(!/.*\:\/\/.*/.test(uri)) { - uri = "http://" + uri; - } - - var reg = /(^https?\:\/\/(?:www\.)?)([a-zA-Z0-9_\-]*\.eth)(.*)/ - - if(reg.test(uri)) { - uri.replace(reg, function(match, pre, domain, path) { - uri = pre; - - var lookup = ui.lookupDomain(domain.substring(0, domain.length - 4)); - var ip = []; - for(var i = 0, l = lookup.length; i < l; i++) { - ip.push(lookup.charCodeAt(i)) - } - - if(ip.length != 0) { - uri += lookup; - } else { - uri += domain; - } - - uri += path; - }); - } - - this.cleanPath = true; - - webview.url = uri; - } else { - // Prevent inf loop. - this.cleanPath = false; - } - } - - - experimental.preferences.javascriptEnabled: true - experimental.preferences.navigatorQtObjectEnabled: true - experimental.preferences.developerExtrasEnabled: true - experimental.userScripts: ["../ext/pre.js", "../ext/big.js", "../ext/string.js", "../ext/ethereum.js"] - experimental.onMessageReceived: { - console.log("[onMessageReceived]: ", message.data) - // TODO move to messaging.js - var data = JSON.parse(message.data) - - try { - switch(data.call) { - case "getCoinBase": - postData(data._seed, eth.coinBase()) - - break - - case "getIsListening": - postData(data._seed, eth.isListening()) - - break - - case "getIsMining": - postData(data._seed, eth.isMining()) - - break - - case "getPeerCount": - postData(data._seed, eth.peerCount()) - - break - - case "getTxCountAt": - require(1) - postData(data._seed, eth.txCountAt(data.args[0])) - - break - - case "getBlockByNumber": - var block = eth.blockByNumber(data.args[0]) - postData(data._seed, block) - - break - - case "getBlockByHash": - var block = eth.blockByHash(data.args[0]) - postData(data._seed, block) - - break - - case "transact": - require(5) - - var tx = eth.transact(data.args[0], data.args[1], data.args[2],data.args[3],data.args[4],data.args[5]) - postData(data._seed, tx) - - break - - case "getStorage": - require(2); - - var stateObject = eth.stateObject(data.args[0]) - var storage = stateObject.storageAt(data.args[1]) - postData(data._seed, storage) - - break - - case "getEachStorage": - require(1); - var storage = JSON.parse(eth.eachStorage(data.args[0])) - postData(data._seed, storage) - - break - - case "getTransactionsFor": - require(1); - var txs = eth.transactionsFor(data.args[0], true) - postData(data._seed, txs) - - break - - case "getBalance": - require(1); - - postData(data._seed, eth.stateObject(data.args[0]).value()); - - break - - case "getKey": - var key = eth.key().privateKey; - - postData(data._seed, key) - break - - /* - case "watch": - require(1) - eth.watch(data.args[0], data.args[1]); - - break - */ - case "watch": - require(2) - eth.watch(data.args[0], data.args[1]) - - case "disconnect": - require(1) - postData(data._seed, null) - - break; - - case "getSecretToAddress": - require(1) - postData(data._seed, eth.secretToAddress(data.args[0])) - - break; - - case "messages": - require(1); - - var messages = JSON.parse(eth.getMessages(data.args[0])) - postData(data._seed, messages) - - break - - case "mutan": - require(1) - - var code = eth.compileMutan(data.args[0]) - postData(data._seed, "0x"+code) - - break; - } - } catch(e) { - console.log(data.call + ": " + e) - - postData(data._seed, null); - } - } - - function post(seed, data) { - console.log("data", data) - postData(data._seed, data) - } - - function require(args, num) { - if(args.length < num) { - throw("required argument count of "+num+" got "+args.length); - } - } - function postData(seed, data) { - webview.experimental.postMessage(JSON.stringify({data: data, _seed: seed})) - } - function postEvent(event, data) { - webview.experimental.postMessage(JSON.stringify({data: data, _event: event})) - } - - function onWatchedCb(data, id) { - var messages = JSON.parse(data) - postEvent("watched:"+id, messages) - } - - function onNewBlockCb(block) { - postEvent("block:new", block) - } - function onObjectChangeCb(stateObject) { - postEvent("object:"+stateObject.address(), stateObject) - } - function onStorageChangeCb(storageObject) { - var ev = ["storage", storageObject.stateAddress, storageObject.address].join(":"); - postEvent(ev, [storageObject.address, storageObject.value]) - } - } - - - Rectangle { - id: sizeGrip - color: "gray" - visible: false - height: 10 - anchors { - left: root.left - right: root.right - } - y: Math.round(root.height * 2 / 3) - - MouseArea { - anchors.fill: parent - drag.target: sizeGrip - drag.minimumY: 0 - drag.maximumY: root.height - drag.axis: Drag.YAxis - } - } - - WebView { - id: inspector - visible: false - anchors { - left: root.left - right: root.right - top: sizeGrip.bottom - bottom: root.bottom - } - } - - states: [ - State { - name: "inspectorShown" - PropertyChanges { - target: inspector - } - } - ] - } -} diff --git a/ethereum/flags.go b/ethereum/flags.go index 5ed208411..c488e6314 100644 --- a/ethereum/flags.go +++ b/ethereum/flags.go @@ -74,6 +74,7 @@ func Init() { flag.IntVar(&LogLevel, "loglevel", int(ethlog.InfoLevel), "loglevel: 0-5: silent,error,warn,info,debug,debug detail)") flag.BoolVar(&DiffTool, "difftool", false, "creates output for diff'ing. Sets LogLevel=0") flag.StringVar(&DiffType, "diff", "all", "sets the level of diff output [vm, all]. Has no effect if difftool=false") + flag.BoolVar(&ShowGenesis, "genesis", false, "Dump the genesis block") flag.BoolVar(&Dump, "dump", false, "output the ethereum state in JSON format. Sub args [number, hash]") flag.StringVar(&DumpHash, "hash", "", "specify arg in hex") diff --git a/ethereum/main.go b/ethereum/main.go index b7c8ea1e7..df9737c1f 100644 --- a/ethereum/main.go +++ b/ethereum/main.go @@ -13,7 +13,7 @@ import ( const ( ClientIdentifier = "Ethereum(G)" - Version = "0.6.4" + Version = "0.6.5" ) var logger = ethlog.NewLogger("CLI") @@ -40,6 +40,12 @@ func main() { utils.InitLogging(Datadir, LogFile, LogLevel, DebugFile) db := utils.NewDatabase() + err := utils.DBSanityCheck(db) + if err != nil { + logger.Errorln(err) + + os.Exit(1) + } keyManager := utils.NewKeyManager(KeyStore, Datadir, db) @@ -70,6 +76,8 @@ func main() { os.Exit(1) } + fmt.Printf("RLP: %x\nstate: %x\nhash: %x\n", ethutil.Rlp(block), block.GetRoot(), block.Hash()) + // Leave the Println. This needs clean output for piping fmt.Printf("%s\n", block.State().Dump()) diff --git a/javascript/javascript_runtime.go b/javascript/javascript_runtime.go index c794c32a8..ffc672a63 100644 --- a/javascript/javascript_runtime.go +++ b/javascript/javascript_runtime.go @@ -42,7 +42,7 @@ func (jsre *JSRE) LoadExtFile(path string) { } func (jsre *JSRE) LoadIntFile(file string) { - assetPath := path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "ethereal", "assets", "ext") + assetPath := path.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "mist", "assets", "ext") jsre.LoadExtFile(path.Join(assetPath, file)) } diff --git a/javascript/types.go b/javascript/types.go index afa0a41c6..53a2977a8 100644 --- a/javascript/types.go +++ b/javascript/types.go @@ -88,6 +88,10 @@ func (self *JSEthereum) GetStateObject(addr string) otto.Value { return self.toVal(&JSStateObject{ethpipe.NewJSObject(self.JSPipe.World().SafeGet(ethutil.Hex2Bytes(addr))), self}) } +func (self *JSEthereum) Peers() otto.Value { + return self.toVal(self.JSPipe.Peers()) +} + func (self *JSEthereum) Transact(key, recipient, valueStr, gasStr, gasPriceStr, dataStr string) otto.Value { r, err := self.JSPipe.Transact(key, recipient, valueStr, gasStr, gasPriceStr, dataStr) if err != nil { diff --git a/ethereal/assets/back.png b/mist/assets/back.png similarity index 100% rename from ethereal/assets/back.png rename to mist/assets/back.png diff --git a/mist/assets/browser.png b/mist/assets/browser.png new file mode 100644 index 000000000..1d7348170 Binary files /dev/null and b/mist/assets/browser.png differ diff --git a/ethereal/assets/bug.png b/mist/assets/bug.png similarity index 100% rename from ethereal/assets/bug.png rename to mist/assets/bug.png diff --git a/ethereal/assets/close.png b/mist/assets/close.png similarity index 100% rename from ethereal/assets/close.png rename to mist/assets/close.png diff --git a/mist/assets/debugger/debugger.qml b/mist/assets/debugger/debugger.qml new file mode 100644 index 000000000..8d54b5b5d --- /dev/null +++ b/mist/assets/debugger/debugger.qml @@ -0,0 +1,435 @@ +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: win + visible: false + title: "IceCREAM" + minimumWidth: 1280 + minimumHeight: 700 + width: 1290 + height: 750 + + property alias codeText: codeEditor.text + property alias dataText: rawDataField.text + + onClosing: { + dbg.Stop() + } + + menuBar: MenuBar { + Menu { + title: "Edit" + MenuItem { + text: "Focus code" + shortcut: "Ctrl+1" + onTriggered: { + codeEditor.focus = true + } + } + MenuItem { + text: "Focus data" + shortcut: "Ctrl+2" + onTriggered: { + rawDataField.focus = true + } + } + + MenuItem { + text: "Command" + shortcut: "Ctrl+l" + onTriggered: { + dbgCommand.focus = true + } + } + } + + Menu { + title: "Debugger" + MenuItem { + text: "Run" + shortcut: "Ctrl+r" + onTriggered: debugCurrent() + } + + MenuItem { + text: "Stop" + onTriggered: dbp.stop() + } + + MenuSeparator {} + + MenuItem { + text: "Next" + shortcut: "Ctrl+n" + onTriggered: dbg.next() + } + + MenuItem { + text: "Continue" + shortcut: "Ctrl+g" + onTriggered: dbg.continue() + } + } + } + + + SplitView { + anchors.fill: parent + property var asmModel: ListModel { + id: asmModel + } + + TableView { + id: asmTableView + width: 200 + headerVisible: false + TableViewColumn{ role: "value" ; title: "" ; width: asmTableView.width - 2 } + model: asmModel + /* + alternatingRowColors: false + itemDelegate: Item { + Rectangle { + anchors.fill: parent + color: "#DDD" + Text { + anchors { + left: parent.left + right: parent.right + leftMargin: 10 + verticalCenter: parent.verticalCenter + } + color: "#333" + elide: styleData.elideMode + text: styleData.value + font.pixelSize: 11 + MouseArea { + acceptedButtons: Qt.LeftButton + anchors.fill: parent + onClicked: { + mouse.accepted = true + } + } + } + } + } + */ + } + + Rectangle { + color: "#00000000" + anchors.left: asmTableView.right + anchors.right: parent.right + SplitView { + orientation: Qt.Vertical + anchors.fill: parent + + Rectangle { + color: "#00000000" + height: 330 + anchors.left: parent.left + anchors.right: parent.right + + TextArea { + id: codeEditor + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: settings.left + focus: true + + /* + Timer { + id: compileTimer + interval: 500 ; running: true ; repeat: true + onTriggered: { + dbg.autoComp(codeEditor.text) + } + } + */ + } + + Column { + id: settings + spacing: 5 + width: 300 + height: parent.height + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + + Label { + text: "Arbitrary data" + } + TextArea { + id: rawDataField + anchors.left: parent.left + anchors.right: parent.right + height: 150 + } + + Label { + text: "Amount" + } + TextField { + id: txValue + width: 200 + placeholderText: "Amount" + validator: RegExpValidator { regExp: /\d*/ } + } + Label { + text: "Amount of gas" + } + TextField { + id: txGas + width: 200 + validator: RegExpValidator { regExp: /\d*/ } + text: "10000" + placeholderText: "Gas" + } + Label { + text: "Gas price" + } + TextField { + id: txGasPrice + width: 200 + placeholderText: "Gas price" + text: "1000000000000" + validator: RegExpValidator { regExp: /\d*/ } + } + } + } + + SplitView { + orientation: Qt.Vertical + id: inspectorPane + height: 500 + + SplitView { + orientation: Qt.Horizontal + height: 150 + + TableView { + id: stackTableView + property var stackModel: ListModel { + id: stackModel + } + height: parent.height + width: 300 + TableViewColumn{ role: "value" ; title: "Local VM stack" ; width: stackTableView.width - 2 } + model: stackModel + } + + TableView { + id: memoryTableView + property var memModel: ListModel { + id: memModel + } + height: parent.height + width: parent.width - stackTableView.width + TableViewColumn{ id:mnumColmn ; role: "num" ; title: "#" ; width: 50 } + TableViewColumn{ role: "value" ; title: "Memory" ; width: 650 } + model: memModel + } + } + + Rectangle { + height: 100 + width: parent.width + TableView { + id: storageTableView + property var memModel: ListModel { + id: storageModel + } + height: parent.height + width: parent.width + TableViewColumn{ id: key ; role: "key" ; title: "#" ; width: storageTableView.width / 2 - 1} + TableViewColumn{ role: "value" ; title: "Storage" ; width: storageTableView.width / 2 - 1} + model: storageModel + } + } + + Rectangle { + height: 200 + width: parent.width * 0.66 + TableView { + id: logTableView + property var logModel: ListModel { + id: logModel + } + height: parent.height + width: parent.width + TableViewColumn{ id: message ; role: "message" ; title: "log" ; width: logTableView.width - 2 } + model: logModel + } + } + + } + } + } + } + + function exec() { + dbg.execCommand(dbgCommand.text); + dbgCommand.text = ""; + } + statusBar: StatusBar { + height: 30 + + + TextField { + id: dbgCommand + y: 1 + x: asmTableView.width + width: 500 + placeholderText: "Debugger (type 'help')" + Keys.onReturnPressed: { + exec() + } + } + + RowLayout { + anchors.left: dbgCommand.right + anchors.leftMargin: 10 + spacing: 5 + y: parent.height / 2 - this.height / 2 + + Text { + objectName: "stackFrame" + font.pixelSize: 10 + text: "stack ptr: 0" + } + + Text { + objectName: "stackSize" + font.pixelSize: 10 + text: "stack size: 0" + } + + Text { + objectName: "memSize" + font.pixelSize: 10 + text: "mem size: 0" + } + } + } + + toolBar: ToolBar { + height: 30 + RowLayout { + spacing: 10 + + Button { + property var enabled: true + id: debugStart + onClicked: { + debugCurrent() + } + text: "Debug" + } + + Button { + property var enabled: true + id: debugNextButton + onClicked: { + dbg.next() + } + text: "Next" + } + + Button { + id: debugContinueButton + onClicked: { + dbg.continue() + } + text: "Continue" + } + } + + + ComboBox { + id: snippets + anchors.right: parent.right + model: ListModel { + ListElement { text: "Snippets" ; value: "" } + ListElement { text: "Call Contract" ; value: "var[2] in;\nvar ret;\n\nin[0] = \"arg1\"\nin[1] = 0xdeadbeef\n\nvar success = call(0x0c542ddea93dae0c2fcb2cf175f03ad80d6be9a0, 0, 7000, in, ret)\n\nreturn ret" } + } + onCurrentIndexChanged: { + if(currentIndex != 0) { + var code = snippets.model.get(currentIndex).value; + codeEditor.insert(codeEditor.cursorPosition, code) + } + } + } + + } + + function debugCurrent() { + dbg.debug(txValue.text, txGas.text, txGasPrice.text, codeEditor.text, rawDataField.text) + } + + function setAsm(asm) { + asmModel.append({asm: asm}) + } + + function clearAsm() { + asmModel.clear() + } + + function setInstruction(num) { + asmTableView.selection.clear() + asmTableView.selection.select(num) + asmTableView.positionViewAtRow(num, ListView.Center) + } + + function setMem(mem) { + memModel.append({num: mem.num, value: mem.value}) + } + function clearMem(){ + memModel.clear() + } + + function setStack(stack) { + stackModel.append({value: stack}) + } + function addDebugMessage(message){ + debuggerLog.append({value: message}) + } + + function clearStack() { + stackModel.clear() + } + + function clearStorage() { + storageModel.clear() + } + + function setStorage(storage) { + storageModel.append({key: storage.key, value: storage.value}) + } + + function setLog(msg) { + // Remove first item once we've reached max log items + if(logModel.count > 250) { + logModel.remove(0) + } + + if(msg.len != 0) { + if(logTableView.flickableItem.atYEnd) { + logModel.append({message: msg}) + logTableView.positionViewAtRow(logTableView.rowCount - 1, ListView.Contain) + } else { + logModel.append({message: msg}) + } + } + } + + function clearLog() { + logModel.clear() + } +} diff --git a/ethereal/assets/ext/big.js b/mist/assets/ext/big.js similarity index 100% rename from ethereal/assets/ext/big.js rename to mist/assets/ext/big.js diff --git a/ethereal/assets/ext/ethereum.js b/mist/assets/ext/ethereum.js similarity index 100% rename from ethereal/assets/ext/ethereum.js rename to mist/assets/ext/ethereum.js diff --git a/mist/assets/ext/filter.js b/mist/assets/ext/filter.js new file mode 100644 index 000000000..c23706249 --- /dev/null +++ b/mist/assets/ext/filter.js @@ -0,0 +1,49 @@ +var ethx = { + prototype: Object, + + watch: function(options) { + return new Filter(options); + }, + + note: function() { + var args = Array.prototype.slice.call(arguments, 0); + var o = [] + for(var i = 0; i < args.length; i++) { + o.push(args[i].toString()) + } + + eth.notef(o); + }, +}; + +var Filter = function(options) { + this.callbacks = []; + this.options = options; + + if(options === "chain") { + this.id = eth.newFilterString(options); + } else if(typeof options === "object") { + this.id = eth.newFilter(options); + } +}; + +Filter.prototype.changed = function(callback) { + this.callbacks.push(callback); + + var self = this; + messages.connect(function(messages, id) { + if(id == self.id) { + for(var i = 0; i < self.callbacks.length; i++) { + self.callbacks[i].call(self, messages); + } + } + }); +}; + +Filter.prototype.uninstall = function() { + eth.uninstallFilter(this.id) +} + +Filter.prototype.messages = function() { + return eth.messages(this.id) +} diff --git a/ethereal/assets/ext/home.html b/mist/assets/ext/home.html similarity index 100% rename from ethereal/assets/ext/home.html rename to mist/assets/ext/home.html diff --git a/mist/assets/ext/html_messaging.js b/mist/assets/ext/html_messaging.js new file mode 100644 index 000000000..3c67c77ea --- /dev/null +++ b/mist/assets/ext/html_messaging.js @@ -0,0 +1,481 @@ +// The magic return variable. The magic return variable will be set during the execution of the QML call. +(function(window) { + function message(type, data) { + document.title = JSON.stringify({type: type, data: data}); + + return window.____returnData; + } + + function isPromise(o) { + return typeof o === "object" && o.then + } + + window.eth = { + _callbacks: {}, + _events: {}, + prototype: Object(), + + toHex: function(str) { + var hex = ""; + for(var i = 0; i < str.length; i++) { + var n = str.charCodeAt(i).toString(16); + hex += n.length < 2 ? '0' + n : n; + } + + return hex; + }, + + toAscii: function(hex) { + // Find termination + var str = ""; + var i = 0, l = hex.length; + for(; i < l; i+=2) { + var code = hex.charCodeAt(i) + if(code == 0) { + break; + } + + str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); + } + + return str; + }, + + fromAscii: function(str, pad) { + if(pad === undefined) { + pad = 32 + } + + var hex = this.toHex(str); + + while(hex.length < pad*2) + hex += "00"; + + return hex + }, + + block: function(numberOrHash) { + return new Promise(function(resolve, reject) { + var func; + if(typeof numberOrHash == "string") { + func = "getBlockByHash"; + } else { + func = "getBlockByNumber"; + } + + postData({call: func, args: [numberOrHash]}, function(block) { + if(block) + resolve(block); + else + reject("not found"); + + }); + }); + }, + + transact: function(params) { + if(params === undefined) { + params = {}; + } + + if(params.endowment !== undefined) + params.value = params.endowment; + if(params.code !== undefined) + params.data = params.code; + + + var promises = [] + if(isPromise(params.to)) { + promises.push(params.to.then(function(_to) { params.to = _to; })); + } + if(isPromise(params.from)) { + promises.push(params.from.then(function(_from) { params.from = _from; })); + } + + if(isPromise(params.data)) { + promises.push(params.data.then(function(_code) { params.data = _code; })); + } else { + if(typeof params.data === "object") { + data = ""; + for(var i = 0; i < params.data.length; i++) { + data += params.data[i] + } + } else { + data = params.data; + } + } + + // Make sure everything is string + var fields = ["value", "gas", "gasPrice"]; + for(var i = 0; i < fields.length; i++) { + if(params[fields[i]] === undefined) { + params[fields[i]] = ""; + } + params[fields[i]] = params[fields[i]].toString(); + } + + // Load promises then call the last "transact". + return Q.all(promises).then(function() { + return new Promise(function(resolve, reject) { + postData({call: "transact", args: params}, function(data) { + if(data[1]) + reject(data[0]); + else + resolve(data[0]); + }); + }); + }) + }, + + compile: function(code) { + return new Promise(function(resolve, reject) { + postData({call: "compile", args: [code]}, function(data) { + if(data[1]) + reject(data[0]); + else + resolve(data[0]); + }); + }); + }, + + balanceAt: function(address) { + var promises = []; + + if(isPromise(address)) { + promises.push(address.then(function(_address) { address = _address; })); + } + + return Q.all(promises).then(function() { + return new Promise(function(resolve, reject) { + postData({call: "getBalanceAt", args: [address]}, function(balance) { + resolve(balance); + }); + }); + }); + }, + + countAt: function(address) { + var promises = []; + + if(isPromise(address)) { + promises.push(address.then(function(_address) { address = _address; })); + } + + return Q.all(promises).then(function() { + return new Promise(function(resolve, reject) { + postData({call: "getCountAt", args: [address]}, function(count) { + resolve(count); + }); + }); + }); + }, + + codeAt: function(address) { + var promises = []; + + if(isPromise(address)) { + promises.push(address.then(function(_address) { address = _address; })); + } + + return Q.all(promises).then(function() { + return new Promise(function(resolve, reject) { + postData({call: "getCodeAt", args: [address]}, function(code) { + resolve(code); + }); + }); + }); + }, + + storageAt: function(address, storageAddress) { + var promises = []; + + if(isPromise(address)) { + promises.push(address.then(function(_address) { address = _address; })); + } + + if(isPromise(storageAddress)) { + promises.push(storageAddress.then(function(_sa) { storageAddress = _sa; })); + } + + return Q.all(promises).then(function() { + return new Promise(function(resolve, reject) { + postData({call: "getStorageAt", args: [address, storageAddress]}, function(entry) { + resolve(entry); + }); + }); + }); + }, + + stateAt: function(address, storageAddress) { + return this.storageAt(address, storageAddress); + }, + + call: function(params) { + if(params === undefined) { + params = {}; + } + + if(params.endowment !== undefined) + params.value = params.endowment; + if(params.code !== undefined) + params.data = params.code; + + + var promises = [] + if(isPromise(params.to)) { + promises.push(params.to.then(function(_to) { params.to = _to; })); + } + if(isPromise(params.from)) { + promises.push(params.from.then(function(_from) { params.from = _from; })); + } + + if(isPromise(params.data)) { + promises.push(params.data.then(function(_code) { params.data = _code; })); + } else { + if(typeof params.data === "object") { + data = ""; + for(var i = 0; i < params.data.length; i++) { + data += params.data[i] + } + } else { + data = params.data; + } + } + + // Make sure everything is string + var fields = ["value", "gas", "gasPrice"]; + for(var i = 0; i < fields.length; i++) { + if(params[fields[i]] === undefined) { + params[fields[i]] = ""; + } + params[fields[i]] = params[fields[i]].toString(); + } + + // Load promises then call the last "transact". + return Q.all(promises).then(function() { + return new Promise(function(resolve, reject) { + postData({call: "call", args: params}, function(data) { + if(data[1]) + reject(data[0]); + else + resolve(data[0]); + }); + }); + }) + }, + + watch: function(params) { + return new Filter(params); + }, + + secretToAddress: function(key) { + var promises = []; + if(isPromise(key)) { + promises.push(key.then(function(_key) { key = _key; })); + } + + return Q.all(promises).then(function() { + return new Promise(function(resolve, reject) { + postData({call: "getSecretToAddress", args: [key]}, function(address) { + resolve(address); + }); + }); + }); + }, + + on: function(event, cb) { + if(eth._events[event] === undefined) { + eth._events[event] = []; + } + + eth._events[event].push(cb); + + return this + }, + + off: function(event, cb) { + if(eth._events[event] !== undefined) { + var callbacks = eth._events[event]; + for(var i = 0; i < callbacks.length; i++) { + if(callbacks[i] === cb) { + delete callbacks[i]; + } + } + } + + return this + }, + + trigger: function(event, data) { + var callbacks = eth._events[event]; + if(callbacks !== undefined) { + for(var i = 0; i < callbacks.length; i++) { + // Figure out whether the returned data was an array + // array means multiple return arguments (multiple params) + if(data instanceof Array) { + callbacks[i].apply(this, data); + } else { + callbacks[i].call(this, data); + } + } + } + }, + }; + + // Eth object properties + Object.defineProperty(eth, "key", { + get: function() { + return new Promise(function(resolve, reject) { + postData({call: "getKey"}, function(k) { + resolve(k); + }); + }); + }, + }); + + Object.defineProperty(eth, "gasPrice", { + get: function() { + return "1000000000000" + } + }); + + Object.defineProperty(eth, "coinbase", { + get: function() { + return new Promise(function(resolve, reject) { + postData({call: "getCoinBase"}, function(coinbase) { + resolve(coinbase); + }); + }); + }, + }); + + Object.defineProperty(eth, "listening", { + get: function() { + return new Promise(function(resolve, reject) { + postData({call: "getIsListening"}, function(listening) { + resolve(listening); + }); + }); + }, + }); + + + Object.defineProperty(eth, "mining", { + get: function() { + return new Promise(function(resolve, reject) { + postData({call: "getIsMining"}, function(mining) { + resolve(mining); + }); + }); + }, + }); + + Object.defineProperty(eth, "peerCount", { + get: function() { + return new Promise(function(resolve, reject) { + postData({call: "getPeerCount"}, function(peerCount) { + resolve(peerCount); + }); + }); + }, + }); + + var filters = []; + var Filter = function(options) { + filters.push(this); + + this.callbacks = []; + this.options = options; + + var call; + if(options === "chain") { + call = "newFilterString" + } else if(typeof options === "object") { + call = "newFilter" + } + + var self = this; // Cheaper than binding + this.promise = new Promise(function(resolve, reject) { + postData({call: call, args: [options]}, function(id) { + self.id = id; + + resolve(id); + }); + }); + }; + + Filter.prototype.changed = function(callback) { + var self = this; + this.promise.then(function(id) { + self.callbacks.push(callback); + }); + }; + + Filter.prototype.trigger = function(messages, id) { + if(id == this.id) { + for(var i = 0; i < this.callbacks.length; i++) { + this.callbacks[i].call(this, messages); + } + } + }; + + Filter.prototype.uninstall = function() { + this.promise.then(function(id) { + postData({call: "uninstallFilter", args:[id]}); + }); + }; + + Filter.prototype.messages = function() { + var self=this; + return Q.all([this.promise]).then(function() { + var id = self.id + return new Promise(function(resolve, reject) { + postData({call: "getMessages", args: [id]}, function(messages) { + resolve(messages); + }); + }); + }); + }; + + // Register to the messages callback. "messages" will be emitted when new messages + // from the client have been created. + eth.on("messages", function(messages, id) { + for(var i = 0; i < filters.length; i++) { + filters[i].trigger(messages, id); + } + }); + + var g_seed = 1; + function postData(data, cb) { + data._seed = g_seed; + if(cb) { + eth._callbacks[data._seed] = cb; + } + + if(data.args === undefined) { + data.args = []; + } + + g_seed++; + + navigator.qt.postMessage(JSON.stringify(data)); + } + + navigator.qt.onmessage = function(ev) { + var data = JSON.parse(ev.data) + + if(data._event !== undefined) { + eth.trigger(data._event, data.data); + } else { + if(data._seed) { + var cb = eth._callbacks[data._seed]; + if(cb) { + cb.call(this, data.data) + + // Remove the "trigger" callback + delete eth._callbacks[ev._seed]; + } + } + } + } +})(this); diff --git a/mist/assets/ext/http.js b/mist/assets/ext/http.js new file mode 100644 index 000000000..725ce8e6b --- /dev/null +++ b/mist/assets/ext/http.js @@ -0,0 +1,13 @@ +// this function is included locally, but you can also include separately via a header definition +function request(url, callback) { + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = (function(req) { + return function() { + if(req.readyState === 4) { + callback(req); + } + } + })(xhr); + xhr.open('GET', url, true); + xhr.send(''); +} diff --git a/mist/assets/ext/pre.js b/mist/assets/ext/pre.js new file mode 100644 index 000000000..f298fe9a1 --- /dev/null +++ b/mist/assets/ext/pre.js @@ -0,0 +1,3 @@ +if(typeof(Promise) === "undefined") { + window.Promise = Q.Promise; +} diff --git a/mist/assets/ext/q.js b/mist/assets/ext/q.js new file mode 100644 index 000000000..23c4245ee --- /dev/null +++ b/mist/assets/ext/q.js @@ -0,0 +1,1909 @@ +// vim:ts=4:sts=4:sw=4: +/*! + * + * Copyright 2009-2012 Kris Kowal under the terms of the MIT + * license found at http://github.com/kriskowal/q/raw/master/LICENSE + * + * With parts by Tyler Close + * Copyright 2007-2009 Tyler Close under the terms of the MIT X license found + * at http://www.opensource.org/licenses/mit-license.html + * Forked at ref_send.js version: 2009-05-11 + * + * With parts by Mark Miller + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +(function (definition) { + // Turn off strict mode for this function so we can assign to global.Q + /* jshint strict: false */ + + // This file will function properly as a