├── babel.config.js ├── .github ├── open.png ├── accounts.png ├── actions.png ├── bitcoin.png ├── messages.png ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── information.md │ ├── support.md │ ├── suggestion.md │ └── bug_report.md └── PULL_REQUEST_TEMPLATE.md ├── public ├── favicon.png └── index.html ├── solidity-ide.png ├── src ├── assets │ ├── icons │ │ ├── file.png │ │ ├── host.png │ │ ├── folder.png │ │ ├── refresh.png │ │ └── folder_open.png │ └── fonts │ │ ├── Monospace.woff │ │ ├── Monospace.woff2 │ │ ├── nunito-v9-latin-700.woff │ │ ├── nunito-v9-latin-700.woff2 │ │ ├── nunito-v9-latin-regular.woff │ │ └── nunito-v9-latin-regular.woff2 ├── sass │ ├── _variables.scss │ ├── _icons.scss │ ├── _fonts.scss │ └── app.scss ├── components │ ├── Sidebar.vue │ ├── ConstructorParameter.vue │ ├── ContractAction.vue │ ├── Contracts.vue │ ├── Messages.vue │ ├── Contract.vue │ ├── Accounts.vue │ ├── Directory.vue │ ├── Browser.vue │ └── Editor.vue ├── js │ ├── AccountManager.js │ └── keylistener.js ├── main.js └── App.vue ├── .npmignore ├── .gitignore ├── replace.js ├── LICENSE ├── package.json ├── README.md └── solc-server.js /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.github/open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/System-Glitch/Solidity-IDE/HEAD/.github/open.png -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/System-Glitch/Solidity-IDE/HEAD/public/favicon.png -------------------------------------------------------------------------------- /solidity-ide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/System-Glitch/Solidity-IDE/HEAD/solidity-ide.png -------------------------------------------------------------------------------- /.github/accounts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/System-Glitch/Solidity-IDE/HEAD/.github/accounts.png -------------------------------------------------------------------------------- /.github/actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/System-Glitch/Solidity-IDE/HEAD/.github/actions.png -------------------------------------------------------------------------------- /.github/bitcoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/System-Glitch/Solidity-IDE/HEAD/.github/bitcoin.png -------------------------------------------------------------------------------- /.github/messages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/System-Glitch/Solidity-IDE/HEAD/.github/messages.png -------------------------------------------------------------------------------- /src/assets/icons/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/System-Glitch/Solidity-IDE/HEAD/src/assets/icons/file.png -------------------------------------------------------------------------------- /src/assets/icons/host.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/System-Glitch/Solidity-IDE/HEAD/src/assets/icons/host.png -------------------------------------------------------------------------------- /src/assets/icons/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/System-Glitch/Solidity-IDE/HEAD/src/assets/icons/folder.png -------------------------------------------------------------------------------- /src/assets/icons/refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/System-Glitch/Solidity-IDE/HEAD/src/assets/icons/refresh.png -------------------------------------------------------------------------------- /src/assets/fonts/Monospace.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/System-Glitch/Solidity-IDE/HEAD/src/assets/fonts/Monospace.woff -------------------------------------------------------------------------------- /src/assets/fonts/Monospace.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/System-Glitch/Solidity-IDE/HEAD/src/assets/fonts/Monospace.woff2 -------------------------------------------------------------------------------- /src/assets/icons/folder_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/System-Glitch/Solidity-IDE/HEAD/src/assets/icons/folder_open.png -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | babel.config.js 2 | .gitignore 3 | src/* 4 | public/* 5 | .github/ISSUE_TEMPLATE/* 6 | .github/PULL_REQUEST_TEMPLATE.md 7 | replace.js -------------------------------------------------------------------------------- /src/assets/fonts/nunito-v9-latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/System-Glitch/Solidity-IDE/HEAD/src/assets/fonts/nunito-v9-latin-700.woff -------------------------------------------------------------------------------- /src/assets/fonts/nunito-v9-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/System-Glitch/Solidity-IDE/HEAD/src/assets/fonts/nunito-v9-latin-700.woff2 -------------------------------------------------------------------------------- /src/assets/fonts/nunito-v9-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/System-Glitch/Solidity-IDE/HEAD/src/assets/fonts/nunito-v9-latin-regular.woff -------------------------------------------------------------------------------- /src/assets/fonts/nunito-v9-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/System-Glitch/Solidity-IDE/HEAD/src/assets/fonts/nunito-v9-latin-regular.woff2 -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: System-Glitch 2 | patreon: system_glitch 3 | custom: https://blockstream.info/address/bc1qk2sut30zvry8glr6yca7tlgc39knepjp86ywpt 4 | -------------------------------------------------------------------------------- /src/sass/_variables.scss: -------------------------------------------------------------------------------- 1 | 2 | // Typography 3 | $font-family-sans-serif: "Nunito", sans-serif; 4 | $font-family-monospace: "Monospace", monospace; 5 | $font-size-base: 0.9rem; 6 | $line-height-base: 1.6; 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/information.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Information 3 | about: Report a vulnerability, a possible optimization, bad grammar/spelling, ... 4 | 5 | --- 6 | 7 | ## Description 8 | 9 | Describe precisely what you want to inform me about. Provide as much information as you can. 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /replace.js: -------------------------------------------------------------------------------- 1 | // Replace '/' paths in index.html to relative paths so ide can be run with file:// 2 | var fs = require('fs') 3 | fs.readFile('dist/index.html', 'utf8', function (err,data) { 4 | if (err) { 5 | return console.log(err); 6 | } 7 | var result = data.replace(/href=\//g, 'href='); 8 | result = result.replace(/src=\//g, 'src='); 9 | 10 | fs.writeFile('dist/index.html', result, 'utf8', function (err) { 11 | if (err) return console.log(err); 12 | }); 13 | }); -------------------------------------------------------------------------------- /src/components/Sidebar.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 20 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Solidity IDE 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/support.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Support 3 | about: Ask for help installing your environment or using a specific feature 4 | 5 | --- 6 | 7 | ## Description 8 | 9 | **Branch:** [current branch] 10 | **Commit:** [current commit hash] 11 | 12 | Describe what you are trying to achieve. 13 | 14 | **Goal:** Describe the goal of what you are trying to achieve, as well as the context. 15 | 16 | **Relevant code:** 17 | 18 | ``` 19 | Code 20 | ``` 21 | 22 | **Related features:** 23 | 24 | * [Feature 1] 25 | * [Feature 2] 26 | * [...] 27 | 28 | ## Additional information 29 | 30 | If anything you need to say doesn't fit in any of the above sections, write them here. 31 | -------------------------------------------------------------------------------- /src/js/AccountManager.js: -------------------------------------------------------------------------------- 1 | class AccountManager { 2 | 3 | constructor() { 4 | this.accounts = []; 5 | this.selectedAccount = -1; 6 | } 7 | 8 | accounts() { 9 | return this.accounts; 10 | } 11 | 12 | selectedAccount() { 13 | return this.selectedAccount; 14 | } 15 | 16 | getActiveAccount() { 17 | return this.accounts[this.selectedAccount]; 18 | } 19 | 20 | find(address) { 21 | for(let key in this.accounts) { 22 | if(this.accounts[key].address == address) 23 | return this.accounts[key]; 24 | } 25 | return null; 26 | } 27 | } 28 | 29 | export default AccountManager; -------------------------------------------------------------------------------- /src/sass/_icons.scss: -------------------------------------------------------------------------------- 1 | .icon { 2 | background-repeat: no-repeat; 3 | padding-left: 16px; 4 | padding-right: 0px; 5 | background-position: center; 6 | } 7 | 8 | .icon.icon-pad { 9 | background-position: 2px center; 10 | padding-left: 19px; 11 | padding-right: 6px; 12 | } 13 | 14 | .icon.directory { 15 | background-image: url('~@/assets/icons/folder.png'); 16 | } 17 | 18 | .icon.directory-open { 19 | background-image: url('~@/assets/icons/folder_open.png'); 20 | } 21 | 22 | .icon.file { 23 | background-image: url('~@/assets/icons/file.png'); 24 | } 25 | 26 | .icon.refresh { 27 | background-image: url('~@/assets/icons/refresh.png'); 28 | } 29 | 30 | .icon.host { 31 | background-image: url('~@/assets/icons/host.png'); 32 | } 33 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | process.versions = { 2 | node: '' // Patch for process.versions is undefined 3 | } 4 | 5 | import Vue from 'vue' 6 | import BootstrapVue from 'bootstrap-vue' 7 | import App from './App.vue' 8 | 9 | window.Vue = Vue; 10 | Vue.use(BootstrapVue) 11 | 12 | require('./sass/app.scss'); 13 | 14 | Vue.config.productionTip = false 15 | window.GlobalEvent = new Vue; 16 | 17 | const Web3 = require('web3'); 18 | window.Web3 = Web3; 19 | window.web3 = new Web3(new Web3.providers.HttpProvider(localStorage['ganache-host'] || 'http://localhost:8545')); 20 | 21 | window.axios = require('axios'); 22 | 23 | import AccountManager from './js/AccountManager.js'; 24 | window.accountManager = new AccountManager(); 25 | 26 | require('./js/keylistener'); 27 | 28 | new Vue({ 29 | render: h => h(App), 30 | }).$mount('#app') 31 | 32 | -------------------------------------------------------------------------------- /src/js/keylistener.js: -------------------------------------------------------------------------------- 1 | var isCtrl = false; 2 | var isF5 = false; 3 | var isR = false; 4 | 5 | document.onkeyup = function(e) { 6 | if(e.ctrlKey) isCtrl = false; 7 | else if(e.key == 'F5') isF5 = false; 8 | else if(e.key == 'r') isR = false; 9 | } 10 | 11 | document.onkeydown = function(e) { 12 | if(e.ctrlKey) isCtrl = true; 13 | else if(e.key == 'F5') isF5 = true; 14 | else if(e.key == 'r') isR = true; 15 | 16 | if(e.key == 's' && e.ctrlKey) { 17 | GlobalEvent.$emit('compile'); 18 | return false; 19 | } 20 | } 21 | 22 | document.onwheel = function(e) { 23 | if(e.ctrlKey) { 24 | e.preventDefault(); 25 | GlobalEvent.$emit('fontSize', e.deltaY < 0); 26 | } 27 | } 28 | 29 | window.onbeforeunload = function() { 30 | // Shutdown server if tab closed 31 | if(!isF5 && !isCtrl && !isR) { 32 | axios.get('http://localhost:8081/shutdown'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/suggestion.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Suggest a feature or enhancement 3 | about: Submit an idea for a feature or enhancement 4 | 5 | --- 6 | 7 | ## Description 8 | 9 | Describe your suggestion. What should it be used for? Is it a user-end feature or enhancement or a change aimed at developers? 10 | 11 | If relevant, provide detailed a step-by-step description of how your change would be used or how it would process. 12 | 13 | ### Comparison 14 | 15 | Describe the current behavior and compare it to the one you expect to see. 16 | 17 | ### Benefits 18 | 19 | Describe benefits of the change you're suggesting. What would it bring? Why would it be a good change? 20 | 21 | ### Possible drawbacks 22 | 23 | Describe the possible drawbacks of the change you're suggesting. If there are none, keep this section and write only `None`. 24 | 25 | ## Additional information 26 | 27 | If anything you need to say doesn't fit in any of the above sections, write them here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug, improperly working feature or code 4 | 5 | --- 6 | 7 | ## Description 8 | 9 | **Branch:** [current branch or tag] 10 | **Commit:** [current commit hash or tag] 11 | **Environment:** [Operating system, node version, browser] 12 | **Reproduces:** [always|sometimes|once|every second time|...] 13 | 14 | Describe your issue. 15 | 16 | **Relevant logs:** *(if needed)* 17 | ``` 18 | Insert logs here 19 | ``` 20 | 21 | ### How it occurred 22 | 23 | 1. [First step] 24 | 2. [Second step] 25 | 3. [...] 26 | 27 | **Expected result:** 28 | Describe the expected result and how it differs from the actual result. 29 | 30 | ### How to reproduce 31 | 32 | If you managed to reproduce the bug, fill this section, else keep this section and write only `Couldn't reproduce`. 33 | 34 | ## Additional information 35 | 36 | If anything you need to say doesn't fit in any of the above sections, write them here. 37 | -------------------------------------------------------------------------------- /src/sass/_fonts.scss: -------------------------------------------------------------------------------- 1 | /* nunito-regular - latin */ 2 | @font-face { 3 | font-family: 'Nunito'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: local('Nunito Regular'), local('Nunito-Regular'), 7 | url('~@/assets/fonts/nunito-v9-latin-regular.woff2') format('woff2'), 8 | url('~@/assets/fonts/nunito-v9-latin-regular.woff') format('woff'); 9 | } 10 | 11 | /* nunito-700 - latin */ 12 | @font-face { 13 | font-family: 'Nunito'; 14 | font-style: normal; 15 | font-weight: 700; 16 | src: local('Nunito Bold'), local('Nunito-Bold'), 17 | url('~@/assets/fonts/nunito-v9-latin-700.woff2') format('woff2'), 18 | url('~@/assets/fonts/nunito-v9-latin-700.woff') format('woff'); 19 | } 20 | 21 | @font-face { 22 | font-family: 'Monospace', monospace; 23 | src: local('Monospace') 24 | url('~@/assets/fonts/Monospace.woff2') format('woff2'), 25 | url('~@/assets/fonts/Monospace.woff') format('woff'); 26 | font-weight: 500; 27 | font-style: normal; 28 | } 29 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Merge (Feature name if relevant) 2 | 3 | ## Description 4 | 5 | Make a clear and detailed description of your changes. 6 | 7 | If your pull request contains visual changes, **include screenshots**. Ideally, include a "before-after" screenshot too. 8 | If your pull request is functionnal (new utilities for example), **include some usage examples**. 9 | 10 | ### Benefits 11 | 12 | Describe the benefits of your changes. 13 | 14 | ### Possible drawbacks 15 | 16 | Describe the possible drawbacks and caveats of your changes here. If there are none, keep this section and write only `None`. 17 | 18 | ## Related issue(s) 19 | 20 | List all related issues here: 21 | 22 | * #xxx (Issue title) 23 | * #yyy (Issue title) 24 | * #zzz (Issue title) 25 | 26 | If your pull request is not related to any issue, keep this section and write only `None`. 27 | 28 | ## Additional information 29 | 30 | If anything you need to say doesn't fit in any of the above sections, write it here. 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2019 Jérémy LAMBERT (System-Glitch) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/components/ConstructorParameter.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solidity-ide", 3 | "version": "2.3.3", 4 | "private": false, 5 | "description": "A light and easy IDE for Solidity smart contract development.", 6 | "author": "SystemGlitch", 7 | "license": "MIT", 8 | "main": "solc-server.js", 9 | "bin": { 10 | "solidity-ide": "./solc-server.js" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/System-Glitch/Solidity-IDE/issues" 14 | }, 15 | "homepage": "https://github.com/System-Glitch/Solidity-IDE", 16 | "keywords": [ 17 | "solidity", 18 | "ide", 19 | "ganache", 20 | "ethereum", 21 | "smart contract" 22 | ], 23 | "scripts": { 24 | "serve": "vue-cli-service serve", 25 | "build": "vue-cli-service build", 26 | "build-local": "vue-cli-service build && node replace.js", 27 | "lint": "vue-cli-service lint", 28 | "server": "node solc-server.js -d", 29 | "ide": "node solc-server.js" 30 | }, 31 | "dependencies": { 32 | "body-parser": "^1.18.3", 33 | "express": "^4.17.1", 34 | "ganache-cli": "^6.10.1", 35 | "minimist": "^1.2.5", 36 | "solc": "^0.7.1" 37 | }, 38 | "devDependencies": { 39 | "@vue/cli": "^3.12.1", 40 | "@vue/cli-plugin-babel": "^3.12.1", 41 | "@vue/cli-plugin-eslint": "^3.12.1", 42 | "@vue/cli-service": "^4.5.4", 43 | "@vue/cli-service-global": "^4.5.4", 44 | "ace-mode-solidity": "^0.1.1", 45 | "axios": "^0.18.1", 46 | "babel-eslint": "^10.1.0", 47 | "bootstrap": "^4.5.2", 48 | "bootstrap-vue": "^2.16.0", 49 | "brace": "^0.11.1", 50 | "core-js": "^2.6.11", 51 | "es6-promise": "^4.2.8", 52 | "eslint": "^5.16.0", 53 | "eslint-plugin-vue": "^5.2.3", 54 | "node-sass": "^4.14.1", 55 | "sass-loader": "^7.3.1", 56 | "split.js": "^1.6.2", 57 | "vue": "^2.6.12", 58 | "vue-template-compiler": "^2.6.12", 59 | "web3": "^1.2.11", 60 | "webpack": "^4.44.1" 61 | }, 62 | "eslintConfig": { 63 | "root": true, 64 | "env": { 65 | "browser": true, 66 | "node": true 67 | }, 68 | "globals": { 69 | "GlobalEvent": true, 70 | "axios": true 71 | }, 72 | "extends": [ 73 | "plugin:vue/essential", 74 | "eslint:recommended" 75 | ], 76 | "rules": {}, 77 | "parserOptions": { 78 | "parser": "babel-eslint" 79 | } 80 | }, 81 | "postcss": { 82 | "plugins": { 83 | "autoprefixer": {} 84 | } 85 | }, 86 | "browserslist": [ 87 | "> 1%", 88 | "last 2 versions", 89 | "not ie <= 8" 90 | ] 91 | } 92 | -------------------------------------------------------------------------------- /src/components/ContractAction.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 66 | 71 | -------------------------------------------------------------------------------- /src/components/Contracts.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 87 | -------------------------------------------------------------------------------- /src/components/Messages.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 83 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 91 | 106 | -------------------------------------------------------------------------------- /src/sass/app.scss: -------------------------------------------------------------------------------- 1 | 2 | @import 'fonts'; 3 | @import 'variables'; 4 | @import 'icons'; 5 | 6 | @import '~bootstrap/scss/bootstrap'; 7 | 8 | body, html { 9 | height: 100%; 10 | min-width: 800px; 11 | min-height: 500px; 12 | } 13 | 14 | .ace_autocomplete, .ace_autocomplete .ace_line, .ace_autocomplete .ace_content { 15 | background-color: #212529; 16 | color: var(--light); 17 | } 18 | 19 | .ace_autocomplete .ace_rightAlignedText { 20 | color: #b4bfca; 21 | font-style: italic; 22 | z-index: 0; 23 | } 24 | 25 | .ace_editor.ace_autocomplete .ace_marker-layer .ace_active-line { 26 | background-color: var(--info); 27 | } 28 | 29 | .ace_completion-highlight { 30 | color: var(--light) !important; 31 | font-weight: bold; 32 | } 33 | 34 | .ace_autocomplete .ace_line-hover { 35 | border-color: var(--info) !important; 36 | background-color: #007bff88 !important; 37 | } 38 | 39 | .ace_autocomplete .ace_selected { 40 | background-color: #2c3034; 41 | } 42 | 43 | .btn { 44 | text-overflow: ellipsis; 45 | overflow: hidden; 46 | } 47 | 48 | .monospace { 49 | font-family: 'Monospace', monospace !important; 50 | } 51 | 52 | .nav-link { 53 | padding-top: 0.25rem !important; 54 | padding-bottom: 0.25rem !important; 55 | } 56 | 57 | .flex-fit { 58 | height: 1px; 59 | } 60 | 61 | .form-control-sm { 62 | height: calc(1.68125rem + 2px) !important; 63 | padding: 0.25rem 0.5rem !important; 64 | font-size: 0.7875rem; 65 | line-height: 1.5 !important; 66 | border-radius: 0.2rem; 67 | } 68 | 69 | .scrollable { 70 | overflow-y: auto; 71 | } 72 | 73 | .fit-parent, .fit-parent .tab-content { 74 | height: 100%; 75 | overflow: hidden; 76 | } 77 | 78 | .table-fixed { 79 | table-layout: fixed; 80 | } 81 | 82 | .table-fit { 83 | width: 40px; 84 | } 85 | 86 | .table-container td { 87 | font-size: 12px; 88 | } 89 | 90 | .table-container table { 91 | margin: 0; 92 | } 93 | 94 | .table-container .table th { 95 | position: sticky; 96 | top: 0; 97 | border: 0; 98 | z-index: 2; 99 | } 100 | 101 | .table-container .table-header th:after, .table-container .table-header th:before { 102 | content: ''; 103 | position: absolute; 104 | left: 0; 105 | width: 100%; 106 | } 107 | 108 | .table-container .table-header th:before { 109 | top: 0; 110 | border-top: 1px solid #32383e; 111 | } 112 | 113 | .table-container .table-header th:after { 114 | top: 100%; 115 | border-bottom: 1px solid #32383e; 116 | } 117 | 118 | .bottom-separator { 119 | border-bottom: 1px solid #444; 120 | } 121 | 122 | .bg-dark { 123 | color: #fff; 124 | } 125 | 126 | .bg-dark textarea:focus { 127 | color: #fff; 128 | } 129 | 130 | .bg-dark .nav-tabs .nav-link, .bg-dark .nav-tabs .nav-link { 131 | color: #ddd; 132 | } 133 | 134 | .bg-dark .nav-tabs .nav-link:hover, .bg-dark .nav-tabs .nav-link:focus { 135 | border-color: #878787; 136 | color: #fff; 137 | } 138 | 139 | .bg-dark .nav-tabs .nav-link.active, .bg-dark .nav-tabs .nav-item.show .nav-link { 140 | color: #fff; 141 | background-color: var(--dark); 142 | border-color: #878787 #878787 var(--dark); 143 | } 144 | 145 | .bg-dark .nav-tabs .nav-link.disabled { 146 | color: #7d7d7d; 147 | background-color: #0000004d; 148 | border-bottom-color: #878787; 149 | } 150 | 151 | .bg-dark .nav-tabs { 152 | border-bottom: 1px solid #878787; 153 | } 154 | 155 | .bg-dark .list-group-item { 156 | color: #9f9f9f; 157 | background-color: var(--dark); 158 | } 159 | 160 | .list-group-item .close { 161 | color: #fff; 162 | z-index: 1; 163 | } 164 | 165 | .list-group-item.active > span { 166 | color: #fff; 167 | z-index: 1; 168 | } 169 | 170 | .list-group-item:hover > * { 171 | cursor: pointer; 172 | color: #fff; 173 | } 174 | 175 | .list-group-item .icon.directory, .list-group-item .icon.file { 176 | opacity: 0.7; 177 | } 178 | 179 | .list-group-item:hover > span > .icon.directory, .list-group-item:hover > span > .icon.file, .list-group-item.active > span > .icon.file { 180 | opacity: 1; 181 | } 182 | 183 | .selected-indicator { 184 | display: block; 185 | position: absolute; 186 | opacity: 0; 187 | left: 0; 188 | top: 0; 189 | right: 0; 190 | height: 100%; 191 | background-color: #3e444a; 192 | border-left: 3px solid var(--info); 193 | } 194 | 195 | .list-group-item.active .selected-indicator { 196 | opacity: 1; 197 | } 198 | 199 | .list-group-item { 200 | border-radius: 0 !important; 201 | border: none; 202 | padding: 0; 203 | user-select: none; 204 | } 205 | 206 | .list-group-item > .list-group { 207 | padding-left: 0.5rem; 208 | } 209 | 210 | .list-group-item > .close { 211 | display: none; 212 | } 213 | 214 | .list-group-item:hover > .close { 215 | display: inline-block; 216 | } 217 | 218 | .alert-dismissible .close { 219 | padding: 0 0.25rem !important; 220 | } 221 | 222 | .marker-error { 223 | position: absolute; 224 | border-bottom: 2px solid var(--danger); 225 | z-index: 2; 226 | opacity: 0.85; 227 | border-radius: 0px; 228 | } 229 | 230 | .marker-warning { 231 | position: absolute; 232 | border-bottom: 2px solid var(--warning); 233 | z-index: 2; 234 | opacity: 0.85; 235 | border-radius: 0px; 236 | } 237 | 238 | .gutter { 239 | background-color: #444; 240 | } 241 | 242 | .gutter-horizontal:hover { 243 | cursor: col-resize; 244 | } 245 | 246 | .gutter-vertical:hover { 247 | cursor: row-resize; 248 | } 249 | 250 | .modal-body { 251 | word-wrap: break-word; 252 | } 253 | 254 | .file { 255 | min-width: 100px; 256 | } 257 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Solidity IDE

