├── .gitignore
├── .prettierrc
├── README.md
├── babel.config.js
├── ide.png
├── jsconfig.json
├── package.json
├── public
├── favicon.ico
└── index.html
├── server.ts
├── src
├── App.vue
├── assets
│ ├── global.styl
│ └── tailwind.css
├── components
│ ├── Compiler.vue
│ ├── Deployer.vue
│ ├── Editor.vue
│ ├── FileManager.vue
│ ├── Menubar.vue
│ ├── Statusbar.vue
│ ├── Terminal.vue
│ ├── Toolbar.vue
│ ├── index.js
│ └── template.vue
├── main.ts
├── plugins
│ ├── element-ui.ts
│ └── resolve-github.ts
├── router
│ └── index.ts
├── shims-tsx.d.ts
├── shims-vue.d.ts
├── store
│ ├── editor.ts
│ ├── index.ts
│ ├── storage.ts
│ └── type.ts
├── utils
│ ├── antenna.ts
│ ├── constant.ts
│ ├── directives.ts
│ ├── eventBus.ts
│ ├── filter.ts
│ ├── fs.ts
│ ├── helper.ts
│ ├── index.ts
│ ├── lodash.ts
│ ├── sharefolder.ts
│ ├── solc.ts
│ ├── vm.ts
│ └── ws-plugin.ts
└── views
│ └── Home.vue
├── tsconfig.json
├── tslint.json
└── vue.config.js
/.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 |
23 | *lock.json
24 | local
25 | package-lock.json
26 | yarn.lock
27 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 200,
3 | "bracketSpacing": true
4 | }
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ## Project setup
6 | ```
7 | yarn install
8 | ```
9 |
10 | ### Compiles and hot-reloads for development
11 | ```
12 | yarn run dev
13 | ```
14 |
15 | ### Compiles and minifies for production
16 | ```
17 | yarn run build
18 | ```
19 |
20 | ### Run your tests
21 | ```
22 | yarn run test
23 | ```
24 |
25 | ### Lints and fixes files
26 | ```
27 | yarn run lint
28 | ```
29 |
30 | ### Customize configuration
31 | See [Configuration Reference](https://cli.vuejs.org/config/).
32 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ["@vue/cli-plugin-babel/preset"],
3 | plugins: [
4 | [
5 | "component",
6 | {
7 | libraryName: "element-ui",
8 | styleLibraryName: "theme-chalk"
9 | }
10 | ]
11 | ]
12 | };
13 |
--------------------------------------------------------------------------------
/ide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iotexproject/iotex-studio/4d6c350499c305394781ce04b1fdcb5833e27037/ide.png
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./",
4 | "paths": {
5 | "@/*": ["src/*"]
6 | }
7 | },
8 | "exclude": ["node_modules", "dist"]
9 | }
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "iotex-editor",
3 | "version": "0.0.2",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint",
9 | "pretty": "pretty-quick",
10 | "start": "ts-node -T ./server.ts"
11 | },
12 | "husky": {
13 | "hooks": {
14 | "pre-commit": "pretty-quick --staged"
15 | }
16 | },
17 | "dependencies": {
18 | "@types/axios": "^0.14.0",
19 | "@types/cors": "^2.8.7",
20 | "@types/express-http-proxy": "^1.6.1",
21 | "@types/parse-github-url": "^1.0.0",
22 | "@types/prettier": "^2.0.1",
23 | "ace-mode-solidity": "^0.1.1",
24 | "axios": "^0.19.2",
25 | "brace": "^0.11.1",
26 | "browserfs": "^1.4.3",
27 | "comlink": "^4.3.0",
28 | "comlink-fetch": "^0.1.2",
29 | "core-js": "^3.3.2",
30 | "cors": "^2.8.5",
31 | "element-ui": "^2.12.0",
32 | "ethereumjs-abi": "^0.6.8",
33 | "ethereumjs-account": "^3.0.0",
34 | "ethereumjs-tx": "^2.1.1",
35 | "ethereumjs-util": "^7.0.2",
36 | "ethereumjs-vm": "^4.1.1",
37 | "ethers": "^5.0.2",
38 | "express": "^4.17.1",
39 | "express-http-proxy": "^1.6.2",
40 | "express-http-to-https": "^1.1.4",
41 | "fs.promises": "^0.1.2",
42 | "helpful-decorators": "^2.0.5",
43 | "http-proxy-middleware": "^1.0.5",
44 | "iotex-antenna": "^0.30.0",
45 | "lodash": "^4.17.15",
46 | "module": "^1.2.5",
47 | "parse-github-url": "^1.0.2",
48 | "prettier": "^2.0.5",
49 | "prettier-plugin-solidity": "^1.0.0-alpha.36",
50 | "prettier-standalone": "^1.3.1-0",
51 | "promise-retry": "^2.0.1",
52 | "remix-solidity": "^0.3.31",
53 | "resolve-github": "^0.2.0",
54 | "resolve-http": "^0.2.0",
55 | "resolve-ipfs": "^0.1.0",
56 | "semver": "^7.3.2",
57 | "shrink-ray-current": "^4.1.2",
58 | "solc": "^0.6.3",
59 | "solc-js": "^1.0.1",
60 | "solc-resolver": "^0.2.3",
61 | "solc-version": "^0.3.1",
62 | "solcjs-core-fix": "^0.7.0",
63 | "ts-node": "^8.10.2",
64 | "tslib": "^2.0.0",
65 | "typescript": "^3.9.5",
66 | "v-contextmenu": "^2.8.1",
67 | "vue": "^2.6.10",
68 | "vue-clipboard2": "^0.3.1",
69 | "vue-router": "^3.1.3",
70 | "vue-split-panel": "^1.0.4",
71 | "vuex": "^3.0.1",
72 | "vuex-class-modules": "^1.1.2",
73 | "vuex-pathify": "^1.4.0",
74 | "vuex-persist": "^2.2.0",
75 | "websocket-as-promised": "^1.0.1"
76 | },
77 | "devDependencies": {
78 | "@types/ace": "0.0.43",
79 | "@types/ethereumjs-abi": "^0.6.3",
80 | "@types/ethereumjs-tx": "^2.0.0",
81 | "@types/ethereumjs-util": "^6.1.0",
82 | "@types/express": "^4.17.2",
83 | "@types/fs-promise": "^2.0.0",
84 | "@types/lodash": "^4.14.149",
85 | "@types/lodash-webpack-plugin": "^0.11.3",
86 | "@types/promise-retry": "^1.1.3",
87 | "@types/pug": "^2.0.4",
88 | "@types/vue-splitpane": "^1.0.0",
89 | "@vue/cli-plugin-babel": "^4.0.0",
90 | "@vue/cli-plugin-typescript": "^4.0.0",
91 | "@vue/cli-service": "^4.0.0",
92 | "babel-plugin-component": "^1.1.1",
93 | "comlink-loader": "^2.0.0",
94 | "husky": "^4.0.10",
95 | "pretty-quick": "^2.0.1",
96 | "pug": "^3.0.0",
97 | "pug-loader": "^2.4.0",
98 | "pug-plain-loader": "^1.0.0",
99 | "stylus": "^0.54.7",
100 | "stylus-loader": "^3.0.2",
101 | "tailwindcss": "^1.1.3",
102 | "terser-webpack-plugin": "^3.0.5",
103 | "tslint-config-prettier": "^1.18.0",
104 | "tslint-plugin-prettier": "^2.0.1",
105 | "typed-emitter": "^1.2.0",
106 | "vue-cli-plugin-element-ui": "^1.1.4",
107 | "vue-property-decorator": "^8.3.0",
108 | "vue-runtime-helpers": "^1.1.2",
109 | "vue-template-compiler": "^2.6.10"
110 | },
111 | "postcss": {
112 | "plugins": {
113 | "autoprefixer": {},
114 | "tailwindcss": {}
115 | }
116 | },
117 | "browserslist": [
118 | "> 1%",
119 | "last 2 versions"
120 | ]
121 | }
122 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iotexproject/iotex-studio/4d6c350499c305394781ce04b1fdcb5833e27037/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | IoTeX-Studio
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/server.ts:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import serveStatic from "serve-static";
3 | import compression from "shrink-ray-current";
4 | import { redirectToHTTPS } from "express-http-to-https";
5 | import axios from "axios";
6 | import cors from "cors";
7 |
8 | const app = express();
9 |
10 | app
11 | .use(cors())
12 | .use(redirectToHTTPS([/localhost:(\d{4})/], [/\/insecure/], 301))
13 | .use(compression())
14 | .use(serveStatic(__dirname + "/dist", { maxAge: 86400 * 1000 }));
15 |
16 | app.get("/bin/:version", async (req, res, next) => {
17 | const version = req.params["version"];
18 | axios.get(`https://solc-bin.ethereum.org/bin/${version}`, { responseType: "stream" }).then((response) => {
19 | res.set({
20 | ...response.headers,
21 | "Access-Control-Allow-Origin": "*",
22 | "Access-Control-Allow-Methods": "PUT,POST,GET,DELETE,OPTIONS",
23 | "Cache-Control": "public, max-age=31557600",
24 | });
25 | response.data.pipe(res);
26 | });
27 | });
28 |
29 | var port = process.env.PORT || 5000;
30 | app.listen(port);
31 | console.log("server started " + port);
32 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
13 |
14 |
115 |
--------------------------------------------------------------------------------
/src/assets/global.styl:
--------------------------------------------------------------------------------
1 | color-primary = #06BFC1
2 | color-dark = #2d2d2d
3 | color-dark-text = rgb(190, 188, 183)
4 | color-dark-input-border = lighten(color-dark, 20%)
5 | color-dark-border = darken(color-dark, 15%)
6 | ::-webkit-scrollbar
7 | background-color darken(color-dark, 10%)
8 | width 8px
9 | background-clip padding-box
10 | ::-webkit-scrollbar-thumb
11 | background-color lighten(color-dark, 10%)
12 | border 1px solid lighten(color-dark, 20%)
13 | border-radius 5px
14 | ::-webkit-scrollbar-corner
15 | background-color darken(color-dark, 10%)
16 |
--------------------------------------------------------------------------------
/src/assets/tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/src/components/Compiler.vue:
--------------------------------------------------------------------------------
1 |
2 | .compiler.flex.flex-col
3 | .flex.mb-2.text-sm.font-bold SOLIDITY COMPILER
4 | el-form(label-position="left" label-width="80px" )
5 | el-form-item(label="Compiler")
6 | el-select(v-model="version")
7 | el-option(v-for="(item, version) in versions" :key="item" :label="item" :value="item" v-if="")
8 | div.mt-2.w-full
9 | el-checkbox.mb-4(v-model="optimizer") Enable Optimization
10 | el-button.w-full(@click="compile" :loading="solc.loading || solc.compileLoading" size="small" type="primary") Compile Contract
11 | .contract.mt-4(v-if="currentContractName")
12 | el-form-item(label="Contract")
13 | el-select(v-model="currentContractName")
14 | el-option(v-for="item in compileResult" :key="item.name" :label="item.name" :value="item.name")
15 | div.flex.justify-end
16 | el-button(icon="el-icon-document" type="text" @click="copyAbi" :disabled="!$_.get(currentContract, 'abi')") ABI
17 | el-button(icon="el-icon-document" type="text" @click="copyBytecode" :disabled="!$_.get(currentContract, 'evm.bytecode.object')") Bytecode
18 |
19 |
20 |
21 |
165 |
--------------------------------------------------------------------------------
/src/components/Deployer.vue:
--------------------------------------------------------------------------------
1 |
2 | .deployer.flex.flex-col
3 | .flex.mb-2.text-sm.font-bold DEPLOY & RUN TRANSACTIONS
4 | // Account & Env & Gas
5 | el-form(label-position="left" label-width="80px" )
6 | el-form-item(label="Environment")
7 | el-select.mb-1.w-full(v-model="currentEnvironment" )
8 | el-option(v-for="item in environments" :key="item" :label="item" :value="item")
9 | el-form-item(label="Account")
10 | .flex.flex-row.items-center
11 | el-select.mb-1.flex-1(v-model="accountIndex")
12 | el-option(v-for="(item, index) in accounts" :key="index" :label="accountLabel(item)" :value="index")
13 | span(@click="copyText(account.address)")
14 | el-icon.el-icon-document-copy.cursor-pointer.ml-2(class="hover:text-blue-600")
15 | el-form-item(label="Gas Limit")
16 | el-input.mb-1(v-model="form.gasLimit" size="small")
17 | el-form-item(label="Value")
18 | .flex.mb-1
19 | el-input(v-model="form.value" size="small")
20 | el-select(v-model="form.valueType" size="small")
21 | el-option(v-for="(item, index) in valueTypes" :key="index" :label="item" :value="item")
22 | // Contract argument
23 | .deploy-contract.mt-4
24 | .flex.mb-2.text-xs.font-bold Compiled contracts
25 | el-select.w-full(v-model="currentContractName")
26 | el-option(v-for="(item, index) in contracts" :key="index" :label="item.name" :value="item.name") {{item.name}}
27 | .flex.mt-4(v-if="currentContract")
28 | .func-full.w-full.func-detail.py-1.px-1(v-if="contractConsturcter.showConstructorDetail")
29 | .func-name.flex.justify-between.w-full.items-center(@click="contractConsturcter.showConstructorDetail=false")
30 | span Deploy
31 | el-icon.el-icon-arrow-up.cursor-pointer.ml-2(class="hover:text-blue-600" style="font-weight: bold;")
32 | .func-input-list.flex.items-center.my-2(v-if="$_.get(currentContract, 'abi.constructor.0')" v-for="(input,index) in $_.get(currentContract, 'abi.constructor.0.inputs')" :key="index")
33 | span.text-right(class="w-4/12") {{input.name}}:
34 | el-input.ml-2(v-model="input.value" size="mini" class="w-4/12" :placeholder="input.type")
35 | .func-actions.flex.justify-end.items-center
36 | span.mr-2
37 | el-icon.el-icon-document-copy.cursor-pointer.ml-2(class="hover:text-blue-600" )
38 | el-button(size="small" type="primary" @click="deployContract({fromDetail: true})")
39 | span transact
40 | .flex.flex-1(v-if="!contractConsturcter.showConstructorDetail")
41 | el-button(style="width: 160px;min-width: 160px" size="mini" @click="deployContract" type="primary") Deploy Contract
42 | el-input.flex-1(v-if="$_.get(currentContract, 'abi.constructor.0')" size="small" :placeholder="parseInputs($_.get(currentContract, 'abi.constructor.0'))" v-model="currentContract.abi.constructor[0].datas")
43 | span.func-bar-actions(@click="contractConsturcter.showConstructorDetail =true")
44 | el-icon.el-icon-arrow-down.cursor-pointer.ml-2(class="hover:text-blue-600" style="font-weight: bold;")
45 | .flex.mt-4(v-if="currentContract")
46 | el-button(style="width: 160px;min-width: 160px" size="mini" @click="deployContractFromAddress" :disabled="!deployForm.atContractInput" type="primary") Load deployed contract
47 | el-input(size="small" placeholder="Load contract from Address" v-model="deployForm.atContractInput")
48 | el-select.mb-1(size="small" v-if="currentEnvironment == 'Deploy via ioPay(Desktop)'" v-model="currentDeployProvider" value-key="name")
49 | el-option(v-for="item in providers" :key="item.name" :label="item.name" :value="item") {{item.name}}
50 | //Deployed contract list
51 | .deplyed-contracts.mt-6.text-sm
52 | .flex.mb-2.text-xs.font-bold Deployed Contracts
53 | .contract-list.p-2.rounded.border(v-for="(contract, index) in deployedContracts" :key="index")
54 | .flex.justify-between.items-center
55 | .contract(@click="$set(contract, 'showDetail', !contract.showDetail)")
56 | span.contract-title
57 | el-icon.el-icon-arrow-up.cursor-pointer.ml-2(v-if="contract.showDetail" class="hover:text-blue-600" style="font-weight: bold;")
58 | el-icon.el-icon-arrow-down.cursor-pointer.ml-2(v-else class="hover:text-blue-600" style="font-weight: bold;")
59 | span.ml-2 {{contract.name}} at {{contract.address|truncate(12, "...")}}
60 | .contract-actions.cursor-pointer
61 | span(@click="copyText(contract.address)")
62 | el-icon.el-icon-document-copy.cursor-pointer.ml-2(class="hover:text-blue-600" )
63 | span.ml-2(@click="deleteContract(contract)")
64 | el-icon.el-icon-delete(class="hover:text-blue-600" )
65 | .contract-func-list.my-4(v-if="contract.showDetail" v-for="(func,index) in $_.get(contract, 'abi.function')" :key="index")
66 | .func-full.w-full.func-detail.py-1.px-1(v-if="func.showDetail")
67 | .func-name.flex.justify-between.w-full.items-center(@click="$set(func, 'showDetail', !func.showDetail)")
68 | span {{func.name}}
69 | el-icon.el-icon-arrow-up.cursor-pointer.ml-2(class="hover:text-blue-600" style="font-weight: bold;")
70 | .func-input-list.flex.items-center.my-2(v-for="(input,index) in $_.get(func, 'inputs')" :key="index")
71 | span.text-right(class="w-4/12") {{input.name}}:
72 | el-input.ml-2(v-model="input.value" size="mini" class="w-4/12" :placeholder="input.type")
73 | .func-actions.flex.justify-end.items-center
74 | span.mr-2(@click="copyDatafromFunc({func, contract})")
75 | el-icon.el-icon-document-copy.cursor-pointer.ml-2(class="hover:text-blue-600" )
76 | el-button(size="small" type="primary" @click="interactContract({func, contract, fromDetail: true })")
77 | span(v-if="func.stateMutability == 'view'") call
78 | span(v-else) transact
79 | .func-bar.flex.items-center(v-else)
80 | el-button.func-action(@click="interactContract({func, contract })" style="width: 100px;min-width: 100px;" size="small" type="primary") {{func.name}}
81 | .func-input.flex-1
82 | el-input.w-full(v-if="func.inputs.length > 0" :placeholder="parseInputs(func)" v-model="func.datas" size="small")
83 | span.func-bar-actions(@click="$set(func, 'showDetail', !func.showDetail)")
84 | el-icon.el-icon-arrow-down.cursor-pointer.ml-2(class="hover:text-blue-600" style="font-weight: bold;")
85 | p(v-for="(result, index) in func.results" :key="index") {{index}} : {{result}}
86 |
87 |
88 |
504 |
505 |
514 |
--------------------------------------------------------------------------------
/src/components/Editor.vue:
--------------------------------------------------------------------------------
1 |
2 | div.h-full.w-full
3 |
4 |
5 |
97 |
--------------------------------------------------------------------------------
/src/components/FileManager.vue:
--------------------------------------------------------------------------------
1 |
2 | .file-manager.flex.flex-col.relative
3 | p.pt-1.pb-2.text-sm.font-bold.ml-4.flex.items-center
4 | span FILE EXPLORERS
5 | span.px-4.cursor-pointer(style="margin-left: auto" @click="handleLinkLocalhost")
6 | i.el-icon-link(title="link sharefolder" :class="['hover:text-grey-400', curLinkStatus=='connected' &&'text-teal-400', curLinkStatus == 'failed' && 'text-red-400' ]")
7 | .file-explorer.flex.flex-col.flex-1
8 | el-tree(:data="filesLoaded" ref="tree" node-key="path" highlight-current default-expand-all :props="{label: 'name'}" @node-click="handleNodeClick" @node-contextmenu="handleNodeContextMenu")
9 | div.custom-tree-node.w-full.h-full.px-4(slot-scope="{node, data}" v-contextmenu:contextmenu)
10 | div.el-tree-node_label.h-full
11 | el-icon(:class="[node.expanded? 'el-icon-folder-opened' : 'el-icon-folder']" v-if="data.isDir")
12 | el-icon.el-icon-document(v-if="!data.isDir")
13 | el-input.edit-name(v-model="data.editName" v-if="data.edit" v-focus :placeholder="data.name" @blur="renameFile" @keyup.enter.native="renameFile")
14 | span.ml-2.text-sm.select-none(v-else) {{data.name}}
15 | .space.h-full(v-contextmenu:contextmenu)
16 | v-contextmenu(ref="contextmenu" @hide="onContextMenuHide")
17 | v-contextmenu-item(v-if="!cursor.file || cursor.isDir" @click="showCreateNewFile(cursor.file, 'file')") New File
18 | v-contextmenu-item(v-if="!cursor.file || cursor.isDir" @click="showCreateNewFile(cursor.file, 'dir')") New Folder
19 | v-contextmenu-item(v-if="cursor.file" @click="startRenameFile(cursor.file)") Rename
20 | v-contextmenu-item(v-if="cursor.file" @click="deleteFile") Delete
21 | el-dialog(:visible.sync="createFileForm.visible" title="Create new File" width="30%")
22 | el-form(:model="createFileForm" ref="createFileForm" :rules="createFileForm.rules" v-if="createFileForm.visible" @submit.native.prevent)
23 | el-form-item(prop="name")
24 | el-input(v-model="createFileForm.name" v-focus placeholder="file name" @keyup.enter.native="createNewFile")
25 | span(slot="footer")
26 | el-button(@click="createFileForm.visible= false") Cancel
27 | el-button(type="primary" @click="createNewFile") Confirm
28 |
29 |
30 |
31 |
32 |
33 |
372 |
373 |
384 |
--------------------------------------------------------------------------------
/src/components/Menubar.vue:
--------------------------------------------------------------------------------
1 |
2 | .menubar
3 | .flex.px-4.items-center
4 | span IoTeX Studio
5 | el-menu.menu(mode="horizontal")
6 | el-submenu(index="1" :show-timeout="0" :hide-timeout="0")
7 | span(slot="title") File
8 | el-menu-item(@click="newFile") New File
9 | el-menu-item(@click="newFolder") New Folder
10 | el-menu-item(@click="saveAll") Save All
11 | el-submenu(index="2" :show-timeout="0" :hide-timeout="0")
12 | span(slot="title") Edit
13 | el-menu-item(@click="undo") Undo
14 | el-menu-item(@click="redo") Redo
15 |
16 |
17 |
18 |
19 |
20 |
43 |
44 |
85 |
--------------------------------------------------------------------------------
/src/components/Statusbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
--------------------------------------------------------------------------------
/src/components/Terminal.vue:
--------------------------------------------------------------------------------
1 |
2 | .terminal.flex.flex-col.h-full
3 | .content.flex-1
4 | .flex.flex-col-reverse.h-full.overflow-auto
5 | .item(v-for="(item,index) in stdout" :key="index")
6 | div(@click="item.expanded = !item.expanded")
7 | el-alert(v-if="item.component='alert'" :title="item.text" :type="item.type" show-icon :description="item.description" :closable="false")
8 | span(v-else)
9 | .flex.flex-col.detail.px-6.text-xs(v-if="item.data && item.expanded")
10 | div.flex.justify-between(v-for="(value, key) in item.data" :key="key")
11 | div {{key}}:
12 | div
13 | span {{value}}
14 | span(@click="copyText(value)")
15 | el-icon.el-icon-document-copy.cursor-pointer.ml-2(class="hover:text-blue-600" )
16 | .input-bar.flex.px-2.w-full.items-center
17 | span >
18 | el-input.input(v-model="input" @keyup.enter.native="runCommand" size="small")
19 |
20 |
21 |
22 |
76 |
77 |
98 |
--------------------------------------------------------------------------------
/src/components/Toolbar.vue:
--------------------------------------------------------------------------------
1 |
2 | .flex
3 | el-tabs(:value="curFilePath" type="card" closable @tab-remove="removeTab" @tab-click="clickTab")
4 | el-tab-pane(v-for="item in tabs" :key="item.path" :label="item.name" :name="item.path")
5 |
6 |
7 |
61 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 |
3 | import Editor from "./Editor.vue";
4 | import Compiler from "./Compiler.vue";
5 | import Deployer from "./Deployer.vue";
6 | import FileManager from "./FileManager.vue";
7 | import Terminal from "./Terminal.vue";
8 | import Toolbar from "./Toolbar.vue";
9 | import Menubar from "./Menubar.vue";
10 | import StatusBar from "./Statusbar.vue";
11 |
12 | Vue.component("editor", Editor);
13 | Vue.component("compiler", Compiler);
14 | Vue.component("deployer", Deployer);
15 | Vue.component("file-manager", FileManager);
16 | Vue.component("terminal", Terminal);
17 | Vue.component("toolbar", Toolbar);
18 | Vue.component("menubar", Menubar);
19 | Vue.component("statusbar", StatusBar);
20 |
--------------------------------------------------------------------------------
/src/components/template.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import App from "./App.vue";
3 | import router from "./router";
4 | import store from "./store";
5 | import "./components";
6 | import "./utils/filter";
7 | import "./utils/directives";
8 | import { _ } from "./utils/lodash";
9 |
10 | import "./plugins/element-ui";
11 |
12 | import VueClipboard from "vue-clipboard2";
13 | Vue.use(VueClipboard);
14 |
15 | import contentmenu from "v-contextmenu";
16 | Vue.use(contentmenu);
17 |
18 | import VueSplit from "vue-split-panel";
19 | import { eventBus } from "./utils/eventBus";
20 | Vue.use(VueSplit);
21 |
22 | // Vue.config.errorHandler = (err, vm, info) => {
23 | // console.log({ err, vm, info });
24 | // // eventBus.emit("term.error", {
25 | // // text: "err"
26 | // // });
27 | // };
28 |
29 | Vue.prototype.$_ = _;
30 |
31 | Vue.config.productionTip = false;
32 | new Vue({
33 | router,
34 | store,
35 | render: (h) => h(App),
36 | }).$mount("#app");
37 |
--------------------------------------------------------------------------------
/src/plugins/element-ui.ts:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import { Button, Select, Option, Form, FormItem, Icon, Divider, Tree, Tabs, Alert, Dialog, Input, Menu, MenuItem, Submenu, Message, TabPane, MessageBox, Loading, Checkbox } from "element-ui";
3 |
4 | import lang from "element-ui/lib/locale/lang/en";
5 | import locale from "element-ui/lib/locale";
6 | locale.use(lang);
7 |
8 | Vue.use(Button)
9 | .use(Select)
10 | .use(Option)
11 | .use(Form)
12 | .use(FormItem)
13 | .use(Icon)
14 | .use(Divider)
15 | .use(Tree)
16 | .use(Alert)
17 | .use(Dialog)
18 | .use(Input)
19 | .use(Menu)
20 | .use(MenuItem)
21 | .use(Submenu)
22 | .use(Tabs)
23 | .use(TabPane)
24 | .use(Loading)
25 | .use(Checkbox);
26 |
27 | Vue.prototype.$alert = Alert;
28 | Vue.prototype.$message = Message;
29 | Vue.prototype.$msgbox = MessageBox;
30 | Vue.prototype.$confirm = MessageBox.confirm;
31 |
--------------------------------------------------------------------------------
/src/plugins/resolve-github.ts:
--------------------------------------------------------------------------------
1 | // ref: https://github.com/alincode/resolve-github/blob/master/src/resolver.js
2 | import { replaceContent } from "solc-import";
3 | import gh from "parse-github-url";
4 |
5 | const match = /^(https?:\/\/)?(www.)?github.com\/([^/]*\/[^/]*)\/(.*)/;
6 |
7 | const resolver = (content, from, subImportPath) => {
8 | let newContent = content;
9 | let url = new window.URL(subImportPath, from);
10 | let fixedPath = url.href;
11 | newContent = newContent.replace(`import '${subImportPath}'`, `import '${fixedPath}'`);
12 | newContent = newContent.replace(`import "${subImportPath}"`, `import "${fixedPath}"`);
13 | return newContent;
14 | };
15 |
16 | const parser = async function (importPath) {
17 | const { owner, name, repo, branch, filepath } = gh(importPath);
18 |
19 | let url = `https://raw.githubusercontent.com/${repo}/${branch}/${filepath}`;
20 | try {
21 | let data = await getData(url);
22 | if (isSymbolicLink(data)) {
23 | const tmps = url.split("/");
24 | const filename = tmps[tmps.length - 1];
25 | url = url.replace(filename, data);
26 | data = await getData(url);
27 | } else {
28 | data = replaceContent(data, importPath, resolver);
29 | }
30 | return data;
31 | } catch (error) {
32 | throw error;
33 | }
34 | };
35 |
36 | async function getData(url) {
37 | let response = await fetch(url, { method: "GET" });
38 | let data = await response.text();
39 | if (!response.ok || response.status !== 200) throw Error("Content " + data);
40 | return data;
41 | }
42 |
43 | function isSymbolicLink(data) {
44 | if (data.length < 50 && data.indexOf(".sol")) return true;
45 | return false;
46 | }
47 |
48 | // async function getSource(importPath, root, path) {
49 | // const url = `https://api.github.com/repos/${root}/contents/${path}`;
50 | // // console.log('url:', url);
51 | // try {
52 | // const response = await fetch(url, { method: 'GET' });
53 | // let data = await response.text();
54 | // if (!response.ok || response.status !== 200) throw Error(data);
55 | // data = JSON.parse(data);
56 | // data.content = window.atob(data.content);
57 | // data.content = replaceContent(data.content, importPath, pathResolve);
58 | // if ('content' in data) return data.content;
59 | // if ('message' in data) throw Error(data.message);
60 | // throw Error('Content not received');
61 | // } catch (error) {
62 | // // Unknown transport error
63 | // throw error;
64 | // }
65 | // }
66 |
67 | export default {
68 | type: "github",
69 | parser,
70 | resolver,
71 | match,
72 | };
73 |
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import VueRouter from "vue-router";
3 | import Home from "../views/Home.vue";
4 |
5 | Vue.use(VueRouter);
6 |
7 | const routes = [
8 | {
9 | path: "/",
10 | name: "home",
11 | component: Home
12 | }
13 | ];
14 |
15 | const router = new VueRouter({
16 | mode: "history",
17 | base: process.env.BASE_URL,
18 | routes
19 | });
20 |
21 | export default router;
22 |
--------------------------------------------------------------------------------
/src/shims-tsx.d.ts:
--------------------------------------------------------------------------------
1 | import Vue, { VNode } from "vue";
2 | declare global {
3 | namespace JSX {
4 | // tslint:disable no-empty-interface
5 | interface Element extends VNode {}
6 | // tslint:disable no-empty-interface
7 | interface ElementClass extends Vue {}
8 | interface ElementAttributesProperty {
9 | $props: any; // specify the property name to use
10 | }
11 | interface IntrinsicElements {
12 | [elem: string]: any;
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | import { _ } from "./utils/lodash";
2 |
3 | declare module "*.vue" {
4 | import Vue from "vue";
5 |
6 | export default Vue;
7 | }
8 |
9 | declare module "vue/types/vue" {
10 | interface Vue {
11 | $_: _;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/store/editor.ts:
--------------------------------------------------------------------------------
1 | import { make } from "vuex-pathify";
2 | import * as constant from "../utils/constant";
3 | import store from "@/store";
4 | import { FS } from "../utils/fs";
5 | import ace from "brace";
6 | import { CompiledContract } from "./type";
7 | import { _ } from "../utils/lodash";
8 | import semver from "semver";
9 |
10 | const state: {
11 | fileManager: {
12 | file?: FS["file"];
13 | files: {
14 | [key: string]: FS["file"];
15 | };
16 | filesLoaded: FS["files"];
17 | defaultFiles: { path: string; content: string; ensure: boolean }[];
18 | };
19 | ace: {
20 | editor: ace.Editor;
21 | };
22 | solc: {
23 | loading: boolean;
24 | compileLoading: boolean;
25 | compiler: any;
26 | compileResult: Record;
27 | currentContractName: string;
28 | versions: {
29 | builds: { build: string; keccak256: string; longVersion: string; path: string; urls: string[] }[];
30 | latestRelease: string;
31 | releases: { [key: string]: string };
32 | };
33 | };
34 | } = {
35 | ace: {
36 | editor: null,
37 | },
38 | fileManager: {
39 | files: {},
40 | filesLoaded: [],
41 | defaultFiles: [
42 | { path: "/project/default/test.sol", content: constant.defaultContract, ensure: true },
43 | { path: "/project/default/erc20/erc20.sol", content: constant.erc20, ensure: true },
44 | { path: "/project/default/erc721/erc721.sol", content: constant.erc721, ensure: true },
45 | ],
46 | },
47 |
48 | solc: {
49 | loading: false,
50 | compileLoading: false,
51 | compiler: null,
52 | currentContractName: "",
53 | compileResult: {},
54 | versions: {
55 | builds: [],
56 | latestRelease: "",
57 | releases: {},
58 | },
59 | },
60 | };
61 |
62 | export type EditorStore = typeof state;
63 |
64 | const getters: {
65 | [key: string]: (state: EditorStore) => any;
66 | } = {
67 | curFile: (state) => store.state.editor.fileManager.files[store.state.storage.curProject.fileManager.curFilePath],
68 | versions: (state) => {
69 | const versions = {};
70 | _.each(state.solc.versions.releases, (v, k) => {
71 | if (semver.satisfies(k, "0.1.1 - 0.5.13")) {
72 | versions[k] = v;
73 | }
74 | });
75 | return versions;
76 | },
77 | };
78 | const mutations = make.mutations(state);
79 |
80 | export default {
81 | namespaced: true,
82 | getters,
83 | mutations,
84 | state,
85 | };
86 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import Vuex from "vuex";
3 | import editor from "./editor";
4 | import storage from "./storage";
5 |
6 | import pathify, { make } from "vuex-pathify";
7 | pathify.options.mapping = "simple";
8 |
9 | import VuexPersist from "vuex-persist";
10 | import { StorageStore } from "./storage";
11 | import { EditorStore } from "./editor";
12 |
13 | const storagePersist = new VuexPersist({
14 | key: "iotex-studio",
15 | storage: window.localStorage,
16 | modules: ["storage"],
17 | });
18 |
19 | const state = {};
20 |
21 | const mutations = make.mutations(state);
22 |
23 | Vue.use(Vuex);
24 | export default new Vuex.Store<{
25 | editor: EditorStore;
26 | storage: StorageStore;
27 | }>({
28 | plugins: [pathify.plugin, storagePersist.plugin],
29 | //@ts-ignore
30 | state,
31 | mutations,
32 | actions: {},
33 | modules: {
34 | editor,
35 | storage,
36 | },
37 | });
38 |
--------------------------------------------------------------------------------
/src/store/storage.ts:
--------------------------------------------------------------------------------
1 | import { make } from "vuex-pathify";
2 | import { FS } from "../utils/fs";
3 | import { CompiledContract } from "./type";
4 |
5 | const state: {
6 | curProject: {
7 | solc: {
8 | version: string;
9 | optimizer: boolean;
10 | };
11 | fileManager: {
12 | curDir: string;
13 | curFilePath: string;
14 | curLinkStatus: "init" | "connecting" | "failed" | "connected";
15 | };
16 | toolbar: {
17 | tabs: {
18 | [key: string]: Partial;
19 | };
20 | };
21 | };
22 | ace: {
23 | content: string;
24 | theme: string;
25 | lang: string;
26 | options: any;
27 | };
28 |
29 | split: {
30 | size: {
31 | main: number[];
32 | editor: number[];
33 | };
34 | };
35 | } = {
36 | ace: {
37 | content: "",
38 | theme: "tomorrow_night_eighties",
39 | lang: "solidity",
40 | options: {
41 | enableBasicAutocompletion: true,
42 | enableLiveAutocompletion: true,
43 | // enableSnippets: true
44 | },
45 | },
46 | split: {
47 | size: {
48 | main: [10, 70, 20],
49 | editor: [75, 25],
50 | },
51 | },
52 | curProject: {
53 | solc: {
54 | version: "soljson-v0.5.5+commit.47a71e8f.js",
55 | optimizer: false,
56 | },
57 | fileManager: {
58 | curDir: "/project/default",
59 | curFilePath: null,
60 | curLinkStatus: "init",
61 | },
62 | toolbar: {
63 | tabs: {},
64 | },
65 | },
66 | };
67 |
68 | export type StorageStore = typeof state;
69 |
70 | const mutations = make.mutations(state);
71 |
72 | export default {
73 | namespaced: true,
74 | mutations,
75 | state,
76 | };
77 |
--------------------------------------------------------------------------------
/src/store/type.ts:
--------------------------------------------------------------------------------
1 | import ace from "brace";
2 | import { FS } from "../utils/fs";
3 |
4 | export interface StdoutType {
5 | text: string;
6 | component?: "alert" | "json";
7 | data?: Object;
8 | type?: "success" | "info" | "warning" | "error";
9 | description?: string;
10 | expanded?: boolean;
11 | }
12 |
13 | export type AbiFunc = {
14 | constant: boolean;
15 | inputs: {
16 | name: string;
17 | type: string;
18 | value?: string;
19 | }[];
20 | outputs: {
21 | name: string;
22 | type: string;
23 | }[];
24 | datas?: string;
25 | name: string;
26 | payable: boolean;
27 | stateMutability: string;
28 | type: string;
29 | };
30 |
31 | export type CompiledContract = {
32 | abi: AbiFunc[];
33 | showDetail?: boolean;
34 | assembly: Object;
35 | binary: Object;
36 | compiler: Object;
37 | metadata: Object;
38 | name: string;
39 | sources: Object;
40 | };
41 |
--------------------------------------------------------------------------------
/src/utils/antenna.ts:
--------------------------------------------------------------------------------
1 | import Antenna from "iotex-antenna";
2 | import { WsSignerPlugin } from "./ws-plugin";
3 |
4 | export const wsSigner = new WsSignerPlugin({
5 | options: {
6 | packMessage: (data) => JSON.stringify(data),
7 | //@ts-ignore
8 | unpackMessage: (data) => JSON.parse(data),
9 | attachRequestId: (data, requestId) => Object.assign({ reqId: requestId }, data),
10 | extractRequestId: (data) => data && data.reqId,
11 | },
12 | });
13 |
14 | export class AntennaUtils {
15 | static providers = {
16 | mainnet: { name: "mainnet", url: "https://api.iotex.one:443" },
17 | testnet: { name: "testnet", url: "https://api.testnet.iotex.one:443" },
18 | };
19 |
20 | static getProdiver(name: string) {
21 | if (this.providers[name]) {
22 | return this.providers[name].url;
23 | }
24 | throw new Error(`provider ${name} not exists`);
25 | }
26 | }
27 |
28 | export const antenna = new Antenna(AntennaUtils.providers.testnet.url, {
29 | signer: wsSigner,
30 | });
31 |
--------------------------------------------------------------------------------
/src/utils/constant.ts:
--------------------------------------------------------------------------------
1 | export const erc20 = `pragma solidity ^0.5.0;
2 |
3 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
4 | import "@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol";
5 |
6 | contract TestToken is ERC20, ERC20Detailed {
7 | constructor(uint256 initialSupply) ERC20Detailed("Test", "TEST", 18) public {
8 | _mint(msg.sender, initialSupply);
9 | }
10 | }`;
11 |
12 | export const defaultContract = `pragma solidity ^0.5.0;
13 |
14 | contract Array {
15 | uint256[] public arr;
16 |
17 | constructor(uint256[] memory _arr) public {
18 | arr = _arr;
19 | }
20 |
21 | function insert(uint256 element) public {
22 | arr.push(element);
23 | }
24 |
25 | function removeOne() public {
26 | arr.pop();
27 | }
28 | }`;
29 |
30 | export const erc721 = `pragma solidity ^0.5.0;
31 |
32 | import "@openzeppelin/contracts/token/ERC721/ERC721Full.sol";
33 |
34 |
35 | contract ViperToken is ERC721Full {
36 | using SafeMath for uint256;
37 | // This struct will be used to represent one viper
38 | struct Viper {
39 | uint8 genes;
40 | uint256 matronId;
41 | uint256 sireId;
42 | }
43 |
44 | // List of existing vipers
45 | Viper[] public vipers;
46 |
47 | // Event that will be emitted whenever a new viper is created
48 | event Birth(
49 | address owner,
50 | uint256 viperId,
51 | uint256 matronId,
52 | uint256 sireId,
53 | uint8 genes
54 | );
55 |
56 | // Initializing an ERC-721 Token named 'Vipers' with a symbol 'VPR'
57 | constructor() ERC721Full("Vipers", "VPR") public {
58 | }
59 |
60 | // Fallback function
61 | function() external payable {
62 | }
63 |
64 | /** @dev Function to determine a viper's characteristics.
65 | * @param matron ID of viper's matron (one parent)
66 | * @param sire ID of viper's sire (other parent)
67 | * @return The viper's genes in the form of uint8
68 | */
69 | function generateViperGenes(
70 | uint256 matron,
71 | uint256 sire
72 | )
73 | internal
74 | pure
75 | returns (uint8)
76 | {
77 | return uint8(matron.add(sire)) % 6 + 1;
78 | }
79 |
80 | /** @dev Function to create a new viper
81 | * @param matron ID of new viper's matron (one parent)
82 | * @param sire ID of new viper's sire (other parent)
83 | * @param viperOwner Address of new viper's owner
84 | * @return The new viper's ID
85 | */
86 | function createViper(
87 | uint256 matron,
88 | uint256 sire,
89 | address viperOwner
90 | )
91 | internal
92 | returns (uint)
93 | {
94 | require(viperOwner != address(0));
95 | uint8 newGenes = generateViperGenes(matron, sire);
96 | Viper memory newViper = Viper({
97 | genes: newGenes,
98 | matronId: matron,
99 | sireId: sire
100 | });
101 | uint256 newViperId = vipers.push(newViper).sub(1);
102 | super._mint(viperOwner, newViperId);
103 | emit Birth(
104 | viperOwner,
105 | newViperId,
106 | newViper.matronId,
107 | newViper.sireId,
108 | newViper.genes
109 | );
110 | return newViperId;
111 | }
112 |
113 | /** @dev Function to allow user to buy a new viper (calls createViper())
114 | * @return The new viper's ID
115 | */
116 | function buyViper() external payable returns (uint256) {
117 | require(msg.value == 0.02 ether);
118 | return createViper(0, 0, msg.sender);
119 | }
120 |
121 | /** @dev Function to breed 2 vipers to create a new one
122 | * @param matronId ID of new viper's matron (one parent)
123 | * @param sireId ID of new viper's sire (other parent)
124 | * @return The new viper's ID
125 | */
126 | function breedVipers(uint256 matronId, uint256 sireId) external payable returns (uint256) {
127 | require(msg.value == 0.05 ether);
128 | return createViper(matronId, sireId, msg.sender);
129 | }
130 |
131 | /** @dev Function to retrieve a specific viper's details.
132 | * @param viperId ID of the viper who's details will be retrieved
133 | * @return An array, [viper's ID, viper's genes, matron's ID, sire's ID]
134 | */
135 | function getViperDetails(uint256 viperId) external view returns (uint256, uint8, uint256, uint256) {
136 | Viper storage viper = vipers[viperId];
137 | return (viperId, viper.genes, viper.matronId, viper.sireId);
138 | }
139 |
140 | /** @dev Function to get a list of owned vipers' IDs
141 | * @return A uint array which contains IDs of all owned vipers
142 | */
143 | function ownedVipers() external view returns(uint256[] memory) {
144 | uint256 viperCount = balanceOf(msg.sender);
145 | if (viperCount == 0) {
146 | return new uint256[](0);
147 | } else {
148 | uint256[] memory result = new uint256[](viperCount);
149 | uint256 totalVipers = vipers.length;
150 | uint256 resultIndex = 0;
151 | uint256 viperId = 0;
152 | while (viperId < totalVipers) {
153 | if (ownerOf(viperId) == msg.sender) {
154 | result[resultIndex] = viperId;
155 | resultIndex = resultIndex.add(1);
156 | }
157 | viperId = viperId.add(1);
158 | }
159 | return result;
160 | }
161 | }
162 | }
163 | `;
164 |
165 | export const defaultTypeValue = {
166 | address: "io1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqd39ym7",
167 | uint256: "0",
168 | "uint256[]": "[]",
169 | string: "",
170 | "string[]": "[]",
171 | "address[]": [],
172 | };
173 |
--------------------------------------------------------------------------------
/src/utils/directives.ts:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | Vue.directive("focus", {
3 | inserted: function (el) {
4 | el.querySelector("input").focus();
5 | },
6 | });
7 |
--------------------------------------------------------------------------------
/src/utils/eventBus.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from "events";
2 | import TypedEmitter from "typed-emitter";
3 | import ace from "brace";
4 | import { StdoutType } from "../store/type";
5 | import { FS } from "./fs";
6 | import { EditorStore } from "../store/editor";
7 | import { StorageStore } from "../store/storage";
8 |
9 | interface MessageEvents {
10 | "editor.save": () => void;
11 | "editor.init": (editor: ace.Editor) => void;
12 | "editor.content.update": (content: string) => void;
13 | "solc.compile": () => void;
14 | "solc.compiled.finished": (result: EditorStore["solc"]["compileResult"]) => void;
15 | "solc.compiled.failed": (err: Error) => void;
16 | "term.message": (message: StdoutType) => void;
17 | "term.error": (message: StdoutType) => void;
18 | "term.success": (message: StdoutType) => void;
19 | "term.warning": (message: StdoutType) => void;
20 | "term.info": (message: StdoutType) => void;
21 | "term.messages": (messages: StdoutType[]) => void;
22 | "fs.ready": () => void;
23 | "fs.select": (file: FS["file"]) => void;
24 | "fs.loadFiles": (files: EditorStore["fileManager"]["files"]) => void;
25 | "toolbar.tab.select": (file: Partial) => void;
26 | "menubar.newFile": () => void;
27 | "menubar.newFolder": () => void;
28 | "menubar.undo": () => void;
29 | "menubar.redo": () => void;
30 | "menubar.saveAll": () => void;
31 | "sharefolder.ws.connected": () => void;
32 | "sharefolder.ws.closed": () => void;
33 | "sharefolder.ws.error": (e: Error) => void;
34 | }
35 |
36 | export const eventBus = new EventEmitter() as TypedEmitter;
37 |
38 | eventBus.on("editor.init", (editor) => {
39 | require("brace/ext/language_tools");
40 | require("brace/mode/javascript");
41 | require("ace-mode-solidity/build/remix-ide/mode-solidity");
42 | require("brace/ext/searchbox");
43 | require("brace/snippets/javascript");
44 | editor.commands.addCommand({
45 | name: "SaveAndCompile",
46 | bindKey: { win: "Ctrl-S", mac: "Command-S" },
47 | exec: () => {
48 | eventBus.emit("editor.save");
49 | },
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/src/utils/filter.ts:
--------------------------------------------------------------------------------
1 | import { Vue } from "vue-property-decorator";
2 | import { utils } from "ethers";
3 | export const truncate = (fullStr = "", strLen, separator = "") => {
4 | if (fullStr.length <= strLen) return fullStr;
5 |
6 | separator = separator || "...";
7 |
8 | const sepLen = separator.length;
9 | const charsToShow = strLen - sepLen;
10 | const frontChars = Math.ceil(charsToShow / 2);
11 | const backChars = Math.floor(charsToShow / 2);
12 |
13 | return fullStr.substr(0, frontChars) + separator + fullStr.substr(fullStr.length - backChars);
14 | };
15 |
16 | Vue.filter("truncate", (val, length, separator) => truncate(val, length, separator));
17 | Vue.filter("formatEther", val => utils.formatEther);
18 |
--------------------------------------------------------------------------------
/src/utils/fs.ts:
--------------------------------------------------------------------------------
1 | import util from "util";
2 | import _fs, { stat } from "fs";
3 | import { Helper } from "./helper";
4 | import * as path from "path";
5 | import { app } from "./index";
6 |
7 | export const promisify = (obj) => {
8 | return new Proxy(obj, {
9 | get: (target, name) => {
10 | if (name in target) {
11 | if (target[name] instanceof Function) {
12 | return util.promisify(target[name]);
13 | }
14 | }
15 | return target[name];
16 | },
17 | });
18 | };
19 |
20 | export const fs = _fs;
21 | if (!fs.promises) {
22 | fs.promises = promisify(fs);
23 | }
24 |
25 | export class FS {
26 | file: { name: string; content: string | null; edit?: boolean; editName?: string; isDir: boolean; path: string; children: FS["files"] | null };
27 | files: FS["file"][];
28 |
29 | constructor(props: Partial) {
30 | Object.assign(this, props);
31 | }
32 | async list(dir, options: { ensure?: boolean; onDir?: (data: FS["file"]) => any; onFile?: (data: FS["file"]) => any } = {}): Promise {
33 | const { ensure = true, onDir, onFile } = options;
34 | if (ensure) await this.ensureDir(dir);
35 | const filesInDir = await fs.promises.readdir(dir);
36 | const files = await Promise.all(
37 | filesInDir.map(async (file) => {
38 | const filePath = path.join(dir, file);
39 | const stats = await fs.promises.stat(filePath);
40 | const isDir = stats.isDirectory();
41 |
42 | let data = {
43 | name: file,
44 | isDir,
45 | path: filePath,
46 | content: null,
47 | children: null,
48 | };
49 | if (isDir) {
50 | data.children = await this.list(filePath, options);
51 | onDir && (await onDir(data));
52 | } else {
53 | data.content = (await fs.promises.readFile(filePath)).toString();
54 | onFile && (await onFile(data));
55 | }
56 |
57 | return data;
58 | })
59 | );
60 | return files;
61 | }
62 | async ensureDir(dir) {
63 | if (dir == "/") return;
64 | //@ts-ignore
65 | const [exists] = await app.helper.runAsync(fs.promises.exists(dir));
66 | if (!exists) {
67 | await this.ensureDir(path.dirname(dir));
68 | await fs.promises.mkdir(dir);
69 | return false;
70 | }
71 | return true;
72 | }
73 | async ensureWrite(filePath, content, options?) {
74 | await this.ensureDir(path.dirname(filePath));
75 | const [err, stats] = await app.helper.runAsync(fs.promises.stat(filePath));
76 | if (err || !stats.isFile()) {
77 | await fs.promises.writeFile(filePath, content, options);
78 | }
79 | }
80 | async writeFile(filePath, content, options?) {
81 | return fs.promises.writeFile(filePath, content, options);
82 | }
83 |
84 | async rm(path) {
85 | const [err, stat] = await app.helper.runAsync(fs.promises.lstat(path));
86 | if (err) throw err;
87 | if (stat.isDirectory()) {
88 | const files = await fs.promises.readdir(path);
89 |
90 | for (let file of files) {
91 | const curPath = `${path}/${file}`;
92 | const stat = await fs.promises.lstat(curPath);
93 | if (stat.isDirectory) {
94 | await this.rm(curPath);
95 | } else {
96 | await fs.promises.unlink(curPath);
97 | }
98 | }
99 | await fs.promises.rmdir(path);
100 | } else if (stat.isFile()) {
101 | await fs.promises.unlink(path);
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/utils/helper.ts:
--------------------------------------------------------------------------------
1 | export class Helper {
2 | async sleep(ms) {
3 | return new Promise((resolve) => setTimeout(resolve, ms));
4 | }
5 | async runAsync(promise: Promise): Promise<[U | null, T | null]> {
6 | return promise
7 | .then<[null, T]>((data: T) => [null, data])
8 | .catch<[U, null]>((err) => [err, null]);
9 | }
10 | safeJSONParse(value: any) {
11 | try {
12 | return JSON.parse(value);
13 | } catch (error) {
14 | return value;
15 | }
16 | }
17 | }
18 |
19 | export const helper = new Helper();
20 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | import store from "@/store";
2 | import { eventBus } from "./eventBus";
3 | import { fs } from "./fs";
4 | import { jsvm } from "./vm";
5 | import { helper } from "./helper";
6 | import { _ } from "./lodash";
7 | import { antenna } from "./antenna";
8 | import { sf } from "./sharefolder";
9 |
10 | export const app = {
11 | store,
12 | eventBus,
13 | fs,
14 | jsvm,
15 | helper,
16 | _,
17 | antenna,
18 | sf,
19 | };
20 |
--------------------------------------------------------------------------------
/src/utils/lodash.ts:
--------------------------------------------------------------------------------
1 | import each from "lodash/each";
2 | import get from "lodash/get";
3 | import set from "lodash/set";
4 | import keyBy from "lodash/keyBy";
5 | import groupBy from "lodash/groupBy";
6 | import range from "lodash/range";
7 | import omitBy from "lodash/omitBy";
8 | import omit from "lodash/omit";
9 | import pick from "lodash/pick";
10 | import orderBy from "lodash/orderBy";
11 | import isNil from "lodash/isNil";
12 | import compact from "lodash/compact";
13 | import cloneDeep from "lodash/cloneDeep";
14 | import reduce from "lodash/reduce";
15 |
16 | export const _ = {
17 | each,
18 | get,
19 | set,
20 | keyBy,
21 | groupBy,
22 | range,
23 | omitBy,
24 | isNil,
25 | compact,
26 | omit,
27 | pick,
28 | cloneDeep,
29 | orderBy,
30 | reduce
31 | };
32 |
--------------------------------------------------------------------------------
/src/utils/sharefolder.ts:
--------------------------------------------------------------------------------
1 | import WebSocket from "websocket-as-promised";
2 | import { eventBus } from "./eventBus";
3 | import { app } from "./index";
4 |
5 | export class ShareFolder {
6 | public ws: WebSocket;
7 |
8 | async start() {
9 | this.ws = new WebSocket("ws://localhost:65520", {
10 | timeout: 5000,
11 | connectionTimeout: 10000,
12 | packMessage: (data) => JSON.stringify(data),
13 | //@ts-ignore
14 | unpackMessage: (data) => JSON.parse(data),
15 | attachRequestId: (data, requestId) => Object.assign({ id: requestId }, data),
16 | extractRequestId: (data) => data && data.id,
17 | });
18 | this.ws.onOpen.addListener(() => {
19 | console.log("open");
20 | eventBus.emit("sharefolder.ws.connected");
21 | });
22 | this.ws.onClose.addListener(() => {
23 | console.log("close");
24 | eventBus.emit("sharefolder.ws.closed");
25 | });
26 | this.ws.onError.addListener((e) => {
27 | console.error(e);
28 | eventBus.emit("sharefolder.ws.error", e);
29 | });
30 | // this.ws.onMessage.addListener((e) => {
31 | // // console.log(e);
32 | // });
33 | await this.ws.open();
34 | await this.ws.sendRequest({
35 | action: "request",
36 | key: "handshake",
37 | payload: ["iotex-studio"],
38 | });
39 |
40 | return this;
41 | }
42 |
43 | async ensureSocket() {
44 | if (this.ws?.isOpened || this.ws?.isOpening) return this.ws;
45 | await this.start();
46 |
47 | return this.ws;
48 | }
49 |
50 | async close() {
51 | return this.ws.close();
52 | }
53 |
54 | async call({ args = [], method }: { args?: any; method: string }) {
55 | await this.ensureSocket();
56 | return this.ws.sendRequest({
57 | action: "request",
58 | requestInfo: { path: "sharedfolder" },
59 | key: method,
60 | payload: args,
61 | });
62 | }
63 |
64 | async dir() {
65 | const res = await this.call({ method: "list" });
66 | return res.payload;
67 | }
68 | async get({ path }: { path: string }) {
69 | const res = await this.call({ method: "get", args: [{ path }] });
70 | return res.payload;
71 | }
72 | async set({ path, content }) {
73 | await this.call({ method: "set", args: [{ path, content }] });
74 | }
75 | async rename({ oldPath, newPath }) {
76 | await this.call({ method: "rename", args: [{ oldPath, newPath }] });
77 | }
78 | async remove({ path }) {
79 | await await this.call({ method: "remove", args: [{ path }] });
80 | }
81 | }
82 |
83 | export const sf = new ShareFolder();
84 |
--------------------------------------------------------------------------------
/src/utils/solc.ts:
--------------------------------------------------------------------------------
1 | import solcjsCore from "solcjs-core-fix";
2 | import { resolverEngine } from "solc-resolver";
3 | import resolveGithub from "../plugins/resolve-github";
4 | import resolveHttp from "resolve-http";
5 | import store from "@/store";
6 | import path from "path";
7 |
8 | const solcWrapper = solcjsCore.solcWrapper.wrapper;
9 | export class SolcmManager {
10 | static resolveEngine = new resolverEngine().addResolver(resolveGithub).addResolver(resolveHttp);
11 | static compiler: any;
12 | static async loadSolc(version) {
13 | // const url = await solcVersion.version2url(version);
14 | const url = process.env.NODE_ENV == "production" ? `https://ide-solc-iotex.b-cdn.net/bin/${version}` : `/bin/${version}`;
15 | console.time("[fetch compiler]");
16 | let compilersource = await solcjsCore.getCompilersource(url);
17 | console.timeEnd("[fetch compiler]");
18 | const solcjson = solcjsCore.loadModule(compilersource);
19 | const compiler = (this.compiler = solcWrapper(solcjson));
20 | return compiler;
21 | }
22 |
23 | static async compile({ name, content, optimizer = 0 }: { name: string; content: string; optimizer: number }) {
24 | content = content.replace(/("|')..\//g, `$1${path.dirname(path.dirname(name))}/`).replace(/("|').\//g, `$1${path.dirname(name)}/`);
25 | let readCallback = await solcjsCore.getReadCallback(content, async (filePath: string) => {
26 | const { files } = store.state.editor.fileManager;
27 | const _filePath = path.resolve(path.dirname(name), filePath);
28 | const file = files[filePath] || files[_filePath];
29 |
30 | if (file) {
31 | let _content = file.content.replace(/("|')..\//g, `$1${path.dirname(path.dirname(filePath))}/`).replace(/("|').\//g, `$1${path.dirname(filePath)}/`);
32 |
33 | return _content;
34 | }
35 | if (/(openzeppelin\/|@openzeppelin\/|openzeppelin-solidity\/)/.test(filePath)) {
36 | filePath = filePath.replace(/(openzeppelin\/|@openzeppelin\/|openzeppelin-solidity\/)/, "https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/release-v2.3.0/");
37 | }
38 | const data = await this.resolveEngine.require(filePath);
39 |
40 | return data;
41 | });
42 | const res = this.compiler.compile(content, optimizer, readCallback);
43 | console.log({ res });
44 | const {
45 | contracts: { MyContract: MyContract_contract, ...otherContracts } = { MyContract: {} },
46 | sources: { MyContract: MyContract_source, ...otherSources },
47 | } = res;
48 |
49 | return {
50 | errors: res.errors,
51 | contracts: {
52 | [name]: MyContract_contract,
53 | ...otherContracts,
54 | },
55 | sources: {
56 | [name]: MyContract_source,
57 | ...otherSources,
58 | },
59 | };
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/utils/vm.ts:
--------------------------------------------------------------------------------
1 | import VM from "ethereumjs-vm";
2 | import abi from "ethereumjs-abi";
3 | import { Transaction } from "ethereumjs-tx";
4 | import { promisify } from "util";
5 | import * as util from "ethereumjs-util";
6 | import Account from "ethereumjs-account";
7 | import { Wallet, utils } from "ethers";
8 | import { eventBus } from "./eventBus";
9 | import { truncate } from "./filter";
10 |
11 | export class JSVM {
12 | vm = new VM();
13 |
14 | async generateAccount() {
15 | const account = new Account({ balance: 1e18 * 100 });
16 | const wallet = Wallet.createRandom();
17 |
18 | const privateKey = wallet.privateKey.replace(/^0x/, "");
19 | const privateKeyBuffer = Buffer.from(privateKey, "hex");
20 | const addressBuffer = util.privateToAddress(privateKeyBuffer);
21 |
22 | await this.vm.pStateManager.putAccount(addressBuffer, account);
23 | return {
24 | address: wallet.address.replace(/^0x/, "io").toLowerCase(),
25 | addressBuffer,
26 | privateKey,
27 | privateKeyBuffer,
28 | account,
29 | };
30 | }
31 |
32 | async getAccountNonce(accountPrivateKey: Buffer) {
33 | const account = (await this.vm.pStateManager.getAccount(util.privateToAddress(accountPrivateKey))) as Account;
34 | return account.nonce;
35 | }
36 |
37 | async deplyContract({
38 | senderPrivateKey,
39 | bytecode,
40 | types = [],
41 | datas = [],
42 | gasLimit = 3000000,
43 | gasPrice = 1,
44 | value = 0,
45 | }: {
46 | senderPrivateKey: Buffer;
47 | bytecode: Buffer;
48 | types: string[];
49 | datas: string[];
50 | gasLimit?: number;
51 | gasPrice?: number;
52 | value?: number;
53 | }): Promise {
54 | const params = abi.rawEncode(types, datas);
55 | const nonce = await this.getAccountNonce(senderPrivateKey);
56 | let data = "0x" + bytecode.toString("hex") + params.toString("hex");
57 |
58 | const tx = new Transaction({
59 | value,
60 | gasLimit,
61 | gasPrice,
62 | data,
63 | nonce,
64 | });
65 |
66 | tx.sign(senderPrivateKey);
67 | const deploymentResult = await this.vm.runTx({ tx });
68 | if (deploymentResult.execResult.exceptionError) {
69 | throw deploymentResult.execResult.exceptionError;
70 | }
71 |
72 | return util.bufferToHex(deploymentResult.createdAddress).replace(/^0x/, "io");
73 | }
74 |
75 | async readContract({ contractAddress, callerAddress, types, datas, method }: { method: string; contractAddress: string; callerAddress: string; types?: string[]; datas?: string[] }) {
76 | const params = abi.rawEncode(types, datas);
77 | let data = "0x" + abi.methodID(method, types).toString("hex") + params.toString("hex");
78 |
79 | const _contractAddress = util.toBuffer(contractAddress.replace(/^io/, "0x"));
80 | const _callerAddress = util.toBuffer(callerAddress.replace(/^io/, "0x"));
81 | const result = await this.vm.runCall({
82 | to: _contractAddress,
83 | caller: _callerAddress,
84 | origin: _callerAddress,
85 | data: util.toBuffer(data),
86 | });
87 |
88 | eventBus.emit("term.info", {
89 | text: `call from: ${truncate(callerAddress, 12, "...")},to:${truncate(contractAddress, 12, "...")},data:${truncate(data, 12, "...")} `,
90 | data: {
91 | from: callerAddress,
92 | to: contractAddress,
93 | data,
94 | },
95 | });
96 |
97 | if (result.execResult.exceptionError) {
98 | throw result.execResult.exceptionError;
99 | }
100 | return result;
101 | }
102 |
103 | getData({ types = [], datas = [], method }: { types?: string[]; datas?: string[]; method: string }) {
104 | datas = datas.map((i) => i.replace(/^io/, "0x"));
105 | const params = abi.rawEncode(types, datas);
106 | let data = abi.methodID(method, types).toString("hex") + params.toString("hex");
107 | return data;
108 | }
109 |
110 | async interactContract({
111 | gasLimit = 2000000,
112 | gasPrice = 1,
113 | value = 0,
114 | senderPrivateKey,
115 | contractAddress,
116 | types = [],
117 | datas = [],
118 | method,
119 | }: {
120 | method: string;
121 | senderPrivateKey: Buffer;
122 | contractAddress: string;
123 | types?: string[];
124 | datas?: string[];
125 | gasLimit?: number;
126 | gasPrice?: number;
127 | value?: number;
128 | }) {
129 | const params = abi.rawEncode(types, datas);
130 | const nonce = await this.getAccountNonce(senderPrivateKey);
131 | let data = "0x" + abi.methodID(method, types).toString("hex") + params.toString("hex");
132 | const _contractAddress = util.toBuffer(contractAddress.replace(/^io/, "0x"));
133 |
134 | const tx = new Transaction({
135 | to: _contractAddress,
136 | value,
137 | gasLimit,
138 | gasPrice,
139 | data,
140 | nonce,
141 | });
142 |
143 | tx.sign(senderPrivateKey);
144 | const result = await this.vm.runTx({ tx });
145 | const message = {
146 | senderAddress: `io${tx.getSenderAddress().toString("hex")}`,
147 | contractAddress,
148 | data,
149 | };
150 | eventBus.emit("term.info", {
151 | text: `call from: ${truncate(message.senderAddress, 12, "...")},to:${truncate(message.contractAddress, 12, "...")},data:${truncate(message.data, 12, "...")} `,
152 | data: {
153 | from: message.senderAddress,
154 | to: message.contractAddress,
155 | data: message.data,
156 | },
157 | });
158 |
159 | if (result.execResult.exceptionError) {
160 | throw result.execResult.exceptionError;
161 | }
162 | return result;
163 | }
164 | }
165 |
166 | export const jsvm = new JSVM();
167 |
--------------------------------------------------------------------------------
/src/utils/ws-plugin.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import window from "global/window";
3 | import WebSocket from "websocket-as-promised";
4 | import { Account } from "iotex-antenna/lib/account/account";
5 | import { Envelop } from "iotex-antenna/lib/action/envelop";
6 | import { SignerPlugin } from "iotex-antenna/lib/action/method";
7 | import Options from "websocket-as-promised/types/options";
8 | import { eventBus } from "./eventBus";
9 | import { helper } from "./helper";
10 |
11 | interface IRequest {
12 | reqId?: number;
13 | type: "SIGN_AND_SEND" | "GET_ACCOUNTS";
14 | envelop?: string; // serialized proto string
15 | origin?: string;
16 | }
17 |
18 | export interface WsSignerPluginOptions extends Options {
19 | retryCount?: number;
20 | retryDuration?: number;
21 | }
22 |
23 | export interface WsRequest {
24 | // tslint:disable-next-line: no-any
25 | [key: string]: any;
26 | }
27 |
28 | export class WsSignerPlugin implements SignerPlugin {
29 | private ws: WebSocket;
30 |
31 | private readonly provider: string;
32 |
33 | private readonly options: WsSignerPluginOptions;
34 |
35 | constructor({ provider = "wss://local.iotex.io:64102", options = { retryCount: 3, retryDuration: 50, timeout: 5000 } }: { provider?: string; options: WsSignerPluginOptions }) {
36 | this.provider = provider;
37 |
38 | this.options = options;
39 |
40 | this.init();
41 | }
42 |
43 | private async init() {
44 | this.ws = new WebSocket(this.provider, this.options);
45 | this.ws.onOpen.addListener(() => {
46 | eventBus.emit("term.message", { text: "[iopay-desktop] connected" });
47 | });
48 | this.ws.onClose.addListener = () => {
49 | eventBus.emit("term.warning", { text: "[iopay-desktop] disconnected" });
50 | };
51 | await this.ws.open();
52 | return this;
53 | }
54 |
55 | private async wait() {
56 | while (!this.ws.isOpened) {
57 | await helper.sleep(500);
58 | if (!this.ws.isOpened) await this.init();
59 | }
60 | return Promise.resolve(true);
61 | }
62 |
63 | public async signAndSend(envelop: Envelop): Promise {
64 | await this.wait();
65 | const envelopString = Buffer.from(envelop.bytestream()).toString("hex");
66 |
67 | const req: IRequest = {
68 | envelop: envelopString,
69 | type: "SIGN_AND_SEND",
70 | origin: this.getOrigin(),
71 | };
72 | const res = await this.ws.sendRequest(req);
73 | return res;
74 | }
75 |
76 | public async getAccount(address: string): Promise {
77 | const acct = new Account();
78 | acct.address = address;
79 | return acct;
80 | }
81 |
82 | public async getAccounts(): Promise> {
83 | await this.wait();
84 | const req = {
85 | type: "GET_ACCOUNTS",
86 | };
87 | const res = await this.ws.sendRequest(req);
88 | return res.accounts;
89 | }
90 |
91 | public getOrigin(plugin: string = ""): string {
92 | let origin: string = "";
93 | if (location !== undefined && location.hasOwnProperty("hostname") && location.hostname.length) {
94 | origin = location.hostname;
95 | } else {
96 | origin = plugin;
97 | }
98 |
99 | if (origin.substr(0, 4) === "www.") {
100 | origin = origin.replace("www.", "");
101 | }
102 | console.log(origin);
103 | return origin;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 | .app.flex.flex-col
3 | menubar.menubar.border-b
4 | Split.content.flex-1.flex.mt-1(@onDragEnd="size => setSplitSize('main', size)")
5 | SplitArea(:size.sync="splitSize.main[0]")
6 | file-manager.file-manager.border-r.h-full
7 | SplitArea.flex.flex-col.w-full(:size.sync="splitSize.main[1]")
8 | Split.flex.flex-col.w-full.flex-1(direction="vertical" @onDragEnd="size => setSplitSize('editor', size)")
9 | toolbar.toolbar.border-b
10 | SplitArea(:size="splitSize.editor[0]")
11 | editor.w-full.flex-1(ref="editor")
12 | SplitArea(:size="splitSize.editor[1]")
13 | terminal.border-t
14 | SplitArea.plugin.px-2.border-l.py-2(:size="splitSize.main[2]")
15 | compiler
16 | el-divider.mt-4.mb-2
17 | deployer
18 |
19 |
20 |
21 |
40 |
41 |
61 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2018",
4 | // "module": "esnext",
5 | // "strict": true,
6 | "jsx": "preserve",
7 | "allowJs": true,
8 | "noImplicitAny": false,
9 | "noEmit": true,
10 | "resolveJsonModule": true,
11 | "skipLibCheck": true,
12 | "importHelpers": true,
13 | "moduleResolution": "node",
14 | "esModuleInterop": true,
15 | "allowSyntheticDefaultImports": true,
16 | "emitDecoratorMetadata": true,
17 | "experimentalDecorators": true,
18 | "sourceMap": true,
19 | "baseUrl": ".",
20 | "types": ["node", "webpack-env"],
21 | "paths": {
22 | "@/*": ["src/*"]
23 | },
24 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
25 | },
26 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "tests/**/*.ts", "tests/**/*.tsx"],
27 | "exclude": ["node_modules"]
28 | }
29 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "warning",
3 | "extends": ["tslint-config-prettier"],
4 | "linterOptions": {
5 | "exclude": ["node_modules/**"]
6 | },
7 | "rules": {
8 | "indent": [true, "spaces", 2]
9 | },
10 | "jsRules": {
11 | "no-empty": true
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @typedef { import("@vue/cli-service").ProjectOptions } Options
3 | */
4 |
5 | const { ProvidePlugin } = require("webpack");
6 |
7 | /** @type {Options} */
8 | const options = {
9 | configureWebpack: {
10 | devtool: "source-map",
11 | devServer: {
12 | historyApiFallback: true,
13 | open: false,
14 | host: 'localhost',
15 | port: 8080,
16 | https: false,
17 | proxy: {
18 | "/bin": {
19 | "target": "http://solc-bin.ethereum.org/",
20 | "changeOrigin": true
21 | }
22 | }
23 |
24 | },
25 | resolve: {
26 | alias: {
27 | fs: "browserfs/dist/shims/fs.js",
28 | buffer: "browserfs/dist/shims/buffer.js",
29 | path: "browserfs/dist/shims/path.js",
30 | processGlobal: "browserfs/dist/shims/process.js",
31 | bufferGlobal: "browserfs/dist/shims/bufferGlobal.js",
32 | bfsGlobal: require.resolve("browserfs"),
33 | },
34 | },
35 | module: {
36 | noParse: [/browserfs\.js/],
37 | },
38 | plugins: [new ProvidePlugin({ BrowserFS: "bfsGlobal", process: "processGlobal", Buffer: "bufferGlobal" })],
39 | node: {
40 | process: false,
41 | Buffer: false,
42 | module: false,
43 | },
44 | },
45 | };
46 |
47 | module.exports = options;
48 |
--------------------------------------------------------------------------------