├── .electron-vue ├── build.js ├── dev-client.js ├── dev-runner.js ├── webpack.main.config.js ├── webpack.renderer.config.js └── webpack.web.config.js ├── .gitignore ├── .nvmrc ├── .travis.yml ├── LICENSE ├── README.fr-FR.md ├── README.md ├── README.zh-CN.md ├── appveyor.yml ├── build └── icons │ ├── 128x128.png │ ├── 16x16.png │ ├── 24x24.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 64x64.png │ ├── 96x96.png │ ├── icon.icns │ └── icon.ico ├── hedwig ├── README.md ├── client.js ├── package.json └── test.js ├── package.json ├── resources └── bin │ ├── linux │ ├── README.md │ ├── grin │ └── grin-wallet │ ├── mac │ ├── README.md │ ├── grin │ └── grin-wallet │ └── win │ ├── README.md │ ├── grin-wallet.exe │ └── grin.exe ├── src ├── index.ejs ├── lang │ ├── en.js │ ├── fr.js │ ├── index.js │ ├── ko.js │ ├── nl.js │ ├── ru.js │ ├── tr.js │ └── zh.js ├── main │ ├── index.dev.js │ └── index.js ├── renderer │ ├── App.vue │ ├── assets │ │ ├── .gitkeep │ │ ├── imgs │ │ │ ├── arrow-alt-circle-left.svg │ │ │ └── arrow-alt-circle-right.svg │ │ └── logo.png │ ├── components │ │ ├── Check.vue │ │ ├── Commit.vue │ │ ├── Create.vue │ │ ├── FileSend.vue │ │ ├── Finalize.vue │ │ ├── Gaddress.vue │ │ ├── Gnode.vue │ │ ├── GnodeConfig.vue │ │ ├── GnodeConfigModal.vue │ │ ├── HedwigV1.vue │ │ ├── HttpReceive.vue │ │ ├── HttpSend.vue │ │ ├── Landing.vue │ │ ├── Lang.vue │ │ ├── Login.vue │ │ ├── Message.vue │ │ ├── New.vue │ │ ├── Receive.vue │ │ ├── Remove.vue │ │ ├── Restore.vue │ │ ├── SummaryInfo.vue │ │ ├── Tor.vue │ │ └── Transaction.vue │ ├── db.js │ ├── main.js │ ├── messagebus.js │ └── mixin.js └── shared │ ├── config.js │ ├── first.js │ ├── gnode.js │ ├── logger.js │ ├── tor.js │ ├── updateChecker.js │ ├── utils.js │ ├── wallet.js │ └── walletv3.js └── static ├── .gitkeep ├── css ├── animate.css ├── bulma.min.css ├── bulmaswatch.min.css └── font-awesome.min.css ├── grin_mw_logo.png └── grin_mw_logo.svg /.electron-vue/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | const { say } = require('cfonts') 6 | const chalk = require('chalk') 7 | const del = require('del') 8 | const { spawn } = require('child_process') 9 | const webpack = require('webpack') 10 | const Multispinner = require('multispinner') 11 | 12 | 13 | const mainConfig = require('./webpack.main.config') 14 | const rendererConfig = require('./webpack.renderer.config') 15 | const webConfig = require('./webpack.web.config') 16 | 17 | const doneLog = chalk.bgGreen.white(' DONE ') + ' ' 18 | const errorLog = chalk.bgRed.white(' ERROR ') + ' ' 19 | const okayLog = chalk.bgBlue.white(' OKAY ') + ' ' 20 | const isCI = process.env.CI || false 21 | 22 | if (process.env.BUILD_TARGET === 'clean') clean() 23 | else if (process.env.BUILD_TARGET === 'web') web() 24 | else build() 25 | 26 | function clean () { 27 | del.sync(['build/*', '!build/icons', '!build/icons/icon.*']) 28 | console.log(`\n${doneLog}\n`) 29 | process.exit() 30 | } 31 | 32 | function build () { 33 | greeting() 34 | 35 | del.sync(['dist/electron/*', '!.gitkeep']) 36 | 37 | const tasks = ['main', 'renderer'] 38 | const m = new Multispinner(tasks, { 39 | preText: 'building', 40 | postText: 'process' 41 | }) 42 | 43 | let results = '' 44 | 45 | m.on('success', () => { 46 | process.stdout.write('\x1B[2J\x1B[0f') 47 | console.log(`\n\n${results}`) 48 | console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`) 49 | process.exit() 50 | }) 51 | 52 | pack(mainConfig).then(result => { 53 | results += result + '\n\n' 54 | m.success('main') 55 | }).catch(err => { 56 | m.error('main') 57 | console.log(`\n ${errorLog}failed to build main process`) 58 | console.error(`\n${err}\n`) 59 | process.exit(1) 60 | }) 61 | 62 | pack(rendererConfig).then(result => { 63 | results += result + '\n\n' 64 | m.success('renderer') 65 | }).catch(err => { 66 | m.error('renderer') 67 | console.log(`\n ${errorLog}failed to build renderer process`) 68 | console.error(`\n${err}\n`) 69 | process.exit(1) 70 | }) 71 | } 72 | 73 | function pack (config) { 74 | return new Promise((resolve, reject) => { 75 | config.mode = 'production' 76 | webpack(config, (err, stats) => { 77 | if (err) reject(err.stack || err) 78 | else if (stats.hasErrors()) { 79 | let err = '' 80 | 81 | stats.toString({ 82 | chunks: false, 83 | colors: true 84 | }) 85 | .split(/\r?\n/) 86 | .forEach(line => { 87 | err += ` ${line}\n` 88 | }) 89 | 90 | reject(err) 91 | } else { 92 | resolve(stats.toString({ 93 | chunks: false, 94 | colors: true 95 | })) 96 | } 97 | }) 98 | }) 99 | } 100 | 101 | function web () { 102 | del.sync(['dist/web/*', '!.gitkeep']) 103 | webConfig.mode = 'production' 104 | webpack(webConfig, (err, stats) => { 105 | if (err || stats.hasErrors()) console.log(err) 106 | 107 | console.log(stats.toString({ 108 | chunks: false, 109 | colors: true 110 | })) 111 | 112 | process.exit() 113 | }) 114 | } 115 | 116 | function greeting () { 117 | const cols = process.stdout.columns 118 | let text = '' 119 | 120 | if (cols > 85) text = 'lets-build' 121 | else if (cols > 60) text = 'lets-|build' 122 | else text = false 123 | 124 | if (text && !isCI) { 125 | say(text, { 126 | colors: ['yellow'], 127 | font: 'simple3d', 128 | space: false 129 | }) 130 | } else console.log(chalk.yellow.bold('\n lets-build')) 131 | console.log() 132 | } -------------------------------------------------------------------------------- /.electron-vue/dev-client.js: -------------------------------------------------------------------------------- 1 | const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 2 | 3 | hotClient.subscribe(event => { 4 | /** 5 | * Reload browser when HTMLWebpackPlugin emits a new index.html 6 | * 7 | * Currently disabled until jantimon/html-webpack-plugin#680 is resolved. 8 | * https://github.com/SimulatedGREG/electron-vue/issues/437 9 | * https://github.com/jantimon/html-webpack-plugin/issues/680 10 | */ 11 | // if (event.action === 'reload') { 12 | // window.location.reload() 13 | // } 14 | 15 | /** 16 | * Notify `mainWindow` when `main` process is compiling, 17 | * giving notice for an expected reload of the `electron` process 18 | */ 19 | if (event.action === 'compiling') { 20 | document.body.innerHTML += ` 21 | 34 | 35 |
36 | Compiling Main Process... 37 |
38 | ` 39 | } 40 | }) 41 | -------------------------------------------------------------------------------- /.electron-vue/dev-runner.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const chalk = require('chalk') 4 | const electron = require('electron') 5 | const path = require('path') 6 | const { say } = require('cfonts') 7 | const { spawn } = require('child_process') 8 | const webpack = require('webpack') 9 | const WebpackDevServer = require('webpack-dev-server') 10 | const webpackHotMiddleware = require('webpack-hot-middleware') 11 | 12 | const mainConfig = require('./webpack.main.config') 13 | const rendererConfig = require('./webpack.renderer.config') 14 | 15 | let electronProcess = null 16 | let manualRestart = false 17 | let hotMiddleware 18 | 19 | function logStats (proc, data) { 20 | let log = '' 21 | 22 | log += chalk.yellow.bold(`┏ ${proc} Process ${new Array((19 - proc.length) + 1).join('-')}`) 23 | log += '\n\n' 24 | 25 | if (typeof data === 'object') { 26 | data.toString({ 27 | colors: true, 28 | chunks: false 29 | }).split(/\r?\n/).forEach(line => { 30 | log += ' ' + line + '\n' 31 | }) 32 | } else { 33 | log += ` ${data}\n` 34 | } 35 | 36 | log += '\n' + chalk.yellow.bold(`┗ ${new Array(28 + 1).join('-')}`) + '\n' 37 | 38 | console.log(log) 39 | } 40 | 41 | function startRenderer () { 42 | return new Promise((resolve, reject) => { 43 | rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer) 44 | rendererConfig.mode = 'development' 45 | const compiler = webpack(rendererConfig) 46 | hotMiddleware = webpackHotMiddleware(compiler, { 47 | log: false, 48 | heartbeat: 2500 49 | }) 50 | 51 | compiler.hooks.compilation.tap('compilation', compilation => { 52 | compilation.hooks.htmlWebpackPluginAfterEmit.tapAsync('html-webpack-plugin-after-emit', (data, cb) => { 53 | hotMiddleware.publish({ action: 'reload' }) 54 | cb() 55 | }) 56 | }) 57 | 58 | compiler.hooks.done.tap('done', stats => { 59 | logStats('Renderer', stats) 60 | }) 61 | 62 | const server = new WebpackDevServer( 63 | compiler, 64 | { 65 | contentBase: path.join(__dirname, '../'), 66 | quiet: true, 67 | before (app, ctx) { 68 | app.use(hotMiddleware) 69 | ctx.middleware.waitUntilValid(() => { 70 | resolve() 71 | }) 72 | } 73 | } 74 | ) 75 | 76 | server.listen(9080) 77 | }) 78 | } 79 | 80 | function startMain () { 81 | return new Promise((resolve, reject) => { 82 | mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main) 83 | mainConfig.mode = 'development' 84 | const compiler = webpack(mainConfig) 85 | 86 | compiler.hooks.watchRun.tapAsync('watch-run', (compilation, done) => { 87 | logStats('Main', chalk.white.bold('compiling...')) 88 | hotMiddleware.publish({ action: 'compiling' }) 89 | done() 90 | }) 91 | 92 | compiler.watch({}, (err, stats) => { 93 | if (err) { 94 | console.log(err) 95 | return 96 | } 97 | 98 | logStats('Main', stats) 99 | 100 | if (electronProcess && electronProcess.kill) { 101 | manualRestart = true 102 | process.kill(electronProcess.pid) 103 | electronProcess = null 104 | startElectron() 105 | 106 | setTimeout(() => { 107 | manualRestart = false 108 | }, 5000) 109 | } 110 | 111 | resolve() 112 | }) 113 | }) 114 | } 115 | 116 | function startElectron () { 117 | var args = [ 118 | '--inspect=5858', 119 | path.join(__dirname, '../dist/electron/main.js') 120 | ] 121 | 122 | // detect yarn or npm and process commandline args accordingly 123 | if (process.env.npm_execpath.endsWith('yarn.js')) { 124 | args = args.concat(process.argv.slice(3)) 125 | } else if (process.env.npm_execpath.endsWith('npm-cli.js')) { 126 | args = args.concat(process.argv.slice(2)) 127 | } 128 | 129 | electronProcess = spawn(electron, args) 130 | 131 | electronProcess.stdout.on('data', data => { 132 | electronLog(data, 'blue') 133 | }) 134 | electronProcess.stderr.on('data', data => { 135 | electronLog(data, 'red') 136 | }) 137 | 138 | electronProcess.on('close', () => { 139 | if (!manualRestart) process.exit() 140 | }) 141 | } 142 | 143 | function electronLog (data, color) { 144 | let log = '' 145 | data = data.toString().split(/\r?\n/) 146 | data.forEach(line => { 147 | log += ` ${line}\n` 148 | }) 149 | if (/[0-9A-z]+/.test(log)) { 150 | console.log( 151 | chalk[color].bold('┏ Electron -------------------') + 152 | '\n\n' + 153 | log + 154 | chalk[color].bold('┗ ----------------------------') + 155 | '\n' 156 | ) 157 | } 158 | } 159 | 160 | function greeting () { 161 | const cols = process.stdout.columns 162 | let text = '' 163 | 164 | if (cols > 104) text = 'electron-vue' 165 | else if (cols > 76) text = 'electron-|vue' 166 | else text = false 167 | 168 | if (text) { 169 | say(text, { 170 | colors: ['yellow'], 171 | font: 'simple3d', 172 | space: false 173 | }) 174 | } else console.log(chalk.yellow.bold('\n electron-vue')) 175 | console.log(chalk.blue(' getting ready...') + '\n') 176 | } 177 | 178 | function init () { 179 | greeting() 180 | 181 | Promise.all([startRenderer(), startMain()]) 182 | .then(() => { 183 | startElectron() 184 | }) 185 | .catch(err => { 186 | console.error(err) 187 | }) 188 | } 189 | 190 | init() 191 | -------------------------------------------------------------------------------- /.electron-vue/webpack.main.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.BABEL_ENV = 'main' 4 | 5 | const path = require('path') 6 | const { dependencies } = require('../package.json') 7 | const webpack = require('webpack') 8 | 9 | const BabiliWebpackPlugin = require('babili-webpack-plugin') 10 | 11 | let mainConfig = { 12 | entry: { 13 | main: path.join(__dirname, '../src/main/index.js') 14 | }, 15 | externals: [ 16 | ...Object.keys(dependencies || {}) 17 | ], 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.js$/, 22 | use: 'babel-loader', 23 | exclude: /node_modules/ 24 | }, 25 | { 26 | test: /\.node$/, 27 | use: 'node-loader' 28 | } 29 | ] 30 | }, 31 | node: { 32 | __dirname: process.env.NODE_ENV !== 'production', 33 | __filename: process.env.NODE_ENV !== 'production' 34 | }, 35 | output: { 36 | filename: '[name].js', 37 | libraryTarget: 'commonjs2', 38 | path: path.join(__dirname, '../dist/electron') 39 | }, 40 | plugins: [ 41 | new webpack.NoEmitOnErrorsPlugin() 42 | ], 43 | resolve: { 44 | extensions: ['.js', '.json', '.node'] 45 | }, 46 | target: 'electron-main' 47 | } 48 | 49 | /** 50 | * Adjust mainConfig for development settings 51 | */ 52 | if (process.env.NODE_ENV !== 'production') { 53 | mainConfig.plugins.push( 54 | new webpack.DefinePlugin({ 55 | '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"` 56 | }) 57 | ) 58 | } 59 | 60 | /** 61 | * Adjust mainConfig for production settings 62 | */ 63 | if (process.env.NODE_ENV === 'production') { 64 | mainConfig.plugins.push( 65 | new BabiliWebpackPlugin(), 66 | new webpack.DefinePlugin({ 67 | 'process.env.NODE_ENV': '"production"' 68 | }) 69 | ) 70 | } 71 | 72 | module.exports = mainConfig 73 | -------------------------------------------------------------------------------- /.electron-vue/webpack.renderer.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.BABEL_ENV = 'renderer' 4 | 5 | const path = require('path') 6 | const { dependencies } = require('../package.json') 7 | const webpack = require('webpack') 8 | 9 | const BabiliWebpackPlugin = require('babili-webpack-plugin') 10 | const CopyWebpackPlugin = require('copy-webpack-plugin') 11 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 12 | const HtmlWebpackPlugin = require('html-webpack-plugin') 13 | const { VueLoaderPlugin } = require('vue-loader') 14 | 15 | /** 16 | * List of node_modules to include in webpack bundle 17 | * 18 | * Required for specific packages like Vue UI libraries 19 | * that provide pure *.vue files that need compiling 20 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals 21 | */ 22 | let whiteListedModules = ['vue'] 23 | 24 | let rendererConfig = { 25 | devtool: '#cheap-module-eval-source-map', 26 | entry: { 27 | renderer: path.join(__dirname, '../src/renderer/main.js') 28 | }, 29 | externals: [ 30 | ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d)) 31 | ], 32 | module: { 33 | rules: [ 34 | { 35 | test: /\.scss$/, 36 | use: ['vue-style-loader', 'css-loader', 'sass-loader'] 37 | }, 38 | { 39 | test: /\.sass$/, 40 | use: ['vue-style-loader', 'css-loader', 'sass-loader?indentedSyntax'] 41 | }, 42 | { 43 | test: /\.less$/, 44 | use: ['vue-style-loader', 'css-loader', 'less-loader'] 45 | }, 46 | { 47 | test: /\.css$/, 48 | use: ['vue-style-loader', 'css-loader'] 49 | }, 50 | { 51 | test: /\.html$/, 52 | use: 'vue-html-loader' 53 | }, 54 | { 55 | test: /\.js$/, 56 | use: 'babel-loader', 57 | exclude: /node_modules/ 58 | }, 59 | { 60 | test: /\.node$/, 61 | use: 'node-loader' 62 | }, 63 | { 64 | test: /\.vue$/, 65 | use: { 66 | loader: 'vue-loader', 67 | options: { 68 | extractCSS: process.env.NODE_ENV === 'production', 69 | loaders: { 70 | sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1', 71 | scss: 'vue-style-loader!css-loader!sass-loader', 72 | less: 'vue-style-loader!css-loader!less-loader' 73 | } 74 | } 75 | } 76 | }, 77 | { 78 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 79 | use: { 80 | loader: 'url-loader', 81 | query: { 82 | limit: 10000, 83 | name: 'imgs/[name]--[folder].[ext]' 84 | } 85 | } 86 | }, 87 | { 88 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 89 | loader: 'url-loader', 90 | options: { 91 | limit: 10000, 92 | name: 'media/[name]--[folder].[ext]' 93 | } 94 | }, 95 | { 96 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 97 | use: { 98 | loader: 'url-loader', 99 | query: { 100 | limit: 10000, 101 | name: 'fonts/[name]--[folder].[ext]' 102 | } 103 | } 104 | } 105 | ] 106 | }, 107 | node: { 108 | __dirname: process.env.NODE_ENV !== 'production', 109 | __filename: process.env.NODE_ENV !== 'production' 110 | }, 111 | plugins: [ 112 | new VueLoaderPlugin(), 113 | new MiniCssExtractPlugin({filename: 'styles.css'}), 114 | new HtmlWebpackPlugin({ 115 | filename: 'index.html', 116 | template: path.resolve(__dirname, '../src/index.ejs'), 117 | minify: { 118 | collapseWhitespace: true, 119 | removeAttributeQuotes: true, 120 | removeComments: true 121 | }, 122 | nodeModules: process.env.NODE_ENV !== 'production' 123 | ? path.resolve(__dirname, '../node_modules') 124 | : false 125 | }), 126 | new webpack.HotModuleReplacementPlugin(), 127 | new webpack.NoEmitOnErrorsPlugin() 128 | ], 129 | output: { 130 | filename: '[name].js', 131 | libraryTarget: 'commonjs2', 132 | path: path.join(__dirname, '../dist/electron') 133 | }, 134 | resolve: { 135 | alias: { 136 | '@': path.join(__dirname, '../src/renderer'), 137 | 'vue$': 'vue/dist/vue.esm.js' 138 | }, 139 | extensions: ['.js', '.vue', '.json', '.css', '.node'] 140 | }, 141 | target: 'electron-renderer' 142 | } 143 | 144 | /** 145 | * Adjust rendererConfig for development settings 146 | */ 147 | if (process.env.NODE_ENV !== 'production') { 148 | rendererConfig.plugins.push( 149 | new webpack.DefinePlugin({ 150 | '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"` 151 | }) 152 | ) 153 | } 154 | 155 | /** 156 | * Adjust rendererConfig for production settings 157 | */ 158 | if (process.env.NODE_ENV === 'production') { 159 | rendererConfig.devtool = '' 160 | 161 | rendererConfig.plugins.push( 162 | new BabiliWebpackPlugin(), 163 | new CopyWebpackPlugin([ 164 | { 165 | from: path.join(__dirname, '../static'), 166 | to: path.join(__dirname, '../dist/electron/static'), 167 | ignore: ['.*'] 168 | } 169 | ]), 170 | new webpack.DefinePlugin({ 171 | 'process.env.NODE_ENV': '"production"' 172 | }), 173 | new webpack.LoaderOptionsPlugin({ 174 | minimize: true 175 | }) 176 | ) 177 | } 178 | 179 | module.exports = rendererConfig 180 | -------------------------------------------------------------------------------- /.electron-vue/webpack.web.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | process.env.BABEL_ENV = 'web' 4 | 5 | const path = require('path') 6 | const webpack = require('webpack') 7 | 8 | const BabiliWebpackPlugin = require('babili-webpack-plugin') 9 | const CopyWebpackPlugin = require('copy-webpack-plugin') 10 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 11 | const HtmlWebpackPlugin = require('html-webpack-plugin') 12 | const { VueLoaderPlugin } = require('vue-loader') 13 | 14 | let webConfig = { 15 | devtool: '#cheap-module-eval-source-map', 16 | entry: { 17 | web: path.join(__dirname, '../src/renderer/main.js') 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.scss$/, 23 | use: ['vue-style-loader', 'css-loader', 'sass-loader'] 24 | }, 25 | { 26 | test: /\.sass$/, 27 | use: ['vue-style-loader', 'css-loader', 'sass-loader?indentedSyntax'] 28 | }, 29 | { 30 | test: /\.less$/, 31 | use: ['vue-style-loader', 'css-loader', 'less-loader'] 32 | }, 33 | { 34 | test: /\.css$/, 35 | use: ['vue-style-loader', 'css-loader'] 36 | }, 37 | { 38 | test: /\.html$/, 39 | use: 'vue-html-loader' 40 | }, 41 | { 42 | test: /\.js$/, 43 | use: 'babel-loader', 44 | include: [ path.resolve(__dirname, '../src/renderer') ], 45 | exclude: /node_modules/ 46 | }, 47 | { 48 | test: /\.vue$/, 49 | use: { 50 | loader: 'vue-loader', 51 | options: { 52 | extractCSS: true, 53 | loaders: { 54 | sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1', 55 | scss: 'vue-style-loader!css-loader!sass-loader', 56 | less: 'vue-style-loader!css-loader!less-loader' 57 | } 58 | } 59 | } 60 | }, 61 | { 62 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 63 | use: { 64 | loader: 'url-loader', 65 | query: { 66 | limit: 10000, 67 | name: 'imgs/[name].[ext]' 68 | } 69 | } 70 | }, 71 | { 72 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 73 | use: { 74 | loader: 'url-loader', 75 | query: { 76 | limit: 10000, 77 | name: 'fonts/[name].[ext]' 78 | } 79 | } 80 | } 81 | ] 82 | }, 83 | plugins: [ 84 | new VueLoaderPlugin(), 85 | new MiniCssExtractPlugin({filename: 'styles.css'}), 86 | new HtmlWebpackPlugin({ 87 | filename: 'index.html', 88 | template: path.resolve(__dirname, '../src/index.ejs'), 89 | minify: { 90 | collapseWhitespace: true, 91 | removeAttributeQuotes: true, 92 | removeComments: true 93 | }, 94 | nodeModules: false 95 | }), 96 | new webpack.DefinePlugin({ 97 | 'process.env.IS_WEB': 'true' 98 | }), 99 | new webpack.HotModuleReplacementPlugin(), 100 | new webpack.NoEmitOnErrorsPlugin() 101 | ], 102 | output: { 103 | filename: '[name].js', 104 | path: path.join(__dirname, '../dist/web') 105 | }, 106 | resolve: { 107 | alias: { 108 | '@': path.join(__dirname, '../src/renderer'), 109 | 'vue$': 'vue/dist/vue.esm.js' 110 | }, 111 | extensions: ['.js', '.vue', '.json', '.css'] 112 | }, 113 | target: 'web' 114 | } 115 | 116 | /** 117 | * Adjust webConfig for production settings 118 | */ 119 | if (process.env.NODE_ENV === 'production') { 120 | webConfig.devtool = '' 121 | 122 | webConfig.plugins.push( 123 | new BabiliWebpackPlugin(), 124 | new CopyWebpackPlugin([ 125 | { 126 | from: path.join(__dirname, '../static'), 127 | to: path.join(__dirname, '../dist/web/static'), 128 | ignore: ['.*'] 129 | } 130 | ]), 131 | new webpack.DefinePlugin({ 132 | 'process.env.NODE_ENV': '"production"' 133 | }), 134 | new webpack.LoaderOptionsPlugin({ 135 | minimize: true 136 | }) 137 | ) 138 | } 139 | 140 | module.exports = webConfig 141 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist/electron/* 3 | dist/web/* 4 | build/* 5 | !build/icons 6 | !build/installer.nsh 7 | coverage 8 | node_modules/ 9 | npm-debug.log 10 | npm-debug.log.* 11 | thumbs.db 12 | !.gitkeep 13 | yarn-error.log 14 | docs/dist/ 15 | .vscode/* 16 | .yarnrc 17 | index.node 18 | target 19 | artifacts.json 20 | Cargo.lock 21 | package-lock.json -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 11.15.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode8.3 2 | sudo: required 3 | dist: trusty 4 | language: c 5 | matrix: 6 | include: 7 | - os: osx 8 | - os: linux 9 | env: CC=clang CXX=clang++ npm_config_clang=1 10 | compiler: clang 11 | cache: 12 | directories: 13 | - node_modules 14 | - "$HOME/.electron" 15 | - "$HOME/.cache" 16 | addons: 17 | apt: 18 | packages: 19 | - libgnome-keyring-dev 20 | - icnsutils 21 | before_install: 22 | - mkdir -p /tmp/git-lfs && curl -L https://github.com/github/git-lfs/releases/download/v1.2.1/git-lfs-$([ 23 | "$TRAVIS_OS_NAME" == "linux" ] && echo "linux" || echo "darwin")-amd64-1.2.1.tar.gz 24 | | tar -xz -C /tmp/git-lfs --strip-components 1 && /tmp/git-lfs/git-lfs pull 25 | - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get install --no-install-recommends -y icnsutils graphicsmagick xz-utils; fi 26 | install: 27 | - nvm install 7 28 | - curl -o- -L https://yarnpkg.com/install.sh | bash 29 | - source ~/.bashrc 30 | - npm install -g xvfb-maybe 31 | - yarn 32 | script: 33 | - yarn run build 34 | branches: 35 | only: 36 | - master 37 | -------------------------------------------------------------------------------- /README.fr-FR.md: -------------------------------------------------------------------------------- 1 | # Niffler Wallet 2 | 3 | Français | [English](./README.md) | [简体中文](./README.zh-CN.md) 4 | 5 | Niffler est une interface graphique de portefeuille [Grin](https://github.com/mimblewimble/grin) prête à l'emploi et conviviale. 6 | Le nom 'Niffler' vient de ["harry potter"](https://harrypotter.fandom.com/wiki/Niffler). 7 | 8 | ### Dernière Version: 0.7.1 9 | 10 | v0.7.1 11 | 12 | Il utilise la version officielle de [grin-wallet v5.0.3](https://github.com/mimblewimble/grin-wallet/releases/tag/v5.0.3) avec [grin v5.0.4](https://github.com/mimblewimble/grin/releases/tag/v5.0.4) pour le backend. 13 | 14 | Mac/Windows/Linux version:[https://github.com/grinfans/niffler/releases/tag/v0.7.1](https://github.com/grinfans/niffler/releases/tag/v0.7.1) 15 | 16 | Le portefeuille Niffler dispose de **hedwig v1 relay service**, qui permet à un utilisateur sans IP publique de pouvoir recevoir des Grin très facilement :) 17 | 18 | Autrement dit,**Vous pouvez prélever des Grin depuis n'importe quel échange ou pool de mineurs sans effort**. 19 | 20 | [Un excellent didacticiel vidéo expliquant comment prélever des Grin depuis n'importe quel échange à l'aide du portefeuille Niffler.](https://www.youtube.com/watch?v=rufKWEv64o8) 21 | [![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/rufKWEv64o8/0.jpg)](https://www.youtube.com/watch?v=rufKWEv64o8) 22 | 23 | ## Contact 24 | 25 | Contactez moi (en anglais) à l'adresse: xiaojay@gmail.com 26 | 27 | Group Telegram : https://t.me/nifflerwallet 28 | 29 | 30 | Logo conçu par [@Duoasa](https://weibo.com/u/3197271025) 31 | 32 | ## Fonctionnalités 33 | 34 | * Utilisation des [binaires Grin officiels](https://github.com/mimblewimble/grin/releases) pour le backend, et [elctron-vue](https://github.com/SimulatedGREG/electron-vue) pour l'IHM. 35 | 36 | * Fonctionne sur mac/linux/windows, et supporte différents langages (actuellement Français, English et 简体中文) 37 | 38 | ## Capture d'écran 39 | 40 | #### Création d'un nouveau portefeuille 41 | 42 | ![Créer un nouveau portefeuille](https://media.giphy.com/media/IeuEOtJvxCLqqiCCyr/giphy.gif) 43 | 44 | #### Envoi de Grin 45 | 46 | 47 | ![Envoi de Grin](https://media.giphy.com/media/LO2sAR3HmocCdbTwEh/giphy.gif) 48 | 49 | #### Réception de Grin 50 | ![Réception de Grin](https://media.giphy.com/media/iFbSw9rhh5fGVSzyZf/giphy.gif) 51 | 52 | 53 | ## Installation 54 | 55 | *La version actuelle est compilée à partir de la branche [gw5](https://github.com/grinfans/Niffler/tree/gw5)* 56 | 57 | ``` bash 58 | # clonage 59 | git clone https://github.com/grinfans/niffler.git && cd niffler 60 | 61 | # installation des dépendances 62 | npm install 63 | cd hedwig 64 | npm install 65 | 66 | # lancement du serveur avec rechargement à chaud sur localhost:9080 67 | npm run dev 68 | 69 | # compilation de l'application electron pour la production 70 | npm run build 71 | ``` 72 | 73 | --- 74 | 75 | ## Demande d'aide 76 | 77 | Les "pull request" de code sont toujours les bienvenus. 78 | 79 | ~~Plus précisément, si un concepteur de la communauté pouvait faire un **logo** pour Niffler, il serait très apprécié :-)~~ 80 | 81 | Nous avons actuellement un magnifique logo de la communauté grin chinoise. 82 | 83 | Logo conçu par [@Duoasa](https://weibo.com/u/3197271025), avec des remerciements également pour [@机械师区块链](https://weibo.com/u/6318956004) 84 | 85 | La traduction dans d'autres langues est également convoitée. Pour cela récupérez le [dossier lang](https://github.com/grinfans/niffler/tree/master/src/lang). 86 | 87 | Merci à [@yozx](https://github.com/yozx) pour la version russe. 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Niffler Wallet 2 | 3 | English | [简体中文](./README.zh-CN.md) | [Français](./README.fr-FR.md) 4 | 5 | Niffler is a out-of-the box user-friendly gui [Grin](https://github.com/mimblewimble/grin) wallet. 6 | The name 'Niffler' comes from ["harry potter"](https://harrypotter.fandom.com/wiki/Niffler). 7 | 8 | * It's using the [official Grin binaries](https://github.com/mimblewimble/grin/releases) as back-end. 9 | 10 | * It's using [electron-vue](https://github.com/SimulatedGREG/electron-vue) to build the Niffler Wallet gui front-end. 11 | 12 | * It works on mac/linux/windows, and supports multiple languages. 13 | 14 | Logo made by [@Duoasa](https://weibo.com/u/3197271025) 15 | 16 | ## Table of Contents 17 | - [Development resources](#development-resources) 18 | - [Vulnerability response](#vulnerability-response) 19 | - [License](#license) 20 | - [Translations](#translations) 21 | - [Contributing](#contributing) 22 | - [Latest Version](#latest-version) 23 | - [Video tutorial](#video-tutorial) 24 | - [Usage](#usage) 25 | - [Compiling from source](#compiling-from-source) 26 | - [Dependencies](#dependencies) 27 | - [Build instructions](#build-instructions) 28 | - [Credits](#credits) 29 | 30 | ## Development resources 31 | - GitHub: [https://github.com/grinfans/Niffler](https://github.com/grinfans/Niffler) 32 | - Mail: [xiaojay@gmail.com](mailto:xiaojay@gmail.com) 33 | - Telegram: https://t.me/nifflerwallet 34 | 35 | ## Vulnerability response 36 | - [GitHub Niffler issues](https://github.com/grinfans/Niffler/issues) 37 | 38 | ## License 39 | 40 | Apache-2.0 License, see [LICENSE](LICENSE). 41 | 42 | ## Translations 43 | The Niffler wallet is available in different languages. 44 | English, 简体中文, French, Russian, Dutch. 45 | 46 | Translation to other languages is welcome. Please check our [lang folder](https://github.com/grinfans/Niffler/tree/gw5/src/lang). 47 | If you want to help translate it, then translate the next text file [en.js](https://github.com/grinfans/Niffler/blob/gw5/src/lang/en.js) to your local language. 48 | Or better, send a github pull request similar like [this](https://github.com/grinfans/Niffler/pull/66/files). 49 | 50 | ## Contributing 51 | 52 | Code pull requests are always welcome. If you want to help out, please send [pull requests](https://github.com/grinfans/Niffler/pulls). 53 | 54 | 55 | ### Latest Version 56 | 57 | v0.7.1 support Grin5 58 | 59 | Niffler Wallet uses the official [grin-wallet v5.0.3](https://github.com/mimblewimble/grin-wallet/releases/tag/v5.0.3) and [grin v5.0.4](https://github.com/mimblewimble/grin/releases/tag/v5.0.4) as backend. 60 | 61 | Mac/Windows/Linux version:[https://github.com/grinfans/niffler/releases/tag/v0.7.1](https://github.com/grinfans/niffler/releases/tag/v0.7.1) 62 | 63 | Niffler wallet has a **hedwig v1 relay service**, which enable users without public ip receive grin really easy :) 64 | 65 | In other words,**You could withdraw grin from any exchanges and miner pool effortlessly**. 66 | 67 | 68 | ## Video tutorial 69 | 70 | [A great video tutorial of how to withdraw grin from any exchanges using Niffler wallet.](https://www.youtube.com/watch?v=rufKWEv64o8) 71 | [![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/rufKWEv64o8/0.jpg)](https://www.youtube.com/watch?v=rufKWEv64o8) 72 | 73 | 74 | 75 | ## Usage 76 | 77 | #### Create new wallet 78 | ![create new wallet](https://media.giphy.com/media/IeuEOtJvxCLqqiCCyr/giphy.gif) 79 | 80 | Important! Be sure to backup your recovery seed phrase to restore your wallet later. 81 | 82 | #### Send grin 83 | ![send grin](https://media.giphy.com/media/LO2sAR3HmocCdbTwEh/giphy.gif) 84 | 85 | #### Receive grin 86 | ![receive grin](https://media.giphy.com/media/iFbSw9rhh5fGVSzyZf/giphy.gif) 87 | 88 | 89 | 90 | ## Compiling from source 91 | 92 | ### Dependencies 93 | The following table summarizes the tools required to build. A few tools are included in this repository (marked as "Vendored"). 94 | 95 | | Dep | Min. version | Vendored | Debian/Ubuntu pkg | name | Optional | Purpose | 96 | | ------------ | ------------- | -------- | -------------------- | -------------------------------------| -------- | ---------------------- | 97 | | npm | ? | NO | `npm` | javascript package manager | NO | compiler | 98 | | grin | current | YES | | grin node executable | NO | grin node back-end | 99 | | grin-wallet | current | YES | | grin-wallet executable | NO | grin-wallet back-end | 100 | | hedwig | 1.0.0 | YES | | public IP address relay service | NO | https relay service | 101 | 102 | 103 | *The current release is built on branch [gw5](https://github.com/grinfans/Niffler/tree/gw5)* 104 | 105 | 106 | ### Build instructions 107 | 108 | Niffler Wallet uses the [npm build system](https://github.com/npm). 109 | 110 | #### Linux 111 | Install build dependencies on Debian/Ubuntu: 112 | 113 | ```bash 114 | $ sudo apt update && sudo apt install npm 115 | ``` 116 | 117 | 118 | Cloning the repository 119 | ```bash 120 | $ git clone https://github.com/grinfans/niffler.git && cd niffler 121 | ``` 122 | 123 | 124 | download and install dependencies with npm in the repository 125 | 126 | ```bash 127 | npm install 128 | cd hedwig 129 | npm install 130 | cd .. 131 | ``` 132 | 133 | Serve with hot reload at localhost:9080 134 | ```bash 135 | npm run dev 136 | ``` 137 | 138 | Build electron application for production 139 | ```bash 140 | npm run build 141 | ``` 142 | 143 | #### Installation directories 144 | 145 | ##### Linux 146 | /opt/Niffler 147 | 148 | ~/.niffler 149 | 150 | ~/.grin 151 | 152 | ##### Windows 153 | c:\Program Files\Niffler 154 | 155 | c:\user\username\\.niffler 156 | 157 | c:\user\username\\.grin 158 | 159 | 160 | 161 | 162 | ## Credits 163 | 164 | We now have a wonderful logo from chinese grin community. 165 | 166 | Logo made by [@Duoasa](https://weibo.com/u/3197271025), and also thanks [@机械师区块链](https://weibo.com/u/6318956004) 167 | 168 | Thanks to [@yozx](https://github.com/yozx) for Russian version. 169 | 170 | Thanks to [@Kinaou](https://github.com/Kinaou) for French version. 171 | 172 | Thanks to [@frankcoderL](https://github.com/frankcoderL) for Nederlands version. 173 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # Niffler 钱包 2 | 3 | 简体中文 | [English](./README.md) | [Français](./README.fr-FR.md) 4 | 5 | Niffler 是一个开箱即用、用户友好的 [Grin](https://github.com/mimblewimble/grin) 开源图形界面钱包. 6 | 7 | 遵循Grin的命名传统,钱包的名字 **Niffler** 也是来自["哈利波特"](https://harrypotter.fandom.com/wiki/Niffler). 8 | 9 | #### 最新版本 V0.7.1 10 | 11 | v0.7.1 12 | 13 | Mac/Windows/Linux 版本: 14 | [https://github.com/grinfans/niffler/releases/tag/v0.7.1](https://github.com/grinfans/niffler/releases/tag/v0.7.1) 15 | 16 | Niffler钱包 加了 **hedwig** v1 代理服务; 现在,没有公共ip的用户也可以很简单容易地接收grin了 :) 17 | 18 | 也就是说,**你现在可以非常容易从各大交易所或矿池 提取grin到你的本地钱包** 。 19 | 20 | [A great video tutorial of how to withdraw grin from any exchanges using Niffler wallet.](https://www.youtube.com/watch?v=rufKWEv64o8) 21 | [![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/rufKWEv64o8/0.jpg)](https://www.youtube.com/watch?v=rufKWEv64o8) 22 | 23 | #### 中国大陆用户快速下载 24 | 25 | 中国大陆快速下载地址 26 | >Mac: 27 | https://grin-fans.oss-cn-hangzhou.aliyuncs.com/Niffler-v0.7.1-macos.dmg 28 | 29 | 30 | 31 | >Windows: 32 | https://grin-fans.oss-cn-hangzhou.aliyuncs.com/Niffler-v0.7.1-win-x64.zip 33 | 34 | 35 | ## 联系 36 | 37 | 联系我: xiaojay@gmail.com 38 | 39 | Telegram group: https://t.me/nifflerwallet 40 | 41 | Logo 由[@Duoasa](https://weibo.com/u/3197271025)创建. 42 | 43 | ## 特点 44 | 45 | * 用 [官方的Grin命令行钱包](https://github.com/mimblewimble/grin/releases) 作为后端, 用[elctron-vue](https://github.com/SimulatedGREG/electron-vue) 写了GUI界面 46 | 47 | * 会支持多个平台(mac/linux/windows) 48 | 49 | * 支持多个语言 50 | 51 | ## 运行 52 | 53 | #### 新建钱包 54 | 55 | ![create new wallet](https://media.giphy.com/media/VDl8gkDBRjlLBTmE8J/giphy.gif) 56 | 57 | #### 发送 grin 58 | 59 | ![send grin](https://media.giphy.com/media/kdo4hVj7G43yeUZKoQ/giphy.gif) 60 | 61 | #### 接收 grin 62 | ![receive grin](https://media.giphy.com/media/j5ztcFxVGRtJncUolz/giphy.gif) 63 | 64 | 65 | ## 构建 66 | 67 | *目前最新发布版本的Niffler 构建于分支 [gw5](https://github.com/grinfans/Niffler/tree/gw5)* 68 | 69 | 70 | ``` bash 71 | 72 | # git 克隆 73 | git clone https://github.com/grinfans/niffler.git && cd niffler 74 | 75 | # 安装依赖 76 | npm install 77 | cd hedwig 78 | npm install 79 | 80 | 81 | # 开发者模式 82 | npm run dev 83 | 84 | # 打包 85 | npm run build 86 | ``` 87 | 88 | --- 89 | 90 | ## 帮助 91 | 92 | pull request 和 bug报告 当然非常欢迎. 93 | 94 | ~~更具体地说, 希望有grin社区的设计师帮助设计一个logo :-)~~ 95 | 96 | 我们得到了中国grin社区的帮助,有了这个特别棒的logo :-). 97 | 98 | [@Duoasa](https://weibo.com/u/3197271025) 设计了Logo, and 感谢 [@机械师区块链](https://weibo.com/u/6318956004) 牵线搭桥. 99 | 100 | 翻译到其他语言也是很需要的. 只要翻译下这个[语言文件夹](https://github.com/grinfans/niffler/tree/master/src/lang)里的文字即可. 101 | 102 | 感谢 [@yozx](https://github.com/yozx) 翻译俄语版本. 103 | 104 | 感谢 [@Kinaou](https://github.com/Kinaou) 翻译法语版本. 105 | 106 | 感谢 [@frankcoderL](https://github.com/frankcoderL) 翻译荷兰语语版本. 107 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 0.1.{build} 2 | 3 | branches: 4 | only: 5 | - master 6 | 7 | image: Visual Studio 2017 8 | platform: 9 | - x64 10 | 11 | cache: 12 | - node_modules 13 | - '%APPDATA%\npm-cache' 14 | - '%USERPROFILE%\.electron' 15 | - '%USERPROFILE%\AppData\Local\Yarn\cache' 16 | 17 | init: 18 | - git config --global core.autocrlf input 19 | 20 | install: 21 | - ps: Install-Product node 8 x64 22 | - git reset --hard HEAD 23 | - yarn 24 | - node --version 25 | 26 | build_script: 27 | - yarn build 28 | 29 | test: off 30 | -------------------------------------------------------------------------------- /build/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grinfans/Niffler/035f7089044589cb89759a12d0294e65532247e5/build/icons/128x128.png -------------------------------------------------------------------------------- /build/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grinfans/Niffler/035f7089044589cb89759a12d0294e65532247e5/build/icons/16x16.png -------------------------------------------------------------------------------- /build/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grinfans/Niffler/035f7089044589cb89759a12d0294e65532247e5/build/icons/24x24.png -------------------------------------------------------------------------------- /build/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grinfans/Niffler/035f7089044589cb89759a12d0294e65532247e5/build/icons/256x256.png -------------------------------------------------------------------------------- /build/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grinfans/Niffler/035f7089044589cb89759a12d0294e65532247e5/build/icons/32x32.png -------------------------------------------------------------------------------- /build/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grinfans/Niffler/035f7089044589cb89759a12d0294e65532247e5/build/icons/48x48.png -------------------------------------------------------------------------------- /build/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grinfans/Niffler/035f7089044589cb89759a12d0294e65532247e5/build/icons/64x64.png -------------------------------------------------------------------------------- /build/icons/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grinfans/Niffler/035f7089044589cb89759a12d0294e65532247e5/build/icons/96x96.png -------------------------------------------------------------------------------- /build/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grinfans/Niffler/035f7089044589cb89759a12d0294e65532247e5/build/icons/icon.icns -------------------------------------------------------------------------------- /build/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grinfans/Niffler/035f7089044589cb89759a12d0294e65532247e5/build/icons/icon.ico -------------------------------------------------------------------------------- /hedwig/README.md: -------------------------------------------------------------------------------- 1 | # Hedwig v1 客户端 2 | 3 | ## 测试 4 | 5 | ```bash 6 | npm install 7 | node ./test.js 8 | ``` 9 | 10 | # Hedwig v1 服务端 11 | 12 | https://github.com/xiaojay/socket-tunnel -------------------------------------------------------------------------------- /hedwig/client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const IDLE_SOCKET_TIMEOUT_MILLISECONDS = 1000 * 30; 3 | let socket 4 | 5 | function createTunnel(options) { 6 | return new Promise((resolve, reject) => { 7 | // require the things we need 8 | const net = require('net'); 9 | const ss = require('socket.io-stream'); 10 | socket = require('socket.io-client')(options['server']); 11 | 12 | socket.on('connect', () => { 13 | console.log(new Date() + ': connected'); 14 | console.log(new Date() + ': requesting subdomain ' + options['subdomain'] + ' via ' + options['server']); 15 | 16 | socket.emit('createTunnel', options['app'], options['subdomain'], (err) => { 17 | if (err) { 18 | console.log(new Date() + ': [error] ' + err); 19 | 20 | reject(err); 21 | } else { 22 | console.log(new Date() + ': registered with server successfully'); 23 | 24 | // clean and concat requested url 25 | let url; 26 | let subdomain = options['subdomain'].toString(); 27 | let server = options['server'].toString(); 28 | 29 | if (server.includes('https://')) { 30 | url = `https://${subdomain}.${server.slice(8)}`; 31 | } else if (server.includes('http://')) { 32 | url = `http://${subdomain}.${server.slice(7)}`; 33 | } else { 34 | url = `https://${subdomain}.${server}`; 35 | } 36 | 37 | // resolve promise with requested URL 38 | resolve({'url':url, 'socket':socket}); 39 | } 40 | }); 41 | }); 42 | 43 | socket.on('incomingClient', (clientId) => { 44 | let client = net.connect(options['port'], options['hostname'], () => { 45 | let s = ss.createStream(); 46 | s.pipe(client).pipe(s); 47 | 48 | s.on('end', () => { 49 | client.destroy(); 50 | 51 | }); 52 | 53 | ss(socket).emit(clientId, s); 54 | }); 55 | 56 | client.setTimeout(IDLE_SOCKET_TIMEOUT_MILLISECONDS); 57 | client.on('timeout', () => { 58 | client.end(); 59 | process.send({'title':'received'}) 60 | }); 61 | 62 | client.on('error', () => { 63 | // handle connection refusal (create a stream and immediately close it) 64 | let s = ss.createStream(); 65 | ss(socket).emit(clientId, s); 66 | s.end(); 67 | }); 68 | }); 69 | 70 | socket.on('disconnect', function(){ 71 | console.log(new Date() + ': disconnect') 72 | process.send({'title':'disconnect'}) 73 | }); 74 | 75 | socket.on('reconnect', function(){ 76 | console.log(new Date() + ': reconnect') 77 | process.send({'title':'reconnect'}) 78 | }); 79 | 80 | }); 81 | }; 82 | 83 | const optimist = require('optimist'); 84 | let argv = optimist 85 | .usage('Usage: $0 --server [string] --subdomain [string] --hostname [string] --port [number]') 86 | .options('s', { 87 | alias: 'server', 88 | describe: '(Required) Tunnel server endpoint' 89 | }) 90 | .options('sub', { 91 | alias: 'subdomain', 92 | describe: '(Required) Public URL the tunnel server is forwarding to us' 93 | }) 94 | .options('h', { 95 | alias: 'hostname', 96 | default: '127.0.0.1', 97 | describe: 'Address of local server for forwarding over socket-tunnel' 98 | }) 99 | .options('p', { 100 | alias: 'port', 101 | describe: '(Required) Port of local server for forwarding over socket-tunnel' 102 | }) 103 | .options('a', { 104 | alias: 'app', 105 | describe: '(Required) app ' 106 | }) 107 | .argv; 108 | 109 | if (argv.help) { 110 | optimist.showHelp(); 111 | process.exit(); 112 | } 113 | 114 | if (!argv['server'] || !argv['subdomain'] || !argv['port']||!argv['app']) { 115 | for (var key in ['server', 'subdomain', 'port']) { 116 | if (argv[key]) continue; 117 | 118 | console.log('Error: Required option, but nothing found'); 119 | 120 | optimist.showHelp(); 121 | 122 | process.exit(); 123 | } 124 | } 125 | createTunnel(argv).then( 126 | (res)=>{ 127 | let msg = { 128 | 'title':'tunnelCreated', 129 | 'address':res.url, 130 | //'socket':res.socket 131 | } 132 | process.send(msg) 133 | } 134 | ).catch((error)=>{ 135 | console.log('Failed to create tunnel: ' + error ) 136 | process.send({'title': 'failed', 'error':error}) 137 | }) 138 | process.on('message', (msg)=>{ 139 | if(msg.title=='close'){ 140 | socket.close() 141 | //process.exit() 142 | } 143 | }) -------------------------------------------------------------------------------- /hedwig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hedwig", 3 | "version": "0.1.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "ws": ">=7.4.6", 13 | "xmlhttprequest-ssl": ">=1.6.2", 14 | "optimist": "^0.6.1", 15 | "socket.io-client": "^2.2.0", 16 | "socket.io-stream": "^0.9.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /hedwig/test.js: -------------------------------------------------------------------------------- 1 | var cp = require('child_process') 2 | var hedwigServer = 'https://v1.hedwig.im' 3 | var hedwigApp = 'Niffler' 4 | var hedwigPort = '8080' 5 | let args = ['--server', hedwigServer, '--app', hedwigApp, '--port', hedwigPort, 6 | '--subdomain', String(Math.random()).slice(2)] 7 | hedwig = cp.fork('./client.js', args) 8 | hedwig.on('error', (err) => { 9 | console.log(`error when try to start hedwig: ${err}`) 10 | }) 11 | hedwig.on('message', (msg)=>{ 12 | console.log('Msg received: %0', msg) 13 | }) 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Niffler", 3 | "version": "0.7.1", 4 | "author": "xiaojay ", 5 | "description": "Niffler, a grin gui wallet", 6 | "license": "apache-2.0", 7 | "main": "./dist/electron/main.js", 8 | "scripts": { 9 | "build": "node .electron-vue/build.js && electron-builder", 10 | "build:dir": "node .electron-vue/build.js && electron-builder --dir", 11 | "build:clean": "cross-env BUILD_TARGET=clean node .electron-vue/build.js", 12 | "build:web": "cross-env BUILD_TARGET=web node .electron-vue/build.js", 13 | "dev": "node .electron-vue/dev-runner.js", 14 | "pack": "npm run pack:main && npm run pack:renderer", 15 | "pack:main": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.main.config.js", 16 | "pack:renderer": "cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.renderer.config.js", 17 | "postinstall": "" 18 | }, 19 | "build": { 20 | "productName": "Niffler", 21 | "appId": "org.niffler", 22 | "directories": { 23 | "output": "build" 24 | }, 25 | "files": [ 26 | "dist/electron/**/*" 27 | ], 28 | "extraFiles": [ 29 | { 30 | "from": "hedwig", 31 | "to": "resources/bin/hedwig", 32 | "filter": [ 33 | "**/*", 34 | "!test.js" 35 | ] 36 | } 37 | ], 38 | "dmg": { 39 | "contents": [ 40 | { 41 | "x": 410, 42 | "y": 150, 43 | "type": "link", 44 | "path": "/Applications" 45 | }, 46 | { 47 | "x": 130, 48 | "y": 150, 49 | "type": "file" 50 | } 51 | ] 52 | }, 53 | "mac": { 54 | "icon": "build/icons/icon.icns", 55 | "extraFiles": [ 56 | { 57 | "from": "resources/bin/mac", 58 | "to": "Resources/bin/mac", 59 | "filter": [ 60 | "**/*" 61 | ] 62 | } 63 | ] 64 | }, 65 | "win": { 66 | "icon": "build/icons/icon.ico", 67 | "target": "nsis", 68 | "extraFiles": [ 69 | { 70 | "from": "resources/bin/win", 71 | "to": "resources/bin/win", 72 | "filter": [ 73 | "**/*" 74 | ] 75 | } 76 | ] 77 | }, 78 | "nsis": { 79 | "oneClick": false, 80 | "allowToChangeInstallationDirectory": true 81 | }, 82 | "linux": { 83 | "icon": "build/icons", 84 | "target": [ 85 | "deb", 86 | "appImage" 87 | ], 88 | "extraFiles": [ 89 | { 90 | "from": "resources/bin/linux", 91 | "to": "resources/bin/linux", 92 | "filter": [ 93 | "**/*" 94 | ] 95 | } 96 | ], 97 | "desktop": { 98 | "Name": "Niffler", 99 | "Encoding": "UTF-8", 100 | "Type": "Application", 101 | "Comment": "Niffler, a grin gui wallet", 102 | "StartupWMClass": "Niffler" 103 | } 104 | } 105 | }, 106 | "dependencies": { 107 | "animate.css": "^3.7.0", 108 | "app-root-dir": "^1.0.2", 109 | "bufferutil": "^4.0.1", 110 | "bulma": "^0.8.0", 111 | "bulma-pageloader": "^0.3.0", 112 | "electron-build-env": "^0.2.0", 113 | "electron-log": "^3.0.1", 114 | "external-ip": "^2.1.1", 115 | "externalip": "^1.0.2", 116 | "fs-extra": "^7.0.1", 117 | "fstream": "^1.0.12", 118 | "get-folder-size": "^2.0.1", 119 | "is-valid-domain": "0.0.5", 120 | "neon-cli": "^0.2.0", 121 | "optimist": "^0.6.1", 122 | "promise.prototype.finally": "^3.1.0", 123 | "public-ip": "^3.0.0", 124 | "socket.io": "^2.1.0", 125 | "socket.io-client": "^2.1.0", 126 | "socket.io-stream": "^0.9.1", 127 | "tail": "^2.0.2", 128 | "tar": ">=4.4.2", 129 | "tldjs": "^2.3.1", 130 | "url-join": "^4.0.0", 131 | "urllib": "^2.34.0", 132 | "utf-8-validate": "^5.0.2", 133 | "uuid": "^3.2.1", 134 | "vue": "^2.5.16", 135 | "vue-awesome": "^3.5.3", 136 | "vue-electron": "^1.0.6", 137 | "vue-i18n": "^8.16.0", 138 | "vue-moment": "^4.1.0", 139 | "vue-truncate-filter": "^1.1.7", 140 | "dns-packet": ">=1.3.2", 141 | "postcss": ">=8.2.10", 142 | "hosted-git-info": ">=2.8.9", 143 | "url-parse": ">=1.5.0", 144 | "xmlhttprequest-ssl": ">=1.6.2", 145 | "sockjs": ">=0.3.20", 146 | "ssri": ">=6.0.2", 147 | "y18n": ">=4.0.1" 148 | }, 149 | "devDependencies": { 150 | "http-proxy": "^1.18.1", 151 | "node-forge": "^0.10.0", 152 | "ajv": "^6.5.0", 153 | "axios": ">=0.21.1", 154 | "babel-core": "^6.26.3", 155 | "babel-loader": "^7.1.4", 156 | "babel-plugin-transform-runtime": "^6.23.0", 157 | "babel-preset-env": "^1.7.0", 158 | "babel-preset-stage-0": "^6.24.1", 159 | "babel-register": "^6.26.0", 160 | "babili-webpack-plugin": "^0.1.2", 161 | "cfonts": "^2.1.2", 162 | "chalk": "^2.4.1", 163 | "copy-webpack-plugin": "^4.5.1", 164 | "cross-env": "^5.1.6", 165 | "css-loader": "^0.28.11", 166 | "del": "^3.0.0", 167 | "devtron": "^1.4.0", 168 | "electron": "^2.0.4", 169 | "electron-builder": "^22.3.2", 170 | "electron-debug": "^1.5.0", 171 | "electron-devtools-installer": "^2.2.4", 172 | "file-loader": "^1.1.11", 173 | "html-webpack-plugin": "^3.2.0", 174 | "husky": "^2.4.0", 175 | "js-yaml": "3.13.1", 176 | "lodash": "^4.17.21", 177 | "lodash.merge": ">=4.6.2", 178 | "mini-css-extract-plugin": "0.4.0", 179 | "multispinner": "^0.2.1", 180 | "node-loader": "^0.6.0", 181 | "node-sass": "^4.13.1", 182 | "sass-loader": "^7.0.3", 183 | "style-loader": "^0.21.0", 184 | "url-loader": "^1.0.1", 185 | "vue-html-loader": "^1.2.4", 186 | "vue-loader": "^15.2.4", 187 | "vue-style-loader": "^4.1.0", 188 | "vue-template-compiler": "^2.5.16", 189 | "webpack": "^4.15.1", 190 | "webpack-cli": "^3.0.8", 191 | "webpack-dev-server": "^3.1.4", 192 | "webpack-hot-middleware": "^2.22.2", 193 | "webpack-merge": "^4.1.3", 194 | "elliptic": ">=6.5.3", 195 | "dot-prop": ">=5.1.1" 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /resources/bin/linux/README.md: -------------------------------------------------------------------------------- 1 | The linux version of grin binaries is grin v5.0.1 https://github.com/mimblewimble/grin/releases/tag/v5.0.1 2 | downloaded from (https://github.com/mimblewimble/grin/releases/download/v5.0.1/grin-v5.0.1-linux-amd64.tar.gz) 3 | 4 | The linux version of grin-wallet binaries is grin v5.0.3 https://github.com/mimblewimble/grin-wallet/releases/tag/v5.0.3 5 | downloaded from (https://github.com/mimblewimble/grin-wallet/releases/download/v5.0.3/grin-wallet-v5.0.3-linux-amd64.tar.gz) 6 | -------------------------------------------------------------------------------- /resources/bin/linux/grin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grinfans/Niffler/035f7089044589cb89759a12d0294e65532247e5/resources/bin/linux/grin -------------------------------------------------------------------------------- /resources/bin/linux/grin-wallet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grinfans/Niffler/035f7089044589cb89759a12d0294e65532247e5/resources/bin/linux/grin-wallet -------------------------------------------------------------------------------- /resources/bin/mac/README.md: -------------------------------------------------------------------------------- 1 | The Mac version of grin node binaries is grin v5.0.4(https://github.com/mimblewimble/grin/releases/tag/v5.0.4). 2 | Downloaded from (https://github.com/mimblewimble/grin/releases/download/v5.0.4/grin-v5.0.4-macos.tar.gz). 3 | 4 | The Mac version of grin-wallet binaries is grin-wallet v5.0.3(https://github.com/mimblewimble/grin-wallet/releases/tag/v5.0.3). 5 | Downloaded from (https://github.com/mimblewimble/grin-wallet/releases/download/v5.0.3/grin-wallet-v5.0.3-macos.tar.gz). -------------------------------------------------------------------------------- /resources/bin/mac/grin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grinfans/Niffler/035f7089044589cb89759a12d0294e65532247e5/resources/bin/mac/grin -------------------------------------------------------------------------------- /resources/bin/mac/grin-wallet: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grinfans/Niffler/035f7089044589cb89759a12d0294e65532247e5/resources/bin/mac/grin-wallet -------------------------------------------------------------------------------- /resources/bin/win/README.md: -------------------------------------------------------------------------------- 1 | The Windows version of grin node binaries is grin v5.0.4(https://github.com/mimblewimble/grin/releases/tag/v5.0.4), 2 | 3 | Downloaded from (https://github.com/mimblewimble/grin/releases/download/v5.0.4/grin-v5.0.4-win-x64.zip) 4 | 5 | The Windows version of grin-wallet binaries is grin-wallet v5.0.3(https://github.com/mimblewimble/grin-wallet/releases/tag/v5.0.3), 6 | 7 | Downloaded from (https://github.com/mimblewimble/grin-wallet/releases/download/v5.0.3/grin-wallet-v5.0.3-win-x64.zip) -------------------------------------------------------------------------------- /resources/bin/win/grin-wallet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grinfans/Niffler/035f7089044589cb89759a12d0294e65532247e5/resources/bin/win/grin-wallet.exe -------------------------------------------------------------------------------- /resources/bin/win/grin.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grinfans/Niffler/035f7089044589cb89759a12d0294e65532247e5/resources/bin/win/grin.exe -------------------------------------------------------------------------------- /src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Niffler 6 | <% if (htmlWebpackPlugin.options.nodeModules) { %> 7 | 8 | 11 | <% } %> 12 | 17 | 18 | 19 |
20 | 21 | <% if (!process.browser) { %> 22 | 25 | <% } %> 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/lang/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grinfans/Niffler/035f7089044589cb89759a12d0294e65532247e5/src/lang/index.js -------------------------------------------------------------------------------- /src/lang/ko.js: -------------------------------------------------------------------------------- 1 | const messages = { 2 | msg: { 3 | title: 'Niffler 지갑', 4 | password: '비밀번호', 5 | passwordAgain: '암호를 다시 입력하십시오', 6 | wrongPassword: '비밀번호 오류', 7 | login_: '로그인', 8 | logout: '로그 아웃', 9 | search: '검색', 10 | clearup: '제거', 11 | jump: '점프', 12 | 13 | confirmed: '확인 완료', 14 | unconfirmed: '미확인', 15 | locked: '잠김', 16 | 17 | send: '보내기', 18 | receive: '수신', 19 | 20 | cancel:'취소', 21 | save: '저장', 22 | 23 | welcome: 'Niffler 지갑 사용을 환영합니다', 24 | 25 | msg: '메시지', 26 | 27 | more: 'More', 28 | back: 'back', 29 | 30 | remote: 'remote', 31 | local: 'local', 32 | 33 | node: 'Node', 34 | remoteNode: 'Remote node', 35 | localNode: 'Local node', 36 | 37 | other: 'Others', 38 | loading: 'Updating status from Grin node ...', 39 | waitForLocalGnode: 'Wait for local grin node to start', 40 | 41 | exportLog: 'Export Niffler Log', 42 | 43 | error: 'Error', 44 | login: { 45 | walletExist: 'grin 지갑이 존재합니다; 원래 비밀번호로 로그인 해 주십시오:-)', 46 | }, 47 | 48 | create:{ 49 | seedPhrase: '니모닉', 50 | toNewMsg: '지갑이 없습니다. 새 것을 만드십시오.', 51 | newWallet: '새 지갑 만들기', 52 | backupNote: '지갑을 복구하기 위해 니모닉를 저장해 주십시오', 53 | backupFinish: 'Ok, I backed up my seed phrase. Close wallet, then open it again', 54 | errorPasswdEmpty: '비밀번호는 비워 둘 수 없습니다', 55 | errorPasswdConsistency: '같은 암호를 입력하십시오', 56 | errorCreateFailed: 'Error when try to create new wallet. Maybe restart wallet and Try it later.', 57 | }, 58 | 59 | new_:{ 60 | create: 'Create new wallet', 61 | restore: 'Restore wallet via seed phrase', 62 | import: 'Import wallet backup file' 63 | }, 64 | 65 | restore:{ 66 | seedPhrase: 'Seed Phrase', 67 | title: 'Restore wallet via seed phrase', 68 | addSeedsInfo: 'Add seed phrase one by one please', 69 | add: 'Add', 70 | invalid: 'Invalid Seed Phrase', 71 | failed: 'Recover wallet from seeds Failed', 72 | delete: 'Delete', 73 | added: 'Finish enter seed phrase', 74 | newPassword: 'Set a new password', 75 | recover: 'Recover', 76 | reAdd: 'Re-enter seed phrase ', 77 | recovered: 'Wallet recovered, it is time to check balance from Grin blockchain', 78 | restoring: 'It will take 10-30 minutes to finish check. Be patient ......', 79 | restored: 'Wallet recovered and balance checked.', 80 | login: 'Close Wallet, then Open it again to Login', 81 | }, 82 | 83 | app:{ 84 | create: '트랜잭션 파일 생성', 85 | finalize: '완료', 86 | httpSend: 'http/https/Tor 를 통해 전송', 87 | createRespFile: '응답 트랜잭션 파일 작성', 88 | httpReceive: '수신 할 http 리스너 열기', 89 | height:'높이', 90 | updateTitle: '새 버전을 찾았습니다', 91 | updateMsg: '새로운 버전의 Niffler 지갑이 존재합니다. 지금 업데이트 해 주시기 바랍니다.', 92 | yes: '예', 93 | no: '아니오', 94 | hedwig: 'Receive via Hedwig', 95 | tor: 'Tor Network' 96 | }, 97 | 98 | info: { 99 | spendable: '지출 가능', 100 | total: '합계', 101 | unfinalization: 'Unfinalized', 102 | immature: 'Immature' 103 | }, 104 | 105 | txs:{ 106 | tx: '거래', 107 | canceled:'취소됨', 108 | noTxFound: '거래 없음', 109 | noTx:'거래 없음', 110 | cancelSuccess:'이 거래가 취소되었습니다', 111 | }, 112 | 113 | commit:{ 114 | unspentCmt: '사용되지 않은 출력 커밋', 115 | heightCreated: '생성시 블록 높이', 116 | unspent: '미사용', 117 | spent: '지출', 118 | noCmtFound: '사용되지 않은 출력 커밋 없음', 119 | noCmt:'사용되지 않은 출력 커밋 없음', 120 | }, 121 | 122 | fileSend:{ 123 | sendAmount: '보낼 금액', 124 | createTxFile: '새 변환 파일 만들기', 125 | WrongAmount: '잘못된 금액', 126 | saveMsg: '만든 성적서 파일 저장', 127 | CreateFail: '새 변환 파일을 만들지 못했습니다' 128 | }, 129 | 130 | httpSend:{ 131 | sendAmount: '보낼 금액', 132 | address:'주소', 133 | WrongAmount: '잘못된 금액', 134 | WrongAddress: '잘못된 주소', 135 | WrongTxData: '트랜잭션을 만들지 못했습니다', 136 | success: '탈주 성공', 137 | TxFailed: '전송 실패 보내기', 138 | }, 139 | 140 | fileReceive: { 141 | dropMsg: '받은 트랜잭션 파일을 버리십시오', 142 | WrongFileType: '잘못된 트랜잭션 파일 형식', 143 | saveMsg: '생성 된 응답 트랜잭션 파일 저장', 144 | CreateFailed: '새 응답 기록 파일을 만들지 못했습니다', 145 | NoSavePlace: '저장 위치를 선택하십시오', 146 | }, 147 | 148 | finalize: { 149 | finalize: '완료', 150 | success: '거래 성공', 151 | ok:'확인', 152 | sending: '보내기', 153 | dropMsg: '완료 할 응답 트랜잭션 파일 삭제', 154 | WrongFileType: '잘못된 트랜잭션 파일 형식', 155 | TxFailed: '변환 실패', 156 | }, 157 | 158 | httpReceive: { 159 | launchSucess: '시작 성공', 160 | listening: "지갑의 HTTP 수신 대기", 161 | address: '지갑 주소', 162 | reachableMsg2: '인터넷 사용자가 귀하의 IP에 연결할 수 있는지 확인하십시오', 163 | close: '지갑의 HTTP 수신 정지', 164 | attention: '주의', 165 | reachableMsg: 'HTTP 연결을 시작하기 위해 인터넷 사용자들에게 도달할 수 있는 공용 ip가 필요합니다', 166 | password: '지갑 비밀번호 (HTTP 수신 대기)', 167 | start: '시작', 168 | failed: '시작 실패, 비밀번호를 확인 해 주십시오', 169 | failed2: 'HTTP listen failed, your public ip could not reachable by internet user. Try trascation file Or Hedwig.', 170 | failed3: 'Failed to get your public ip; try it later', 171 | failed4: 'Localhost http listen is running(http://127.0.0.1:3415). However, your public ip could not reachable by internet user. Try trascation file Or Hedwig.', 172 | ip: '귀하의 공개 IP' 173 | }, 174 | 175 | hedwig: { 176 | title: 'Receive via Hedwig(v1)', 177 | launchSucess: 'Started successful', 178 | reachable: 'Hedwig address is available', 179 | address: 'Address to receive', 180 | tip:'Please keep wallet online.', 181 | close: 'Stop Hedwig', 182 | introTitle: 'Introduction', 183 | intro1: 'Hedwig(v1) is a relay service for users without a public ip. It provides a temporary address to receive grin.', 184 | intro2: 'When someone send grin to the address, Hedwig(v1) will forward the send request to your wallet. So you will get your grin.', 185 | start: 'Start', 186 | failed: 'Error when try to connect Hedwig server, try it latter maybe', 187 | failed2: 'Error when test Hedwig address, try it later maybe or restart wallet.', 188 | failed3: 'Failed to start local grin receive service, try it later maybe or restart wallet.', 189 | copy: 'copy address', 190 | copied: 'address was copied in clipboard' 191 | }, 192 | 193 | check: { 194 | title: 'Check Balance', 195 | checking: 'Checking, be patient ...', 196 | stop: 'Stop Check', 197 | 198 | tip:'It will take 10-30 minutes to finish check', 199 | introTitle: 'Introduction', 200 | 201 | intro1: 'Because of all of the possibilities listed in the cancel command, as well as the possibility of forks, it is quite possible for your wallet to end up in an inconsistent state', 202 | intro2: "For this reason, Grin provides a manual check command that scans the chain's UTXO set for any outputs belonging to your wallet, and ensures they're in a consistent state with your local wallet database.", 203 | 204 | start: 'Start', 205 | stopCheckMsg: 'Check was cancelled', 206 | checkedMsg: 'Check balance finished' 207 | }, 208 | 209 | lang: { 210 | title: 'Select Language', 211 | lang: 'Language', 212 | select: 'Select' 213 | }, 214 | 215 | gnode:{ 216 | title: 'Local grin node', 217 | tabStatus: 'Status', 218 | tabPeers: 'Peers', 219 | tabLog: 'Log', 220 | tabConfig: 'Settings', 221 | statusRunning: 'Running, Available', 222 | statusSyncing: 'Syncing ...', 223 | statusToStart: 'not Running', 224 | status:'Status', 225 | localRemoteHeight: 'Local Height/Networking Height', 226 | nodeVersion: 'Node Version', 227 | protocolVersion: 'Protocol Version', 228 | connectedCount: 'Connected Peers', 229 | location: 'Location where grin blockchain data store', 230 | size: 'Size of grin blockchain data', 231 | restart: 'Restart grin node', 232 | height: 'height' 233 | }, 234 | 235 | gnodeConfig:{ 236 | warning: 'For General users, default setting is preferred!', 237 | useLocalorNot: '(Recommend)Use local grin node', 238 | connectMethod: 'Connect Method', 239 | remoteFirst: 'Remote grin node is preferred, When remote node is not available, use local node.', 240 | localFirst: '(Recommend)Local grin node is preferred when it is synced. Otherwise, use remote node.', 241 | remoteAllTime: 'Use remote all the time', 242 | localAllTime: 'Use local all the time', 243 | background: '(Recommend)Running grin node background when Niffler is closed.', 244 | restoreToDefault: 'Restore to default setting', 245 | saved: 'Settings was saved, Restart Niffler to take effect.' 246 | }, 247 | 248 | gnodeConfigModal:{ 249 | config: 'Grin Local Node Settings', 250 | title: 'Grin node settings' 251 | }, 252 | 253 | remove:{ 254 | title: 'Remove Current Wallet', 255 | warning: 'Warning !', 256 | info: 'Before you remove current wallet, Make sure there is no grin in this wallet or You write down the Seed Phrase !', 257 | help: 'Enter "remove" into the input box below to confirm', 258 | remove: 'Remove', 259 | success: 'Current wallet was removed. Click "OK" to restart Niffler.' 260 | }, 261 | 262 | tor:{ 263 | title: 'Tor', 264 | tabStatus: 'Status', 265 | tabLog: 'Log', 266 | tabConfig: 'Setting', 267 | statusRunning: 'Tor Running', 268 | statusFailed: 'Tor Start Failed', 269 | statusStarting: 'Tor Starting', 270 | statusToStart: 'Not Running', 271 | status:'Status', 272 | start: 'start', 273 | restart: 'restart', 274 | address: 'Tor address to receive grin', 275 | 276 | tip: 'Please keep wallet online When receving grin', 277 | tip2: 'Starting ... it may take 10-60 seconds', 278 | introTitle: 'Introduction', 279 | intro1: 'Tor is free and open-source software for enabling anonymous communication.', 280 | intro2: 'Receive/send grin via tor is more anonymous/sercurity/decentralized', 281 | intro3: '(If Tor is blocked by network in your location, Configure a https/socks proxy first.)', 282 | 283 | copy: 'Copy Address', 284 | copied: 'copied', 285 | startWhenLaunch: '(Recommend) Auto start Tor after open wallet', 286 | proxy: 'proxy', 287 | proxyUser: 'user', 288 | proxyPassword: 'password', 289 | proxyHelp: 'Connect Tor network via proxy', 290 | optional: 'optional', 291 | proxyHelp2: 'Proxy example: https://127.0.0.1:9009 or socks5://127.0.0.1:1080', 292 | 293 | error: 'Start Tor network failed.', 294 | errorNoCurl: 'No curl found. Install curl first.', 295 | errorNoTor: 'No tor found. Install tor first.', 296 | errorInvalidProxy: 'Invalid proxy', 297 | saved: 'Setting saved. will use this setting when start Tor next time.s' 298 | }, 299 | 300 | } 301 | } 302 | export default messages 303 | -------------------------------------------------------------------------------- /src/lang/zh.js: -------------------------------------------------------------------------------- 1 | const messages = { 2 | msg: { 3 | title: 'Niffler 钱包', 4 | password: '密码', 5 | passwordAgain: '再次输入密码', 6 | wrongPassword: 'Grin owner Api进程启动失败,可能是密码错误', 7 | login_: '登录', 8 | logout: '登出', 9 | search: '搜索', 10 | clearup: '清除', 11 | jump: '跳转', 12 | 13 | confirmed: '已确认', 14 | unconfirmed: '等待确认', 15 | locked: '已锁定', 16 | 17 | send: '发送', 18 | receive: '接收', 19 | 20 | cancel: '撤销', 21 | save: '保存', 22 | welcome: '欢迎使用Niffler钱包', 23 | msg: '消息', 24 | more: '更多', 25 | back: '返回', 26 | 27 | remote: '远程', 28 | local: '本地', 29 | 30 | node: '节点', 31 | remoteNode: '远程节点', 32 | localNode: '本地节点', 33 | 34 | other: '其他', 35 | 36 | loading: '从Grin节点更新状态中 ...', 37 | error: '错误', 38 | optional: '可选', 39 | open: '打开', 40 | close: '关闭', 41 | 42 | address: 'Slatepack 地址', 43 | 44 | login: { 45 | walletExist: '发现已有Grin钱包存在;请用原有的密码登陆吧 :-)', 46 | waitForLocalGnode: '等待本地Grin节点启动中', 47 | selectingGnode: '正在选择Grin节点', 48 | gnode: 'Grin节点' 49 | }, 50 | 51 | remove:{ 52 | title: '移除当前钱包', 53 | warning: '警告 !', 54 | info: '移除钱包前,请确认 当前钱包里已经没有grin 或者 你已经完整记录了当前钱包的助记词 !', 55 | help: '在下面输入框填入 “移除” 用于确认', 56 | remove: '移除钱包', 57 | success: '当前钱包已经移除,请重启Niffler' 58 | }, 59 | 60 | new_:{ 61 | create: '新建钱包', 62 | restore: '通过助记词导入钱包', 63 | import: '通过钱包备份文件导入' 64 | }, 65 | 66 | create:{ 67 | toNewMsg: '没有发现已经建好的钱包;新建一个吧 :-)', 68 | seedPhrase: '助记词', 69 | newWallet: '新建钱包', 70 | backupNote: '重要!请务必备份助记词,用于恢复钱包', 71 | backupFinish: '备份完成,关闭钱包;请再次打开钱包,并登录。', 72 | errorPasswdEmpty: '密码不能为空', 73 | errorPasswdConsistency: '请输入相同的密码', 74 | errorCreateFailed: '新建钱包时发生错误,你可以试试重启下钱包,过一会儿再试试', 75 | }, 76 | 77 | restore:{ 78 | seedPhrase: '助记词', 79 | title: '从助记词中恢复钱包', 80 | addSeedsInfo: '请一个个地输入助记词', 81 | add: '添加', 82 | invalid: '助记词无效', 83 | failed: '从助记词中恢复钱包失败', 84 | delete: '删除', 85 | added: '助记词输入完成', 86 | newPassword: '设置新的密码', 87 | recover: '恢复钱包', 88 | reAdd: '重新输入', 89 | recovered: '钱包恢复成功, 开始从Grin区块链同步钱包余额;', 90 | restoring: '预计需要15-30分钟,不要关闭钱包,请耐心等待 ......', 91 | restored: '钱包恢复成功并且余额同步完成', 92 | login: '关闭钱包, 然后再次打开并登录', 93 | }, 94 | 95 | app:{ 96 | create: '生成 slatepack', 97 | finalize: '确认slatepack并完结交易', 98 | httpSend: '通过 https/http 发送', 99 | createRespFile: '生成回应slatepack', 100 | httpReceive: '开启http端口接收', 101 | height: '同步高度', 102 | updateTitle: '发现新版本', 103 | updateMsg: '发现Niffer钱包的新版本, 请立刻更新! (下载并覆盖原有程序即可).', 104 | yes: '好的', 105 | no: '暂时不更新', 106 | 107 | hedwig: '用Hedwig收币', 108 | tor: '洋葱网络(Tor)', 109 | address: 'Slatepack地址', 110 | }, 111 | 112 | info:{ 113 | spendable: '可用余额', 114 | total: '总计', 115 | unfinalization: '等待对方完成', 116 | immature: '未成熟' 117 | }, 118 | 119 | txs:{ 120 | tx: '交易', 121 | canceled: '已取消', 122 | noTxFound: '没有找到相关交易', 123 | noTx: '没有交易', 124 | cancelSuccess: '取消交易成功', 125 | }, 126 | 127 | commit:{ 128 | unspentCmt: '未花费的交易输出', 129 | heightCreated: '创建时区块高度', 130 | unspent: '可用', 131 | spent: '已用掉', 132 | noCmtFound: '没有找到相关未花费交易输出', 133 | noCmt: '没有未花费的交易输出', 134 | copied: '已复制' 135 | }, 136 | 137 | fileSend:{ 138 | sendAmount: '发送数量', 139 | createTxFile: '生成 slatepack', 140 | WrongAmount: '发送数量错误', 141 | WrongSlatepackAddress: 'Slatepack 地址不对', 142 | saveMsg: '保存新生成的slatepack文件', 143 | CreateFailed: '生成slatepack错误', 144 | NotEnough: '没有足够的余额,请至少留下0.01作为手续费', 145 | 146 | slatepack: 'SLATEPACK', 147 | slatepackAddress: '接收方的 Slatepack 地址', 148 | slatepackAddressTips: 'Slatepack 地址 用于加密slatepack信息 以及 生成付款证明.', 149 | slatepackCreated: 'Slatepack 已经生成 !', 150 | 151 | slatepackEncryptedTips: '此slatepack已加密,可以在非加密通道发送', 152 | slatepackUnEncryptedTips: '此slatepack没有加密, 请通过安全信道发送', 153 | copySlatepack: '复制', 154 | fileSlatepack: '导出至文件', 155 | copied: '已复制' 156 | }, 157 | 158 | httpSend:{ 159 | sendAmount: '发送数量', 160 | sendMessage: '消息', 161 | optional: '可选', 162 | address: '发送地址', 163 | WrongAmount: '发送数量错误', 164 | NotEnough: '没有足够的余额,请至少留下0.01作为手续费', 165 | WrongAddress: '发送地址错误', 166 | WrongTxData: '交易数据生成失败', 167 | success: '交易成功', 168 | TxFailed: '交易失败', 169 | TxResponseFailed: '接收者回应失败', 170 | TxCreateFailed: '发起交易失败', 171 | salteVersion: '交易文件版本', 172 | salteVersionHelp: '如果发送交易失败,可以试下改变交易文件版本', 173 | noTor: '洋葱网络没有启动,需要先启动洋葱网络', 174 | }, 175 | 176 | fileReceive: { 177 | dropMsg: '拖入收到的交易文件', 178 | saveMsg: '保存新生成的 回应slatepack', 179 | CreateFailed: '生成回应slatepack错误', 180 | NoSavePlace: '请选择生成交易文件的位置', 181 | WrongFileType: '交易文件类型错误', 182 | toOpen: '读取slatepack文件', 183 | toPaste: 'or 直接复制slatepack, 并黏贴到下面输入框', 184 | create: '生成回应 slatepack', 185 | WrongSlatepack: 'Slatepack 内容不对', 186 | openMsg: '打开 slatepack 文件', 187 | sender: '发送方', 188 | slatepackCreated: '回应Slatepack 已经生成 !', 189 | copySlatepack: '复制', 190 | fileSlatepack: '导出至文件', 191 | copied: '已复制', 192 | }, 193 | 194 | finalize: { 195 | finalize: '完结交易', 196 | success: '交易成功', 197 | ok: '确定', 198 | sending: '发送中', 199 | dropMsg: '拖入需要确认的交易文件', 200 | WrongFileType: '交易文件类型错误', 201 | WrongSlatepack: 'Slatepack 内容不对', 202 | TxFailed: '交易失败', 203 | toPaste: 'or 直接复制slatepack, 并黏贴到下面输入框', 204 | toOpen: '读取slatepack文件', 205 | send: '完结并发送交易', 206 | openMsg: '打开 slatepack 文件' 207 | }, 208 | 209 | httpReceive: { 210 | launchSucess: '启动成功', 211 | listening: '你的钱包正在监听', 212 | address: '钱包地址', 213 | reachableMsg2: '请确保你的ip地址外网用户能够访问', 214 | close: '关闭HTTP监听', 215 | attention: '注意', 216 | reachableMsg: '设置HTTP接收Grin: 需要你有对外的公网ip, 并且你的ip能被访问', 217 | password: '钱包密码 (用于启动http监听)', 218 | start: '启动', 219 | error: '没有输入密码.', 220 | failed: '启动失败,可能是密码错误', 221 | failed2: '监听失败,你的对外ip无法被访问到; 请使用交易文件的方法 或者 Hedwig 来收发Grin.', 222 | failed3: '无法获取你的IP地址,请过段时间再试下', 223 | failed4: '本地HTTP监听(http://127.0.0.1:3415)已经启动。但是你的对外ip无法被访问到; 请使用交易文件的方法 或者 Hedwig 来收发Grin.', 224 | ip: '你的公网IP' 225 | }, 226 | 227 | hedwig: { 228 | title: '用Hedwig(v1)收币', 229 | launchSucess: '启动成功', 230 | reachable: 'Hedwig临时地址正常可用', 231 | address: '提币地址', 232 | tip: '收币时,请保持钱包在线.', 233 | close: '关闭Hedwig', 234 | introTitle: '介绍', 235 | intro1: 'Hedwig(v1) 是一个 为没有公网IP的用户 定制的代理服务,它会提供一个临时的收币地址.', 236 | intro2: '当有人向这个临时地址发送grin时,它会把这个发币请求转发到你的Niffler钱包,完成交易.', 237 | start: '启动', 238 | failed: '连接到hedwig服务器时 发生错误,等下再试吧.', 239 | failed2: '连接到hedwig临时地址时 发生错误, 重启下钱包 or 等下再试吧.', 240 | failed3: '启动本地grin接收服务失败, 重启下钱包 or 等下再试吧.', 241 | copy: '复制地址', 242 | copied: '地址已经复制到剪贴板' 243 | }, 244 | 245 | check: { 246 | title: '同步余额', 247 | checking: '正在同步,请耐心等待 ...', 248 | stop: '停止同步', 249 | 250 | 251 | tip: '完成同步余额可能需要10-30分钟.', 252 | introTitle: '介绍', 253 | 254 | intro1: '当你的本地钱包余额 和 Grin区块链上的余额不一致(可能是由于交易被取消或者发送失败等原因)时,可以使用 "同步余额" 功能.', 255 | intro2: '同步时,在Grin区块链的所有未花费的输出(uxtos)中,找到你可以使用的未花费的输出,相加就得到你的余额.', 256 | 257 | start: '开始同步', 258 | stopCheckMsg: '同步已经取消', 259 | checkedMsg: '同步完成' 260 | }, 261 | 262 | lang: { 263 | title: '选择语言', 264 | lang: '语言', 265 | select: '选择' 266 | }, 267 | 268 | gnode:{ 269 | title: '本地Grin节点', 270 | tabStatus: '运行状态', 271 | tabPeers: '周边节点', 272 | tabLog: '运行日志', 273 | tabConfig: '设置', 274 | statusRunning: '运行中,可用', 275 | statusSyncing: '正在同步区块', 276 | statusToStart: '没有启动', 277 | status: '状态', 278 | localRemoteHeight: '本地节点高度/全网高度', 279 | nodeVersion: '节点版本', 280 | protocolVersion: '协议版本', 281 | connectedCount: '已连接到周边Grin节点数量', 282 | location: '区块数据存储的位置', 283 | size: '全节点区块数据大小', 284 | restart: '重启本地节点', 285 | height: '最新高度', 286 | remove: '移除区块数据,重头开始同步', 287 | removeConfirm: '确认移除目前的Grin区块数据吗?' 288 | }, 289 | 290 | gnodeConfig:{ 291 | warning: '普通用户推荐使用默认值 !', 292 | useLocalorNot: '(推荐)启用本地节点', 293 | connectMethod: '节点连接方式', 294 | remoteFirst: '优先使用远程. 当远程节点不可用, 使用本地节点', 295 | localFirst: '(推荐)当本地节点同步完成时优先连接本地, 否则使用远程', 296 | remoteAllTime: '始终连接远程节点', 297 | localAllTime: '始终连接本地节点', 298 | background: '(推荐)钱包退出后,本地节点后台运行', 299 | restoreToDefault: '恢复默认值', 300 | saved: '设置已经保存, 重启钱包后生效.' 301 | }, 302 | 303 | gnodeConfigModal:{ 304 | config: '设置Grin本地节点', 305 | title: '设置Grin节点' 306 | }, 307 | 308 | tor:{ 309 | title: '洋葱网络(Tor)', 310 | tabStatus: '运行状态', 311 | tabLog: '运行日志', 312 | tabConfig: '设置', 313 | statusRunning: '洋葱网络运行中', 314 | statusFailed: '启动失败', 315 | statusStarting: '正在启动', 316 | statusToStart: '没有启动', 317 | status: '状态', 318 | start: '启动洋葱网络', 319 | restart: '重启洋葱网络', 320 | address: '洋葱网络(TOR)收币地址', 321 | 322 | tip: '收币时,请保持钱包在线.', 323 | tip2: '启动中...可能需要30-60秒', 324 | introTitle: '介绍', 325 | intro1: '洋葱网络(Tor)是一个自由开放的匿名通信网络。', 326 | intro2: '通过洋葱网络接收/发送 grin, 安全/匿名/去中心化。', 327 | intro3: '(如果你所在地区网络禁止了Tor,那么你需要设置HTTPS/SOCKS5代理来连接Tor。)', 328 | 329 | copy: '复制地址', 330 | copied: '地址已经复制到剪贴板', 331 | startWhenLaunch: '(推荐) 打开钱包,自动启动洋葱网络', 332 | proxy: '代理', 333 | proxyUser: '用户名', 334 | proxyPassword: '密码', 335 | proxyHelp: '通过代理连接Tor', 336 | optional: '可选', 337 | proxyHelp2: '代理示例: https://127.0.0.1:9009 或者 socks5://127.0.0.1:1080', 338 | 339 | error: '洋葱网络启动失败', 340 | errorNoCurl: '没有找到curl命令, 请先安装curl.', 341 | errorNoTor: '没有找到tor, 请先安装tor.', 342 | errorInvalidProxy: '无效代理', 343 | saved: '设置已经保存, 下次启动洋葱网络时生效.' 344 | }, 345 | 346 | gaddress:{ 347 | address: '你的 slatepack地址', 348 | copied: '已复制', 349 | copy: '复制 slatepack地址', 350 | tips: 'slatepack 地址 用于加密slatepack信息以及付款证明,并不会存储在Grin区块链上.' 351 | }, 352 | 353 | log: { 354 | title: '导出日志', 355 | saveMsg: '导出当天的Niffler钱包的日志', 356 | } 357 | 358 | } 359 | } 360 | export default messages 361 | -------------------------------------------------------------------------------- /src/main/index.dev.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is used specifically and only for development. It installs 3 | * `electron-debug` & `vue-devtools`. There shouldn't be any need to 4 | * modify this file, but it can be used to extend your development 5 | * environment. 6 | */ 7 | 8 | /* eslint-disable */ 9 | 10 | // Install `electron-debug` with `devtron` 11 | require('electron-debug')({ showDevTools: true }) 12 | 13 | // Install `vue-devtools` 14 | require('electron').app.on('ready', () => { 15 | let installExtension = require('electron-devtools-installer') 16 | installExtension.default(installExtension.VUEJS_DEVTOOLS) 17 | .then(() => {}) 18 | .catch(err => { 19 | console.log('Unable to install `vue-devtools`: \n', err) 20 | }) 21 | }) 22 | 23 | // Require `main` process to boot app 24 | require('./index') -------------------------------------------------------------------------------- /src/main/index.js: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow, dialog, shell } from 'electron' 2 | const Menu = require('electron').Menu 3 | const {ipcMain} = require('electron') 4 | 5 | import pkg from '../../package.json' 6 | import checkUpdate from '../shared/updateChecker' 7 | import {checkFirstTime} from '../shared/first' 8 | 9 | checkFirstTime() 10 | 11 | import log from '../shared/logger' 12 | import {downloadUrl, gnodeOption} from '../shared/config' 13 | 14 | /** 15 | * Set `__static` path to static files in production 16 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-static-assets.html 17 | */ 18 | if (process.env.NODE_ENV !== 'development') { 19 | global.__static = require('path').join(__dirname, '/static').replace(/\\/g, '\\\\') 20 | } 21 | 22 | //https://molunerfinn.com/electron-vue-5/#%E4%B8%8D%E5%90%8C%E5%B9%B3%E5%8F%B0%E7%9A%84%E6%9E%84%E5%BB%BA%E9%85%8D%E7%BD%AE 23 | if (process.platform === 'win32') { 24 | app.setAppUserModelId(pkg.build.appId) 25 | } 26 | 27 | log.debug(`Platform:${process.platform}`) 28 | 29 | import {exec, execFile, spawn, fork} from 'child_process' 30 | if(process.platform!=='win32'){ 31 | exec('pkill grin-wallet') 32 | }else{ 33 | exec('taskkill -f /im grin-wallet.exe') 34 | } 35 | 36 | if(!gnodeOption.useLocalGnode){ 37 | if(process.platform!=='win32'){ 38 | exec('pkill grin') 39 | }else{ 40 | exec('taskkill -f /im grin.exe') 41 | } 42 | } 43 | 44 | let mainWindow 45 | let firstQuit = true 46 | let firstClose = true 47 | 48 | const winURL = process.env.NODE_ENV === 'development' 49 | ? `http://localhost:9080` 50 | : `file://${__dirname}/index.html` 51 | 52 | function createWindow () { 53 | /** 54 | * Initial window options 55 | */ 56 | mainWindow = new BrowserWindow({ 57 | title: 'Niffler Wallet', 58 | center: true, 59 | height: 500, 60 | useContentSize: true, 61 | width: 600, 62 | resizable:false, 63 | webPreferences: { 64 | webSecurity: false 65 | } 66 | }) 67 | 68 | mainWindow.loadURL(winURL) 69 | 70 | if (process.platform != 'darwin') { 71 | mainWindow.on('close', (e)=>{ 72 | mainWindow.webContents.send('before-quit'); 73 | if(firstClose){ 74 | e.preventDefault() 75 | firstClose = false 76 | } 77 | 78 | setTimeout(()=>{ 79 | log.debug('close now') 80 | if(mainWindow)mainWindow.close()}, 800) 81 | }) 82 | 83 | mainWindow.setMenu(null) 84 | } 85 | 86 | mainWindow.on('closed', () => { 87 | log.debug('mainWidnows closed') 88 | mainWindow = null 89 | }) 90 | //log.debug('Mainwindows created') 91 | } 92 | 93 | async function launch(){ 94 | let toUpdate = await checkUpdate() 95 | if(toUpdate){ 96 | dialog.showMessageBox({ 97 | type: 'info', 98 | title: 'Found new version', 99 | buttons: ['Yes', 'No'], 100 | message: 'Found new version of Niffler wallet, go to download?', 101 | }, (res, checkboxChecked) => { 102 | if (res === 0) { 103 | shell.openExternal(downloadUrl) 104 | app.quit() 105 | }else{ 106 | createWindow() 107 | createMenu() 108 | }}) 109 | } 110 | } 111 | 112 | var shouldQuit = app.makeSingleInstance(function (commandLine, workingDirectory) { 113 | // Someone tried to run a second instance, we should focus our window. 114 | if (mainWindow) { 115 | if (mainWindow.isMinimized()) mainWindow.restore(); 116 | mainWindow.focus(); 117 | } 118 | }); 119 | 120 | if (shouldQuit) { 121 | app.quit(); 122 | } 123 | 124 | app.on('ready', ()=>{ 125 | createWindow() 126 | if (process.platform === 'darwin') { 127 | createMenu() 128 | } 129 | }) 130 | 131 | app.on('window-all-closed', () => { 132 | log.debug('window-all-closed') 133 | //if (process.platform !== 'darwin') { 134 | app.quit() 135 | //} 136 | }) 137 | 138 | app.on('before-quit', (event)=>{ 139 | log.debug('before-quit') 140 | 141 | if(mainWindow){ 142 | mainWindow.webContents.send('before-quit'); 143 | 144 | if(firstQuit){ 145 | event.preventDefault() 146 | firstQuit = false 147 | } 148 | 149 | setTimeout(()=>{ 150 | log.debug('quit now') 151 | app.quit()}, 800) 152 | } 153 | }) 154 | 155 | app.on('activate', () => { 156 | if (mainWindow === null) { 157 | createWindow() 158 | } 159 | }) 160 | 161 | /** 162 | * Auto Updater 163 | * 164 | * Uncomment the following code below and install `electron-updater` to 165 | * support auto updating. Code Signing with a valid certificate is required. 166 | * https://simulatedgreg.gitbooks.io/electron-vue/content/en/using-electron-builder.html#auto-updating 167 | */ 168 | 169 | /* 170 | import { autoUpdater } from 'electron-updater' 171 | 172 | autoUpdater.on('update-downloaded', () => { 173 | autoUpdater.quitAndInstall() 174 | }) 175 | 176 | app.on('ready', () => { 177 | if (process.env.NODE_ENV === 'production') autoUpdater.checkForUpdates() 178 | }) 179 | */ 180 | 181 | ipcMain.on('resize', (event, width, height) => { 182 | mainWindow.setSize(width, height) 183 | mainWindow.center() 184 | //log.debug(`mainWindow resized to ${width}:${height}`) 185 | }) 186 | 187 | ipcMain.on('quit', (event) => { 188 | app.quit() 189 | }) 190 | 191 | function createMenu() { 192 | const application = { 193 | label: "Application", 194 | submenu: [ 195 | { 196 | label: "About Application", 197 | selector: "orderFrontStandardAboutPanel:" 198 | }, 199 | { 200 | type: "separator" 201 | }, 202 | { 203 | label: "Undo", 204 | accelerator: "CmdOrCtrl+Z", 205 | selector: "undo:" 206 | }, 207 | { 208 | label: "Redo", 209 | accelerator: "Shift+CmdOrCtrl+Z", 210 | selector: "redo:" 211 | }, 212 | { 213 | label: "Cut", 214 | accelerator: "CmdOrCtrl+X", 215 | selector: "cut:" 216 | }, 217 | { 218 | label: "Copy", 219 | accelerator: "CmdOrCtrl+C", 220 | selector: "copy:" 221 | }, 222 | { 223 | label: "Paste", 224 | accelerator: "CmdOrCtrl+V", 225 | selector: "paste:" 226 | }, 227 | { 228 | type: "separator" 229 | }, 230 | { 231 | label: "Quit", 232 | accelerator: "Command+Q", 233 | click: () => { 234 | app.quit() 235 | } 236 | } 237 | ] 238 | } 239 | 240 | const template = [ 241 | application, 242 | ] 243 | 244 | Menu.setApplicationMenu(Menu.buildFromTemplate(template)) 245 | } -------------------------------------------------------------------------------- /src/renderer/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grinfans/Niffler/035f7089044589cb89759a12d0294e65532247e5/src/renderer/assets/.gitkeep -------------------------------------------------------------------------------- /src/renderer/assets/imgs/arrow-alt-circle-left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/assets/imgs/arrow-alt-circle-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grinfans/Niffler/035f7089044589cb89759a12d0294e65532247e5/src/renderer/assets/logo.png -------------------------------------------------------------------------------- /src/renderer/components/Check.vue: -------------------------------------------------------------------------------- 1 | 60 | 136 | 143 | -------------------------------------------------------------------------------- /src/renderer/components/Commit.vue: -------------------------------------------------------------------------------- 1 | 94 | 95 | 238 | 239 | 241 | -------------------------------------------------------------------------------- /src/renderer/components/Create.vue: -------------------------------------------------------------------------------- 1 | 70 | 71 | 149 | 150 | 153 | -------------------------------------------------------------------------------- /src/renderer/components/FileSend.vue: -------------------------------------------------------------------------------- 1 | 80 | 254 | 266 | -------------------------------------------------------------------------------- /src/renderer/components/Finalize.vue: -------------------------------------------------------------------------------- 1 | 68 | 242 | 258 | -------------------------------------------------------------------------------- /src/renderer/components/Gaddress.vue: -------------------------------------------------------------------------------- 1 | 34 | 81 | 83 | -------------------------------------------------------------------------------- /src/renderer/components/GnodeConfig.vue: -------------------------------------------------------------------------------- 1 | 70 | 71 | 128 | 130 | -------------------------------------------------------------------------------- /src/renderer/components/GnodeConfigModal.vue: -------------------------------------------------------------------------------- 1 | 18 | 44 | 46 | -------------------------------------------------------------------------------- /src/renderer/components/HedwigV1.vue: -------------------------------------------------------------------------------- 1 | 72 | 280 | 287 | -------------------------------------------------------------------------------- /src/renderer/components/HttpReceive.vue: -------------------------------------------------------------------------------- 1 | 72 | 207 | 214 | -------------------------------------------------------------------------------- /src/renderer/components/Landing.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 21 | 22 | 24 | -------------------------------------------------------------------------------- /src/renderer/components/Lang.vue: -------------------------------------------------------------------------------- 1 | 40 | 72 | 74 | -------------------------------------------------------------------------------- /src/renderer/components/Message.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 53 | 54 | 56 | -------------------------------------------------------------------------------- /src/renderer/components/New.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 77 | 80 | -------------------------------------------------------------------------------- /src/renderer/components/Receive.vue: -------------------------------------------------------------------------------- 1 | 82 | 266 | 282 | -------------------------------------------------------------------------------- /src/renderer/components/Remove.vue: -------------------------------------------------------------------------------- 1 | 52 | 108 | 115 | -------------------------------------------------------------------------------- /src/renderer/components/SummaryInfo.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 64 | 65 | 67 | -------------------------------------------------------------------------------- /src/renderer/components/Transaction.vue: -------------------------------------------------------------------------------- 1 | 89 | 90 | 253 | 255 | -------------------------------------------------------------------------------- /src/renderer/db.js: -------------------------------------------------------------------------------- 1 | 2 | let keyPostedUnconfirmed = 'posted_unconfirmed_txs' 3 | let keyLocalGnodeStatus = 'local_gnode_status' 4 | let keyGnodeLocation = 'local_gnode_location' 5 | let keySpendable = 'spendable' 6 | let keyLocalGnodeCheckable = 'local_gnode_checkable' 7 | let keyTorRunning = 'tor_running' 8 | 9 | class DBService{ 10 | static getPostedUnconfirmedTxs(){ 11 | let txs = localStorage.getItem(keyPostedUnconfirmed) 12 | if(txs === null){ 13 | return new Set() 14 | } 15 | return new Set(JSON.parse(txs)) 16 | } 17 | static setPostedUnconfirmedTxs(txs){ 18 | if(txs === null){ 19 | localStorage.removeItem(keyPostedUnconfirmed) 20 | }else{ 21 | let txs_ = [...txs] 22 | localStorage.setItem(keyPostedUnconfirmed, JSON.stringify(txs_)) 23 | } 24 | } 25 | static addPostedUnconfirmedTx(tx_id){ 26 | let txs = DBService.getPostedUnconfirmedTxs() 27 | txs.add(tx_id) 28 | DBService.setPostedUnconfirmedTxs(txs) 29 | } 30 | static deletePostedUnconfirmedTx(tx_id){ 31 | let txs = DBService.getPostedUnconfirmedTxs() 32 | txs.delete(tx_id) 33 | DBService.setPostedUnconfirmedTxs(txs) 34 | } 35 | 36 | static getSpendable(){ 37 | let amount = localStorage.getItem(keySpendable) 38 | if(amount){ 39 | return parseFloat(amount) 40 | } 41 | } 42 | static setSpendable(amount){ 43 | return localStorage.setItem(keySpendable, amount) 44 | } 45 | 46 | static setLocalGnodeStatus(status){ 47 | return localStorage.setItem(keyLocalGnodeStatus, status) 48 | } 49 | 50 | static getLocalGnodeStatus(){ 51 | return localStorage.getItem(keyLocalGnodeStatus) 52 | } 53 | 54 | static setGnodeLocation(location){ 55 | return localStorage.setItem(keyGnodeLocation, location) 56 | } 57 | 58 | static getGnodeLocation(){ 59 | return localStorage.getItem(keyGnodeLocation) 60 | } 61 | 62 | static setLocalGnodeCheckable(checkable){ 63 | return localStorage.setItem(keyLocalGnodeCheckable, JSON.stringify(checkable)) 64 | } 65 | 66 | static getLocalGnodeCheckable(){ 67 | return JSON.parse(localStorage.getItem(keyLocalGnodeCheckable)) 68 | } 69 | 70 | static setTorRunning(running){ 71 | return localStorage.setItem(keyTorRunning, JSON.stringify(running)) 72 | } 73 | 74 | static getTorRunning(){ 75 | return JSON.parse(localStorage.getItem(keyTorRunning)) 76 | } 77 | 78 | } 79 | 80 | export default DBService -------------------------------------------------------------------------------- /src/renderer/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import axios from 'axios' 3 | import VueI18n from 'vue-i18n' 4 | 5 | import App from './App' 6 | import 'bulma/css/bulma.css' 7 | import 'animate.css/animate.css' 8 | import 'bulma-pageloader/dist/css/bulma-pageloader.min.css' 9 | 10 | import { remote, ipcRenderer} from 'electron' 11 | 12 | import walletService from '../shared/wallet' 13 | import WalletServiceV3 from '../shared/walletv3' 14 | import gnodeService from '../shared/gnode' 15 | import {RemoteGnodeService} from '../shared/gnode' 16 | import log from '../shared/logger' 17 | import dbService from '@/db' 18 | import {startTor, stopTor }from '../shared/tor' 19 | 20 | import 'vue-awesome/icons' 21 | import Icon from 'vue-awesome/components/Icon' 22 | 23 | //https://github.com/electron-userland/electron-builder/issues/880#issuecomment-258602436 24 | process.env.PATH = process.env.PATH + ':/usr/local/bin'; 25 | 26 | Vue.component('icon', Icon) 27 | 28 | Vue.gnodeService = Vue.prototype.$gnodeService = gnodeService 29 | Vue.remoteGnodeService = Vue.prototype.$remoteGnodeService = RemoteGnodeService 30 | 31 | Vue.walletService = Vue.prototype.$walletService = walletService 32 | Vue.walletServiceV3 = Vue.prototype.$walletServiceV3 = WalletServiceV3 33 | 34 | Vue.dbService = Vue.prototype.$dbService = dbService 35 | Vue.log = Vue.prototype.$log = log 36 | 37 | import en from '../lang/en' 38 | import zh from '../lang/zh' 39 | import ru from '../lang/ru' 40 | import ko from '../lang/ko' 41 | import fr from '../lang/fr' 42 | import nl from '../lang/nl' 43 | 44 | Vue.use(VueI18n) 45 | const messages = { 46 | en, 47 | ru, 48 | zh, 49 | ko, 50 | fr, 51 | nl 52 | } 53 | 54 | import {locale, gnodeOption} from '../shared/config' 55 | const i18n = new VueI18n({ 56 | locale: locale, 57 | //locale: 'ru', 58 | messages 59 | }) 60 | 61 | remote.globalShortcut.register('CommandOrControl+Shift+K', () => { 62 | remote.BrowserWindow.getFocusedWindow().webContents.openDevTools() 63 | }) 64 | 65 | window.addEventListener('beforeunload', () => { 66 | remote.globalShortcut.unregisterAll() 67 | }) 68 | 69 | if (!process.env.IS_WEB) Vue.use(require('vue-electron')) 70 | Vue.use(require('vue-moment')) 71 | var VueTruncate = require('vue-truncate-filter') 72 | Vue.use(VueTruncate) 73 | 74 | Vue.http = Vue.prototype.$http = axios 75 | Vue.config.productionTip = false 76 | 77 | /* eslint-disable no-new */ 78 | new Vue({ 79 | components: { App }, 80 | template: '', 81 | i18n 82 | }).$mount('#app') 83 | 84 | import { messageBus } from '@/messagebus' 85 | 86 | ipcRenderer.on('before-quit', ()=>{ 87 | log.debug('Render got msg is about to quit.') 88 | messageBus.$emit('quit') 89 | walletService.stopAll() 90 | }) 91 | 92 | if(gnodeOption.useLocalGnode){ 93 | gnodeService.getStatus().then().catch((err)=>{ 94 | log.debug('Get local node status failed: ' + err) 95 | log.debug('Try to start local grin node.') 96 | gnodeService.startGnode()}) 97 | } 98 | 99 | //startTor() 100 | -------------------------------------------------------------------------------- /src/renderer/messagebus.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export var messageBus = new Vue() -------------------------------------------------------------------------------- /src/renderer/mixin.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mounted () { 3 | this.disableDragEvent() 4 | }, 5 | 6 | methods: { 7 | disableDragEvent () { 8 | window.addEventListener('dragenter', this.disableDrag, false) 9 | window.addEventListener('dragover', this.disableDrag) 10 | window.addEventListener('drop', this.disableDrag) 11 | }, 12 | disableDrag (e) { 13 | const dropzone = document.getElementById('filebox') // 这个是可拖拽的上传区 14 | const dropzone2 = document.getElementById('filebox2') // 这个是可拖拽的上传区 15 | if ((dropzone&&dropzone.contains(e.target)) || (dropzone2&&dropzone2.contains(e.target))) 16 | return 17 | else{ 18 | e.preventDefault() 19 | e.dataTransfer.effectAllowed = 'none' 20 | e.dataTransfer.dropEffect = 'none' 21 | } 22 | }, 23 | beforeDestroy () { 24 | window.removeEventListener('dragenter', this.disableDrag, false) 25 | window.removeEventListener('dragover', this.disableDrag) 26 | window.removeEventListener('drop', this.disableDrag) 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/shared/config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | import path from 'path'; 3 | import { app, remote } from 'electron'; 4 | import os from 'os' 5 | 6 | let appRootDir = require('app-root-dir').get().replace('app.asar', '').replace(/(\s+)/g, '\\$1'); 7 | export const rootDir = require('app-root-dir').get() 8 | function getPlatform(){ 9 | switch (os.platform()) { 10 | case 'aix': 11 | case 'freebsd': 12 | case 'linux': 13 | case 'openbsd': 14 | case 'android': 15 | return 'linux'; 16 | case 'darwin': 17 | case 'sunos': 18 | return 'mac'; 19 | case 'win32': 20 | return 'win'; 21 | } 22 | } 23 | 24 | export const platform = getPlatform() 25 | 26 | const IS_PROD = process.env.NODE_ENV === 'production'; 27 | const root = process.cwd(); 28 | const APP = process.type === 'renderer' ? remote.app : app 29 | 30 | const binariesPath = 31 | IS_PROD || APP.isPackaged 32 | ? path.join(process.resourcesPath, 'bin', platform) 33 | : path.join(root, 'resources', 'bin', platform); 34 | 35 | const grinWalletBinaries = platform==='win'?'grin-wallet.exe':'grin-wallet' 36 | const grinBinaries = platform==='win'?'grin.exe':'grin' 37 | //const torBinaries = platform==='win'?'tor.exe':'tor' 38 | 39 | export let grinWalletPath = path.join(binariesPath, grinWalletBinaries) 40 | export let grinPath = path.join(binariesPath, grinBinaries) 41 | //export let torPath = path.join(binariesPath, torBinaries) 42 | 43 | if(platform=='win'){ 44 | grinWalletPath = '"' + path.resolve(grinWalletPath) + '"' 45 | grinPath = '"' + path.resolve(grinPath) + '"' 46 | } 47 | 48 | export const chainType = 'main' 49 | export const chain = 'Mainnet' 50 | export const walletTOMLPath = path.join(APP.getPath('home'), '.grin', chainType, 'grin-wallet.toml') 51 | export const nodeTOMLPath = path.join(APP.getPath('home'), '.grin', chainType, 'grin-server.toml') 52 | 53 | function getOwnerApiSecret(){ 54 | if(fs.existsSync(walletTOMLPath)){ 55 | const re = /api_secret_path\s*=\s*"(.\S+)"/ 56 | let c = fs.readFileSync(walletTOMLPath).toString() 57 | const found = c.match(re) 58 | if(found)return found[1] 59 | } 60 | return path.join(APP.getPath('home'), '.grin', chainType, '.owner_api_secret') 61 | } 62 | 63 | function getNodeApiSecret(){ 64 | if(fs.existsSync(nodeTOMLPath)){ 65 | const re = /api_secret_path\s*=\s*"(.\S+)"/ 66 | let c = fs.readFileSync(nodeTOMLPath).toString() 67 | const found = c.match(re) 68 | if(found)return found[1] 69 | } 70 | return path.join(APP.getPath('home'), '.grin', chainType, '.api_secret') 71 | } 72 | 73 | export const grinDIR = path.join(APP.getPath('home'), '.grin') 74 | export const seedPath = path.join(APP.getPath('home'), '.grin', chainType, 'wallet_data/wallet.seed') 75 | export const walletPath = path.join(APP.getPath('home'), '.grin', chainType) 76 | export const walletDataPath = path.join(APP.getPath('home'), '.grin', chainType, 'wallet_data') 77 | export const walletConfigPath = path.join(APP.getPath('home'), '.grin', chainType, 'grin-wallet.toml') 78 | export const walletLogPath = path.join(APP.getPath('home'), '.grin', chainType, 'grin-wallet.log') 79 | export const nodeApiSecretPath = getNodeApiSecret() 80 | export const ownerApiSecretPath = getOwnerApiSecret() 81 | export const grinNodeLog = path.join(APP.getPath('home'), '.grin', chainType, 'grin-server.log') 82 | export const chainDataPath = path.join(APP.getPath('home'), '.grin', chainType, 'chain_data') 83 | 84 | export const nifflerPath = path.join(APP.getPath('home'), '.niffler') 85 | export const logDir = path.join(nifflerPath, 'log') 86 | export const tempTxDir = path.join(nifflerPath, 'temp_tx') 87 | export const configPath = path.join(nifflerPath, 'config.json') 88 | export const torDir = path.join(nifflerPath, 'tor') 89 | export const torConfigPath = path.join(nifflerPath, 'tor', 'torrc') 90 | export const torDataPath = path.join(nifflerPath, 'tor', 'data') 91 | export const torHSDataPath = path.join(nifflerPath, 'tor', 'hs_data') 92 | export const torLogPath = path.join(nifflerPath, 'tor', 'tor.log') 93 | export const slatepackDir = path.join(nifflerPath, 'slatepack') 94 | 95 | export const releaseUrl = 'https://api.github.com/repos/grinfans/niffler/releases/latest' 96 | export const downloadUrl = 'https://github.com/grinfans/niffler/releases/latest' 97 | 98 | export function getConfig(){ 99 | try{ 100 | return JSON.parse(fs.readFileSync(configPath)) 101 | }catch (e) { 102 | return {} 103 | } 104 | } 105 | 106 | function setConfig(options){ 107 | return fs.writeFileSync(configPath, JSON.stringify(options)) 108 | } 109 | 110 | export function updateConfig(options){ 111 | let options_ = getConfig() 112 | for(var key in options){ 113 | options_[key] = options[key] 114 | } 115 | setConfig(options_) 116 | } 117 | 118 | //export const logLevel = getConfig()['debug']?'debug':'info' 119 | export const logLevel = 'debug' 120 | 121 | export const hedwigServer = 'https://v1.hedwig.im' 122 | export const hedwigClient = 123 | IS_PROD || APP.isPackaged 124 | ? path.resolve(path.join(process.resourcesPath, 'bin', 'hedwig', 'client.js')) 125 | : path.resolve(path.join(root, 'hedwig', 'client.js')) 126 | 127 | export const hedwigApp = 'Niffler' 128 | 129 | function getLocale(){ 130 | let locale = getConfig()['locale'] 131 | if(locale)return locale 132 | locale = APP.getLocale().toLowerCase() 133 | if(locale.startsWith('zh'))return 'zh' 134 | if(locale.startsWith('ru'))return 'ru' 135 | if(locale.startsWith('fr'))return 'fr' 136 | if(locale.startsWith('nl'))return 'nl' 137 | return 'en' 138 | } 139 | export function setLocale(locale){ 140 | updateConfig({'locale':locale}) 141 | } 142 | export const locale = getLocale() 143 | export const langs = {'zh':'简体中文', 'en':'English', 'fr':'Français', 'nl':'Nederlands'} 144 | 145 | import pkg from '../../package.json' 146 | export const version = pkg.version 147 | 148 | export const defaultGnodeOptions= { 149 | 'useLocalGnode': true, 150 | //connnectMethod: localFirst, remoteFirst, localAllTime, remoteAllTime 151 | 'connectMethod':'localFirst', 152 | 'remoteAddr': 'http://grin-node.niffler.org:3413', 153 | 'localAddr': 'http://127.0.0.1:3413', 154 | 'background': false 155 | } 156 | 157 | let gnodeOption_ 158 | function upgradeGnodeOption(){ 159 | let c = getConfig() 160 | if(c && c.gnode){ 161 | let remote = c.gnode.remoteAddr 162 | if(remote && remote.search('grin3')!==-1){ 163 | gnodeOption_ = defaultGnodeOptions 164 | c['gnode'] = defaultGnodeOptions 165 | setConfig(c) 166 | } 167 | } 168 | } 169 | upgradeGnodeOption() 170 | if(!gnodeOption_){ 171 | gnodeOption_ = getConfig()['gnode']?getConfig()['gnode']: defaultGnodeOptions 172 | } 173 | export const gnodeOption = gnodeOption_ 174 | 175 | export const grinNode = gnodeOption.remoteAddr 176 | export const grinNode2 = gnodeOption.remoteAddr 177 | export const grinLocalNode = gnodeOption.localAddr 178 | 179 | export const defaultTorOptions= { 180 | 'startWhenLaunch': true, 181 | 'proxyHost':'', 182 | 'proxyType': '', 183 | 'proxyUser': '', 184 | 'proxyPassword': '', 185 | 'connectedOnce': false 186 | } 187 | export const torOptions = getConfig()['tor']?getConfig()['tor']: defaultTorOptions 188 | 189 | export const darkMode = getConfig()['darkMode']?getConfig()['darkMode']: false 190 | -------------------------------------------------------------------------------- /src/shared/first.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | import {defaultGnodeOptions, nifflerPath, logDir, getConfig, updateConfig, tempTxDir, slatepackDir} from './config'; 3 | 4 | export function checkFirstTime(){ 5 | console.log(nifflerPath) 6 | const isFirstTime = fs.existsSync(nifflerPath)?false:true 7 | if(isFirstTime){ 8 | fs.mkdirSync(nifflerPath) 9 | fs.mkdirSync(logDir) 10 | fs.mkdirSync(tempTxDir) 11 | fs.mkdirSync(slatepackDir) 12 | updateConfig({'firstTime':true}) 13 | updateConfig({'gnode': defaultGnodeOptions}) 14 | } 15 | else{ 16 | updateConfig({'firstTime':false}) 17 | } 18 | } 19 | 20 | export function isFirstTime(){ 21 | return getConfig()['firstTime'] 22 | } 23 | 24 | -------------------------------------------------------------------------------- /src/shared/gnode.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import {exec, execFile} from 'child_process' 3 | 4 | import axios from 'axios' 5 | require('promise.prototype.finally').shim(); 6 | 7 | import log from './logger' 8 | import {gnodeOption, grinPath, nodeApiSecretPath, nodeTOMLPath, platform, grinNode, chainDataPath} from './config' 9 | import { messageBus } from '../renderer/messagebus' 10 | 11 | let client 12 | let clietForRemote 13 | let gnodeProcess 14 | 15 | // https://github.com/mimblewimble/grin-rfcs/blob/master/text/0007-node-api-v2.md 16 | const gnodeHost = 'http://localhost:3413' 17 | const jsonRPCUrl = 'http://localhost:3413/v2/owner' 18 | const jsonRPCForeignUrl = 'http://localhost:3413/v2/foreign' 19 | 20 | const gnodeRemoteHost = grinNode 21 | 22 | function disableTUI(){ 23 | const re = /run_tui(\s)*=(\s)*true/ 24 | if(fs.existsSync(nodeTOMLPath)){ 25 | let c = fs.readFileSync(nodeTOMLPath).toString() 26 | if(c.search(re) != -1){ 27 | log.debug('Disable tui.') 28 | c = c.replace(re, 'run_tui = false') 29 | fs.writeFileSync(nodeTOMLPath, c) 30 | } 31 | } 32 | } 33 | 34 | function enableArchiveMode(){ 35 | const re = /archive_mode(\s)*=(\s)*false/ 36 | if(fs.existsSync(nodeTOMLPath)){ 37 | let c = fs.readFileSync(nodeTOMLPath).toString() 38 | if(c.search(re) != -1){ 39 | log.debug('enable archive_mode.') 40 | c = c.replace(re, 'archive_mode = true') 41 | fs.writeFileSync(nodeTOMLPath, c) 42 | } 43 | } 44 | } 45 | 46 | class GnodeService { 47 | static initClient() { 48 | if(fs.existsSync(nodeApiSecretPath)){ 49 | client = axios.create({ 50 | baseURL: gnodeHost, 51 | auth: { 52 | username: 'grin', 53 | password: fs.readFileSync(nodeApiSecretPath).toString() 54 | }, 55 | timeout: 2500, 56 | }) 57 | }else{ 58 | client = axios.create({baseURL: gnodeHost, timeout: 2500}) 59 | } 60 | } 61 | 62 | static jsonRPC(method, params, isForeign){ 63 | const headers = { 64 | 'Content-Type': 'application/json' 65 | } 66 | const body = { 67 | jsonrpc: "2.0", 68 | id: +new Date(), 69 | method: method, 70 | params: params, 71 | } 72 | const url = isForeign?jsonRPCForeignUrl:jsonRPCUrl 73 | return client.post(url, body, headers) 74 | } 75 | 76 | static getStatus(){ 77 | return GnodeService.jsonRPC('get_status', [], false) 78 | } 79 | 80 | static getConnectedPeers(){ 81 | return GnodeService.jsonRPC('get_connected_peers', [], false) 82 | } 83 | 84 | static startGnode(){ 85 | disableTUI() 86 | //enableArchiveMode() 87 | if(platform === 'linux'){ 88 | gnodeProcess = execFile(grinPath) 89 | }else{ 90 | gnodeProcess = exec(grinPath) 91 | } 92 | log.debug('gnodeProcess PID: ' + gnodeProcess.pid) 93 | if(platform==='win'){ 94 | localStorage.setItem('gnodeProcessPID', gnodeProcess.pid) 95 | } 96 | gnodeProcess.stderr.on('data', (data) => { 97 | log.error('start grin node got stderr: ' + data) 98 | }) 99 | messageBus.$emit('gnodeStarting') 100 | } 101 | 102 | static stopGnode(){ 103 | let pidName = 'gnodeProcessPID' 104 | const pid = localStorage.getItem(pidName) 105 | log.debug(`try to kill gnode process with pid (get from ${pidName}) : ${pid}`) 106 | localStorage.removeItem(pidName) 107 | 108 | if(platform==='win'&&pid){ 109 | return exec(`taskkill /pid ${pid} /f /t`) 110 | } 111 | 112 | if(gnodeProcess){ 113 | gnodeProcess.kill('SIGKILL') 114 | log.debug("killing gnodeProcess by gnodeProcess.kill('SIGKILL'). ") 115 | } 116 | if(pid) { 117 | try{ 118 | process.kill(pid, 'SIGKILL') 119 | }catch(e){ 120 | log.error(`error when kill ${processName} ${pid}: ${e}` ) 121 | } 122 | } 123 | } 124 | 125 | static stopGnode2(){ 126 | if(process.platform!=='win32'){ 127 | exec('pkill grin$') 128 | }else{ 129 | exec('taskkill -f /im grin.exe') 130 | } 131 | } 132 | 133 | static restartGnode(){ 134 | GnodeService.stopGnode2() 135 | setTimeout(()=>GnodeService.startGnode(), 2500) 136 | } 137 | 138 | static removeChainData(){ 139 | fse.removeSync(chainDataPath) 140 | } 141 | } 142 | GnodeService.initClient() 143 | export default GnodeService 144 | const remoteJsonRPCUrl = `${gnodeRemoteHost}/v2/owner` 145 | const retomteJsonRPCForeignUrl = `${gnodeRemoteHost}/v2/foreign` 146 | 147 | export class RemoteGnodeService{ 148 | static jsonRPC(method, params, isForeign){ 149 | const headers = { 150 | 'Content-Type': 'application/json' 151 | } 152 | const body = { 153 | jsonrpc: "2.0", 154 | id: +new Date(), 155 | method: method, 156 | params: params, 157 | } 158 | const url = isForeign?retomteJsonRPCForeignUrl:remoteJsonRPCUrl 159 | return axios.post(url, body, headers) 160 | } 161 | 162 | static getStatus(){ 163 | return RemoteGnodeService.jsonRPC('get_status', [], false) 164 | } 165 | } -------------------------------------------------------------------------------- /src/shared/logger.js: -------------------------------------------------------------------------------- 1 | import log from 'electron-log' 2 | import { join } from 'path' 3 | import {logDir, logLevel} from './config' 4 | const moment = require('moment'); 5 | 6 | export let logFile = `${moment(new Date()).format('YYYY_MM_DD')}.log` 7 | export let logPath = join(logDir, logFile) 8 | 9 | log.transports.file.file = logPath 10 | log.transports.file.format = '{y}-{m}-{d} {h}:{i}:{s}:{ms} [{level}] {text}' 11 | log.transports.file.maxSize = 5 * 1024 * 1024 12 | log.transports.file.level = logLevel 13 | 14 | log.transports.console.format = '{y}-{m}-{d} {h}:{i}:{s}:{ms} [{level}] {text}' 15 | log.transports.console.level = process.env.NODE_ENV === 'production' ? 'info' : 'debug' 16 | 17 | export default log 18 | -------------------------------------------------------------------------------- /src/shared/tor.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const fse = require('fs-extra') 3 | import {exec, execFile, spawn, fork} from 'child_process' 4 | 5 | import {platform, torDir, torConfigPath, torDataPath, torHSDataPath, torLogPath} from './config' 6 | import log from './logger' 7 | 8 | let torProcess 9 | 10 | let torrc = `SOCKSPort 19050 11 | DataDirectory ${torDataPath} 12 | HiddenServiceDir ${torHSDataPath} 13 | HiddenServicePort 80 0.0.0.0:3415 14 | Log notice file ${torLogPath} 15 | ` 16 | function configureTor(proxyHost, proxyType, proxyUser, proxyPassword){ 17 | let torrc_ = torrc 18 | if(proxyType==='https'){ 19 | torrc_ = torrc + '\nHTTPSProxy ' + proxyHost 20 | if(proxyUser && proxyPassword){ 21 | torrc_ = torrc_ + '\nHTTPSProxyAuthenticator ' + proxyUser + ':' + proxyPassword 22 | } 23 | } 24 | if(proxyType==='socks5'){ 25 | torrc_ = torrc + '\nSocks5Proxy ' + proxyHost 26 | if(proxyUser && proxyPassword){ 27 | torrc_ = torrc_ + '\nSocks5ProxyUsername ' + proxyUser 28 | torrc_ = torrc_ + '\nSocks5ProxyPassword ' + proxyPassword 29 | } 30 | } 31 | fs.writeFileSync(torConfigPath, torrc_) 32 | } 33 | 34 | function ensureTorDir(){ 35 | if(fse.pathExistsSync(torDir))return 36 | 37 | fse.ensureDirSync(torDir) 38 | fse.ensureFileSync(torLogPath) 39 | 40 | if(platform !=='win'){ 41 | fse.ensureDirSync(torHSDataPath, {mode:0o700}) 42 | fse.ensureDirSync(torDataPath, {mode:0o700}) 43 | }else{ 44 | fse.ensureDirSync(torHSDataPath) 45 | fse.ensureDirSync(torDataPath) 46 | } 47 | } 48 | 49 | export function startTor(proxyHost='', proxyType='', proxyUser='', proxyPassword=''){ 50 | log.debug('try to start tor:' + proxyHost + proxyType + proxyUser + proxyPassword) 51 | 52 | ensureTorDir() 53 | configureTor(proxyHost, proxyType, proxyUser, proxyPassword) 54 | 55 | if(platform === 'linux'){ 56 | torProcess = execFile('tor', ['-f', torConfigPath]) 57 | }else{ 58 | torProcess = exec(`tor -f ${torConfigPath}`) 59 | } 60 | localStorage.setItem('torProcessPID', torProcess.pid) 61 | 62 | torProcess.stdout.on('data', (data)=>{ 63 | log.debug('tor process: ' + data) 64 | }) 65 | torProcess.stderr.on('data', (data) => { 66 | log.error('tor process (error): ' + data) 67 | }) 68 | } 69 | 70 | export function stopTor(){ 71 | 72 | const pid = localStorage.getItem('torProcessPID') 73 | localStorage.removeItem('torProcessPID') 74 | if(platform==='win'&&pid){ 75 | return exec(`taskkill /pid ${pid} /f /t`) 76 | } 77 | if(pid){ 78 | try{ 79 | process.kill(pid, 'SIGKILL') 80 | }catch(e){ 81 | log.error(`error when kill tor ${pid}: ${e}` ) 82 | } 83 | } 84 | 85 | if(torProcess)torProcess.kill('SIGKILL') 86 | } 87 | 88 | export function restartTor(proxyHost='', proxyType='', proxyUser='', proxyPassword=''){ 89 | stopTor() 90 | setTimeout(()=>startTor(proxyHost, proxyType, proxyUser, proxyPassword), 2.5*1000) 91 | } 92 | -------------------------------------------------------------------------------- /src/shared/updateChecker.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import {releaseUrl, version} from './config' 3 | import log from './logger' 4 | 5 | const checkUpdate = async () => { 6 | try{ 7 | const res = await axios.get(releaseUrl) 8 | if (res.status === 200) { 9 | const latest = res.data.name 10 | log.debug(`checkUpdate last version is: ${latest}`) 11 | const result = compareVersion2Update(version, latest) 12 | log.debug(`Update need? ${result}`) 13 | return result 14 | }else{ 15 | log.error(`checkUpdate resp error: ${res.status} (${res.data})`) 16 | return false 17 | } 18 | }catch(error){ 19 | log.error(`checkUpdate failed: ${error}`) 20 | return false 21 | } 22 | } 23 | 24 | // if true -> update else return false 25 | const compareVersion2Update = (current, latest) => { 26 | const currentVersion = current.split('.').map(item => parseInt(item)) 27 | const latestVersion = latest.split('.').map(item => parseInt(item)) 28 | 29 | for (let i = 0; i < 3; i++) { 30 | if (currentVersion[i] < latestVersion[i]) { 31 | return true 32 | } 33 | if (currentVersion[i] > latestVersion[i]) { 34 | return false 35 | } 36 | } 37 | return false 38 | } 39 | 40 | export default checkUpdate 41 | -------------------------------------------------------------------------------- /src/shared/utils.js: -------------------------------------------------------------------------------- 1 | import {exec, execFile, spawn, fork} from 'child_process' 2 | 3 | export function execPromise(command) { 4 | return new Promise(function(resolve, reject) { 5 | exec(command, (error, stdout, stderr) => { 6 | if (error) { 7 | reject(error.toString()); 8 | return; 9 | } 10 | resolve(stdout.trim()); 11 | }); 12 | }); 13 | } -------------------------------------------------------------------------------- /src/shared/walletv3.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | require('promise.prototype.finally').shim() 3 | const crypto = require('crypto') 4 | import fs from 'fs' 5 | import {ownerApiSecretPath, grinWalletPath, platform} from './config' 6 | import log from './logger' 7 | import {exec, execFile} from 'child_process' 8 | 9 | let client 10 | let ecdh 11 | let publicKey 12 | let sharedSecret //hex string 13 | let token 14 | 15 | //https://docs.rs/grin_wallet_api/4.0.0/grin_wallet_api/trait.OwnerRpc.html 16 | 17 | const walletHost = 'http://localhost:3420' 18 | const jsonRPCUrl = 'http://localhost:3420/v3/owner' 19 | 20 | function encrypt(key, text, nonce){ 21 | //const nonce = new Buffer(crypto.randomBytes(12), 'utf8') 22 | const cipher = crypto.createCipheriv('aes-256-gcm', key, nonce) 23 | const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]) 24 | const tag = cipher.getAuthTag() 25 | return Buffer.concat([encrypted, tag]).toString('base64') 26 | } 27 | 28 | function decrypt(key, data, nonce){ 29 | //key,nonce is all buffer type; data is base64-encoded string 30 | const data_ = Buffer.from(data, 'base64') 31 | const decipher = crypto.createDecipheriv('aes-256-gcm', key, nonce) 32 | const len = data_.length 33 | const tag = data_.slice(len-16, len) 34 | const text = data_.slice(0, len-16) 35 | decipher.setAuthTag(tag) 36 | const decrypted = decipher.update(text, 'binary', 'utf8') + decipher.final('utf8'); 37 | return decrypted 38 | } 39 | 40 | class WalletServiceV3 { 41 | static initClient() { 42 | if(fs.existsSync(ownerApiSecretPath)){ 43 | client = axios.create({ 44 | baseURL: walletHost, 45 | auth: { 46 | username: 'grin', 47 | password: fs.readFileSync(ownerApiSecretPath).toString() 48 | }, 49 | }) 50 | }else{ 51 | client = axios.create({baseURL: walletHost}) 52 | } 53 | } 54 | 55 | static post(method, params){ 56 | const headers = { 57 | 'Content-Type': 'application/json' 58 | } 59 | const body = { 60 | jsonrpc: '2.0', 61 | id: 1, 62 | method: method, 63 | params: params, 64 | } 65 | return client.post(jsonRPCUrl, body, headers) 66 | } 67 | 68 | static postEncrypted(method, params){ 69 | if(!sharedSecret)return 70 | const body = { 71 | jsonrpc: '2.0', 72 | id: 1, 73 | method: method, 74 | params: params, 75 | } 76 | //console.log('post body: ' + JSON.stringify(body)) 77 | const nonce = crypto.randomBytes(12) 78 | const key = Buffer.from(sharedSecret,'hex') 79 | const bodyEncrypted = encrypt(key, JSON.stringify(body), nonce) 80 | return new Promise((resolve, reject)=>{ 81 | WalletServiceV3.post('encrypted_response_v3', { 82 | 'nonce': nonce.toString('hex'), 83 | 'body_enc': bodyEncrypted 84 | }).then(res=>{ 85 | //console.log('postEncrypted return: ' + JSON.stringify(res.data)) 86 | const nonce2 = Buffer.from(res.data.result.Ok.nonce, 'hex') 87 | const data = Buffer.from(res.data.result.Ok.body_enc, 'base64') 88 | const decrypted = decrypt(key, data, nonce2) 89 | //console.log('decrypted:' + decrypted) 90 | resolve(JSON.parse(decrypted)) 91 | }).catch(err=>{ 92 | reject(err) 93 | }) 94 | }) 95 | } 96 | static initSecureAPI(){ 97 | ecdh = crypto.createECDH('secp256k1') 98 | ecdh.generateKeys() 99 | publicKey = ecdh.getPublicKey('hex', 'compressed') 100 | //console.log('publickey: ' + publicKey) 101 | return WalletServiceV3.post('init_secure_api', { 102 | 'ecdh_pubkey': publicKey 103 | }) 104 | } 105 | 106 | static initSharedSecret(){ 107 | return new Promise((resolve, reject)=>{ 108 | WalletServiceV3.initSecureAPI().then((res)=>{ 109 | //log.debug('initSharedSecret return: ' + JSON.stringify(res)) 110 | const remotePublicKey = res.data.result.Ok 111 | sharedSecret = ecdh.computeSecret(remotePublicKey, 'hex', 'hex') 112 | //console.log('sharedSecret: ' + sharedSecret) 113 | resolve(res) 114 | }).catch((err)=>{ 115 | reject(err) 116 | }) 117 | }) 118 | } 119 | 120 | static getTopLevelDirectory(){ 121 | return WalletServiceV3.postEncrypted('get_top_level_directory', {}) 122 | } 123 | 124 | static createConfig(chainType, walletConfig, loggingConfig, torConfig){ 125 | return WalletServiceV3.postEncrypted('create_config', { 126 | 'chain_type': chainType, 127 | 'wallet_config': walletConfig, 128 | 'logging_config': loggingConfig, 129 | 'tor_config': torConfig 130 | }) 131 | } 132 | 133 | static createWallet(name, mnemonic, mnemonicLength, password){ 134 | return WalletServiceV3.postEncrypted('create_wallet', { 135 | 'name': name, 136 | 'mnemonic': mnemonic, 137 | 'mnemonic_length': mnemonicLength, 138 | 'password': password 139 | }) 140 | } 141 | 142 | static openWallet(name, password){ 143 | return new Promise((resolve, reject)=>{ 144 | WalletServiceV3.postEncrypted('open_wallet', { 145 | 'name': name, 146 | 'password': password 147 | }).then((res)=>{ 148 | //log.debug('open_wallet return: ' + JSON.stringify(res)) 149 | token = res.result.Ok 150 | resolve(res) 151 | }).catch(err=>{ 152 | reject(err) 153 | }) 154 | }) 155 | } 156 | 157 | static isWalletOpened(){ 158 | if(token)return true 159 | return false 160 | } 161 | 162 | static getNodeHeight(){ 163 | if(!token)return 164 | return WalletServiceV3.postEncrypted('node_height', { 165 | 'token': token 166 | }) 167 | } 168 | 169 | static getAccounts(){ 170 | if(!token)return 171 | return WalletServiceV3.postEncrypted('accounts', { 172 | 'token': token 173 | }) 174 | } 175 | 176 | static getSummaryInfo(refresh_from_node, minimum_confirmations){ 177 | if(!token)return 178 | return WalletServiceV3.postEncrypted('retrieve_summary_info', { 179 | 'token': token, 180 | 'refresh_from_node': refresh_from_node, 181 | 'minimum_confirmations': minimum_confirmations 182 | }) 183 | } 184 | 185 | static getTransactions(refresh_from_node, tx_id, tx_slate_id){ 186 | if(!token)return 187 | return WalletServiceV3.postEncrypted('retrieve_txs', { 188 | 'token': token, 189 | 'refresh_from_node': refresh_from_node, 190 | "tx_id": tx_id, 191 | "tx_slate_id": tx_slate_id, 192 | }) 193 | } 194 | 195 | static getCommits(refresh_from_node, include_spent, tx_id){ 196 | if(!token)return 197 | return WalletServiceV3.postEncrypted('retrieve_outputs', { 198 | 'token': token, 199 | 'refresh_from_node': refresh_from_node, 200 | 'include_spent': include_spent, 201 | 'tx_id': tx_id 202 | }) 203 | } 204 | 205 | static cancelTransactions(tx_id, tx_slate_id){ 206 | if(!token)return 207 | return WalletServiceV3.postEncrypted('cancel_tx', { 208 | 'token': token, 209 | 'tx_id': tx_id, 210 | 'tx_slate_id': tx_slate_id 211 | }) 212 | } 213 | 214 | static issueSendTransaction(tx_data){ 215 | if(!token)return 216 | return WalletServiceV3.postEncrypted('init_send_tx', { 217 | 'token': token, 218 | 'args': tx_data 219 | }) 220 | } 221 | 222 | static lockOutputs(slate){ 223 | if(!token)return 224 | return WalletServiceV3.postEncrypted('tx_lock_outputs', { 225 | 'token': token, 226 | 'slate': slate 227 | }) 228 | } 229 | 230 | static finalizeTransaction(slate){ 231 | if(!token)return 232 | return WalletServiceV3.postEncrypted('finalize_tx', { 233 | 'token': token, 234 | 'slate': slate 235 | }) 236 | } 237 | 238 | static postTransaction(slate, is_fluff){ 239 | if(!token)return 240 | return WalletServiceV3.postEncrypted('post_tx', { 241 | 'token': token, 242 | 'slate': slate, 243 | 'fluff': is_fluff 244 | }) 245 | } 246 | 247 | static startUpdater(freq){ 248 | if(!token)return 249 | return WalletServiceV3.postEncrypted('start_updater', { 250 | 'token': token, 251 | 'frequency': freq, 252 | }) 253 | } 254 | 255 | static getUpdaterMessages(count){ 256 | if(!token)return 257 | return WalletServiceV3.postEncrypted('get_updater_messages', { 258 | 'count': count, 259 | }) 260 | } 261 | 262 | static getSlatepackAddress(derivation_index){ 263 | if(!token)return 264 | return WalletServiceV3.postEncrypted('get_slatepack_address', { 265 | 'token': token, 266 | 'derivation_index': derivation_index, 267 | }) 268 | } 269 | 270 | static createSlatepackMessage(slate, sender_index, recipients){ 271 | if(!token)return 272 | return WalletServiceV3.postEncrypted('create_slatepack_message', { 273 | 'token': token, 274 | 'sender_index': sender_index, 275 | 'recipients': recipients, 276 | 'slate': slate 277 | }) 278 | } 279 | 280 | static decodeSlatepackMessage(message, secret_indices){ 281 | if(!token)return 282 | return WalletServiceV3.postEncrypted('decode_slatepack_message', { 283 | 'token': token, 284 | 'message':message, 285 | 'secret_indices': secret_indices 286 | }) 287 | } 288 | 289 | static getSlateFromSlatepackMessage(message, secret_indices){ 290 | if(!token)return 291 | return WalletServiceV3.postEncrypted('slate_from_slatepack_message', { 292 | 'token': token, 293 | 'message':message, 294 | 'secret_indices': secret_indices 295 | }) 296 | } 297 | 298 | } 299 | 300 | WalletServiceV3.initClient() 301 | export default WalletServiceV3 -------------------------------------------------------------------------------- /static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grinfans/Niffler/035f7089044589cb89759a12d0294e65532247e5/static/.gitkeep -------------------------------------------------------------------------------- /static/grin_mw_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grinfans/Niffler/035f7089044589cb89759a12d0294e65532247e5/static/grin_mw_logo.png -------------------------------------------------------------------------------- /static/grin_mw_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | --------------------------------------------------------------------------------