2 | 3 | > A simple alternative to Remix IDE, working with Vue.js and Ganache 4 | 5 | ![IDE screenshot](solidity-ide.png?raw=true "Soldity IDE") 6 | 7 | Solidity IDE is a light solution aimed at making Solidity development easier and more accessible, allowing you to concentrate only on your code by doing the Web3 part for you so you don't have to write anything else than Solidity. 8 | 9 | This IDE is web-based but works with the file system. You can open and use any directory on your computer as your project's folder. Files will be saved and created on your disk, not in the browser's local storage. 10 | 11 | ## Install 12 | 13 | ### From NPM 14 | 15 | ``` 16 | npm install -g solidity-ide 17 | ``` 18 | On windows, you may need to install the [build tools](https://github.com/felixrieseberg/windows-build-tools) using: `npm install --global windows-build-tools`. 19 | 20 | Then, run the IDE using: 21 | ``` 22 | solidity-ide 23 | ``` 24 | 25 | ### Using a release 26 | 27 | Download the latest [release](https://github.com/System-Glitch/Solidity-IDE/releases) and run the IDE using: 28 | ``` 29 | npm run ide 30 | ``` 31 | 32 | ### Build from source 33 | 34 | Clone the repository and run `npm install`. On windows, you may need to install the [build tools](https://github.com/felixrieseberg/windows-build-tools) using: `npm install --global windows-build-tools`. Then build for a local usage using: 35 | 36 | ``` 37 | npm install 38 | npm run build-local 39 | npm run ide 40 | ``` 41 | 42 | The third command will run ganache and the solc server in the background, don't kill this process when using the IDE. 43 | 44 | --- 45 | 46 | `npm run ide` accepts a path as parameter for the default directory: 47 | ``` 48 | npm run ide -- --path=path/to/project 49 | ``` 50 | 51 | You can pass ganache-cli parameters as well: 52 | ``` 53 | npm run ide -- --path=path/to/project -a 20 # Generate 20 accounts on startup 54 | ``` 55 | 56 | If you don't want to run a new ganache in the background (for example to use your own), use: 57 | ``` 58 | npm run ide -- --noganache 59 | ``` 60 | 61 | If you get an error like "_Couldn't fetch..._" on startup, this is probably because the IDE opened before the server finished loading. Just click "_refresh_" and you should be good to go. 62 | 63 | ### Getting started 64 | 65 | #### File management 66 | 67 | To get started, I recommend creating a new folder for your project. Once done, run the ide by specifying the path of the folder you just created. You can also choose to open it through the GUI by clicking the "_Open..._" button at the top of the file browser. The IDE will remember the last opened directory if you proceed that way. 68 | 69 |

File browser

70 | 71 | Only `.sol` files and directories are shown in the browser. 72 | The next step is to create a file using the file browser. You can create files inside subdirectories (missing directories will be created) by writing the path with `/` separators. If you omit the `.sol` at the end of the filename, the IDE will add it automatically. You can start coding! 73 | 74 | #### Account management 75 | 76 | At the top right of the screen is the accounts panel. This panel allows you to manage ganache account. You can select which account you're currently using. When sending a transaction or depolying a contract, the fees will be withdrawn from the selected account. 77 | 78 |

Accounts panel

79 | 80 | If the list is empty, this is probably because the IDE opened before the server finished loading. Just click "_refresh_". 81 | 82 | You can choose another Ganache host if you want, by clicking the "_Change Ganache host_" button. 83 | 84 | #### Compile, deploy and test 85 | 86 | At the bottom right of the screen is the messages panel. This panel displays errors and warnings. 87 | 88 | Clicking "_compile_" will save all changes. Pressing _CTRL+S_ has the same behavior. 89 | Clicking "_deploy_" will recompile everything and deploy all contracts in the currently open `.sol` file on the local ganache host. 90 | 91 |

Messages panel

92 | 93 | A new panel will appear at the bottom of the screen. Each tab represents an instance of your contracts. You can test the contracts methods by clicking the action buttons. 94 | 95 | Grey buttons are `pure` or `view` functions. 96 | Cyan buttons are functions requiring a transaction. You can specifiy an amount to send to the contract using the inputs next to the button. The given amount will be **withdrawn from the selected account** in the accounts panel. 97 | When a functions requires parameters, an aditionnal input is available. Data is written using the **JSON format** and parameters are **comma-separated**. (Strings must be surrounded by quotes for example) 98 | 99 | The result of the call will be displayed in the text area next to the action buttons. 100 | 101 |

Contracts panel

102 | 103 | **You can find the ABI and bytecode (JSON format) in the `build` folder**, which is generated when you compile. 104 | 105 | ## For contributors 106 | 107 | If you want to contribute to this project, fork, install the dependencies and run the development tools. On windows, you may need to install the [build tools](https://github.com/felixrieseberg/windows-build-tools) using: `npm install --global windows-build-tools` 108 | 109 | ``` 110 | npm install 111 | 112 | # Run solc server and ganache 113 | npm run server 114 | # Or npm run server -- --path=path/to/project 115 | # Also accepts ganache-cli parameters 116 | 117 | # Run vue.js server 118 | npm run serve 119 | ``` 120 | -------------------------------------------------------------------------------- /src/components/Contract.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 133 | -------------------------------------------------------------------------------- /src/components/Accounts.vue: -------------------------------------------------------------------------------- 1 | 62 | 63 | 141 | -------------------------------------------------------------------------------- /src/components/Directory.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | -------------------------------------------------------------------------------- /solc-server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // OPTIONS: 3 | // -d: development mode. If set, open 'localhost:8080' in the browser, use the local build instead 4 | // --path=: path to the default directory (optional, use working directory if missing) 5 | 6 | const argv = require('minimist')(process.argv.slice(2)) 7 | for(let key in process.argv) { 8 | if(process.argv[key].startsWith('--path=')) { 9 | process.argv.splice(key, 1) 10 | break 11 | } 12 | } 13 | 14 | const FILE_SEPARATOR = process.platform == 'win32' ? '\\': '/' 15 | var directory = argv.path ? argv.path : process.cwd() 16 | const PORT = 8081 17 | const FORBIDDEN_CHARACTERS = "\\\\|<|>|:|\\\"|\\'|\\||\\?|\\*|~|#|\\n|\\t|\\v|\\f|\\r" 18 | const fs = require('fs') 19 | const solc = require('solc') 20 | const express = require('express') 21 | const bodyParser = require('body-parser') 22 | const app = express() 23 | 24 | const ignored = ['build', 'node_modules'] 25 | 26 | console.log("Starting solc server...") 27 | 28 | if(!directory.endsWith(FILE_SEPARATOR)) 29 | directory += FILE_SEPARATOR 30 | 31 | if (!fs.existsSync(directory) || !fs.lstatSync(directory).isDirectory()) { 32 | console.error('"' + directory + '" doesn\'t exist or is not a directory.') 33 | process.exit(1) 34 | } 35 | 36 | app.use(bodyParser.urlencoded({ extended: false })) 37 | app.use(bodyParser.json()) 38 | app.use(function(req, res, next) { 39 | res.header("Access-Control-Allow-Origin", "*") 40 | res.header("Access-Control-Allow-Methods", "OPTIONS, HEAD, GET, POST, PUT, DELETE") 41 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") 42 | res.header("Content-Type", "application/json") 43 | next() 44 | }) 45 | 46 | app.get('/compile', function (req, res) { 47 | 48 | const sources = listDirForCompile('', {}) 49 | const input = { 50 | language: 'Solidity', 51 | sources: sources, 52 | settings: { 53 | outputSelection: { 54 | '*': { 55 | '*': [ '*' ] 56 | } 57 | } 58 | } 59 | } 60 | 61 | let output = solc.compile(JSON.stringify(input)) 62 | 63 | output = output.replace(new RegExp(directory.replace(/\\/g, '\\\\\\\\').replace(/\./g, '\\.'), 'g'), '') 64 | 65 | if(process.platform == 'win32') { 66 | output = output.replace(/\//g, FILE_SEPARATOR.repeat(2)) 67 | } 68 | 69 | // Save built files 70 | const jsonOutput = JSON.parse(output) 71 | rmdir(directory + 'build') 72 | 73 | for(let file in jsonOutput.contracts) { 74 | const contract = jsonOutput.contracts[file] 75 | 76 | for(let contractName in contract) { 77 | const dir = directory + 'build' + FILE_SEPARATOR + file + FILE_SEPARATOR + contractName 78 | try { 79 | fs.mkdirSync(dir, { recursive: true }) 80 | } catch(err) { 81 | fs.mkdirSync(dir, { recursive: true }) 82 | } 83 | fs.writeFileSync(dir + FILE_SEPARATOR + contractName + '_bytecode.json', JSON.stringify(contract[contractName].evm.bytecode)) 84 | fs.writeFileSync(dir + FILE_SEPARATOR + contractName + '_abi.json', JSON.stringify(contract[contractName].abi)) 85 | } 86 | } 87 | 88 | res.end(output) 89 | console.log('Compile') 90 | }) 91 | 92 | //------------------------------------ 93 | // FILESYSTEM 94 | 95 | app.get('/directory', function(req, res) { 96 | try { 97 | if(req.query.root) { 98 | if (!fs.existsSync(req.query.root) || !fs.lstatSync(req.query.root).isDirectory()) { 99 | res.status(400) 100 | res.end('Given root directory doesn\'t exist or is not a directory.') 101 | return 102 | } 103 | directory = req.query.root 104 | 105 | if(!directory.endsWith('/')) 106 | directory += '/' 107 | 108 | if(process.platform == 'win32') { 109 | directory = directory.replace(/\//g, FILE_SEPARATOR) 110 | } 111 | 112 | console.log('Change root directory: ' + directory) 113 | } 114 | 115 | if(req.query.dir) { 116 | if (!fs.existsSync(directory + req.query.dir) || !fs.lstatSync(directory + req.query.dir).isDirectory()) { 117 | res.status(400) 118 | res.end('Given directory doesn\'t exist or is not a directory.') 119 | return 120 | } 121 | 122 | if(!req.query.dir.endsWith('/')) { 123 | req.query.dir += '/' 124 | } 125 | 126 | if(process.platform == 'win32') { 127 | req.query.dir = req.query.dir.replace(/\//g, FILE_SEPARATOR) 128 | } 129 | } 130 | 131 | const path = req.query.dir ? req.query.dir : '' 132 | const result = listDir(path) 133 | res.end(JSON.stringify(result)) 134 | console.log('List directory content: ' + (path.length ? path : './')) 135 | } catch(error) { 136 | res.status(403) 137 | res.end(error.message) 138 | console.log('Couldn\'t list directory content: ' + file) 139 | } 140 | }) 141 | 142 | // Fetch file 143 | app.get('/file', function(req, res) { 144 | if(req.query.file) { 145 | let file = directory + req.query.file 146 | if(process.platform == 'win32') { 147 | file = file.replace(/\//g, FILE_SEPARATOR) 148 | } 149 | if (validateFile(file, res)) { 150 | try { 151 | res.end(fs.readFileSync(file).toString()) 152 | console.log('Fetch file: ' + file) 153 | } catch(error) { 154 | res.status(403) 155 | res.end(error.message) 156 | console.log('Couldn\'t fetch file: ' + file) 157 | } 158 | } 159 | } else { 160 | res.status(400) 161 | res.end('File is required.') 162 | } 163 | }) 164 | 165 | // Create file 166 | app.post('/create', function(req, res) { 167 | if(req.body.file) { 168 | let path = directory + req.body.file 169 | if(process.platform == 'win32') { 170 | path = path.replace(/\//g, FILE_SEPARATOR) 171 | } 172 | 173 | if (new RegExp(FORBIDDEN_CHARACTERS).test(req.body.file)) { 174 | res.status(400) 175 | res.end('Given file name contains forbidden characters.') 176 | return 177 | } else if(!validateSolidityFile(path)) { 178 | res.status(400) 179 | res.end('Given file is not a solidity file.') 180 | return 181 | } else if (fs.existsSync(path)) { 182 | res.status(400) 183 | res.end('File already exists.') 184 | return 185 | } 186 | 187 | try { 188 | if(path.indexOf(FILE_SEPARATOR) != -1) { 189 | fs.mkdirSync(path.substring(0, path.lastIndexOf(FILE_SEPARATOR)), { recursive: true }) 190 | } 191 | fs.writeFileSync(path, '') 192 | res.status(201) 193 | res.end() 194 | console.log('Create file: ' + path) 195 | } catch(error) { 196 | res.status(403) 197 | res.end(error.message) 198 | console.log('Couldn\'t create file: ' + file) 199 | } 200 | } else { 201 | res.status(400) 202 | res.end('File is required.') 203 | } 204 | }) 205 | 206 | // Save file 207 | app.put('/save', function(req, res) { 208 | let file = '' 209 | if(req.body.file) { 210 | file = directory + req.body.file 211 | if(process.platform == 'win32') { 212 | file = file.replace(/\//g, FILE_SEPARATOR) 213 | } 214 | 215 | if (!validateFile(file, res)) { 216 | return 217 | } 218 | } else { 219 | res.status(400) 220 | res.end('File is required.') 221 | return 222 | } 223 | 224 | if(req.body.content != undefined) { 225 | try { 226 | fs.writeFileSync(file, req.body.content) 227 | res.status(204) 228 | res.end() 229 | console.log('Save file: ' + file) 230 | } catch(error) { 231 | res.status(403) 232 | res.end(error.message) 233 | console.log('Couldn\'t save file: ' + error.message) 234 | } 235 | 236 | } else { 237 | res.status(400) 238 | res.end('Content is required.') 239 | } 240 | }) 241 | 242 | // Delete file 243 | app.delete('/delete', function(req, res) { 244 | if(req.body.file) { 245 | let file = directory + req.body.file 246 | if(process.platform == 'win32') { 247 | file = file.replace(/\//g, FILE_SEPARATOR) 248 | } 249 | 250 | if (validateFile(file, res)) { 251 | try { 252 | fs.unlinkSync(file) 253 | res.status(204) 254 | res.end() 255 | console.log('Delete file: ' + file) 256 | } catch(error) { 257 | res.status(403) 258 | res.end(error.message) 259 | console.log('Couldn\'t delete file: ' + error.message) 260 | } 261 | } 262 | 263 | } else { 264 | res.status(400) 265 | res.end('File is required.') 266 | return 267 | } 268 | }) 269 | 270 | function listDir(dir) { 271 | const result = [] 272 | const items = fs.readdirSync(directory + dir) 273 | 274 | for(let key in items) { 275 | const item = items[key] 276 | 277 | try { 278 | const stats = fs.lstatSync(directory + dir + item) 279 | if((stats.isFile() && !item.endsWith('.sol')) || (!stats.isFile() && !stats.isDirectory())) { 280 | continue // Skip non-sol files and non-directories 281 | } 282 | const file = {name: item, path: (dir + item).replace(/\\/g, '/'), directory: stats.isDirectory(), state: 0, saved: true} 283 | result.push(file) 284 | if(stats.isDirectory()) { 285 | file.childs = [] 286 | } 287 | } catch(err) { 288 | // Ignore files without permission 289 | } 290 | } 291 | 292 | return result 293 | } 294 | 295 | function listDirForCompile(dir, result) { 296 | const items = fs.readdirSync(directory + dir) 297 | 298 | for(let key in items) { 299 | const item = items[key] 300 | const path = directory + dir + item 301 | const stats = fs.lstatSync(path) 302 | if(stats.isFile() && item.endsWith('.sol')) { // Skip non-sol files and non-directories 303 | try { 304 | fs.accessSync(path, fs.constants.R_OK) 305 | result[path] = { content: fs.readFileSync(path).toString() } 306 | } catch(err) { 307 | // Skip if no read permission 308 | } 309 | } 310 | 311 | if(stats.isDirectory() && checkNotIgnored(item)) { 312 | listDirForCompile(dir + item + '/', result) 313 | } 314 | } 315 | 316 | return result 317 | } 318 | 319 | function checkNotIgnored(dir) { 320 | for(let key in ignored) { 321 | if(dir.endsWith(ignored[key])) { 322 | return false 323 | } 324 | } 325 | return true 326 | } 327 | 328 | function validateFile(path, res) { 329 | try { 330 | fs.accessSync(path, fs.constants.S_IFREG & fs.constants.R_OK) 331 | if (!path.endsWith('.sol')) { 332 | res.status(400) 333 | res.end('Given file doesn\'t exist or is not a solidity file.') 334 | return false 335 | } 336 | } catch(error) { 337 | res.status(403) 338 | res.end(error.message) 339 | return false 340 | } 341 | return true 342 | } 343 | 344 | function validateSolidityFile(path) { 345 | return path.endsWith('.sol') && path.substring(path.lastIndexOf('/') + 1) != '.sol' 346 | } 347 | 348 | function rmdir(path) { 349 | let files = []; 350 | while(fs.existsSync(path)) { 351 | files = fs.readdirSync(path) 352 | for(let key in files) { 353 | const curPath = path + FILE_SEPARATOR + files[key] 354 | if(fs.lstatSync(curPath).isDirectory()) { 355 | rmdir(curPath) 356 | } else { 357 | fs.unlinkSync(curPath) 358 | } 359 | } 360 | try { 361 | fs.rmdirSync(path) 362 | } catch(err) { /* try again if failed (windows patch) */ } 363 | } 364 | } 365 | 366 | // TODO Create directory without creating file 367 | 368 | //------------------------------------ 369 | 370 | app.get('/shutdown', function() { 371 | console.log('IDE closed, exiting.') 372 | process.exit(0) 373 | }) 374 | 375 | app.listen(PORT, 'localhost', function () { 376 | console.log('Started solc server. Listening on localhost:' + PORT + ', directory "' + directory + '"') 377 | }) 378 | 379 | if(argv.noganache !== true) { 380 | setTimeout(function() { 381 | console.log("Starting ganache...") 382 | require('ganache-cli/cli') 383 | }, 1) 384 | } 385 | 386 | if(argv.d !== true) { // Not in dev mode 387 | let url = '"file://' + __dirname + '/dist/index.html"' 388 | 389 | if(process.platform == 'win32') url = '"" ' + url 390 | 391 | const start = (process.platform == 'darwin'? 'open': process.platform == 'win32' ? 'start': 'xdg-open') 392 | require('child_process').exec(start + ' ' + url) 393 | } 394 | -------------------------------------------------------------------------------- /src/components/Browser.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 365 | -------------------------------------------------------------------------------- /src/components/Editor.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 471 | --------------------------------------------------------------------------------