Upgrade electron and all dependencies

aftersignhook

Add privacy level

List U addrs

small fixes

s
This commit is contained in:
Aditya Kulkarni 2022-06-17 10:07:54 -05:00
parent 6d497782f5
commit 019c79e219
157 changed files with 11672 additions and 17619 deletions

View File

@ -1,52 +0,0 @@
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
.eslintcache
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
# OSX
.DS_Store
# flow-typed
flow-typed/npm/*
!flow-typed/npm/module_vx.x.x.js
# App packaged
release
app/main.prod.js
app/main.prod.js.map
app/renderer.prod.js
app/renderer.prod.js.map
app/style.css
app/style.css.map
dist
dll
main.js
main.js.map
.idea
npm-debug.log.*
.*.dockerfile

View File

@ -1,12 +0,0 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

View File

@ -1,56 +0,0 @@
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
.eslintcache
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
# OSX
.DS_Store
# flow-typed
flow-typed/npm/*
!flow-typed/npm/module_vx.x.x.js
# App packaged
release
app/main.prod.js
app/main.prod.js.map
app/renderer.prod.js
app/renderer.prod.js.map
app/style.css
app/style.css.map
dist
dll
main.js
main.js.map
.idea
npm-debug.log.*
__snapshots__
# Package.json
package.json
.travis.yml

View File

@ -1,21 +0,0 @@
module.exports = {
extends: 'erb',
settings: {
'import/resolver': {
webpack: {
config: require.resolve('./configs/webpack.config.eslint.js')
}
}
},
overrides: [
{
files: [
"**/*.test.js"
],
env: {
jest: true // now **/*.test.js files' env has both es6 *and* jest
},
plugins: ["jest"],
}
]
}

View File

@ -1,26 +0,0 @@
[ignore]
<PROJECT_ROOT>/app/main.prod.js
<PROJECT_ROOT>/app/main.prod.js.map
<PROJECT_ROOT>/app/dist/.*
<PROJECT_ROOT>/resources/.*
<PROJECT_ROOT>/node_modules/webpack-cli
<PROJECT_ROOT>/release/.*
<PROJECT_ROOT>/dll/.*
<PROJECT_ROOT>/release/.*
<PROJECT_ROOT>/git/.*
[include]
[libs]
[options]
esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable
esproposal.export_star_as=enable
module.name_mapper.extension='css' -> '<PROJECT_ROOT>/internals/flow/CSSModule.js.flow'
module.name_mapper.extension='styl' -> '<PROJECT_ROOT>/internals/flow/CSSModule.js.flow'
module.name_mapper.extension='scss' -> '<PROJECT_ROOT>/internals/flow/CSSModule.js.flow'
module.name_mapper.extension='png' -> '<PROJECT_ROOT>/internals/flow/WebpackAsset.js.flow'
module.name_mapper.extension='jpg' -> '<PROJECT_ROOT>/internals/flow/WebpackAsset.js.flow'
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue

11
.gitattributes vendored
View File

@ -1,11 +0,0 @@
* text eol=lf
*.png binary
*.jpg binary
*.jpeg binary
*.ico binary
*.icns binary
zcashd binary
zcash-cli binary
zcashd.exe binary
zcash-cli.exe binary

65
.gitignore vendored
View File

@ -1,53 +1,24 @@
# Logs
logs
*.log
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# Runtime data
pids
*.pid
*.seed
# dependencies
/node_modules
/.pnp
.pnp.js
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# testing
/coverage
# Coverage directory used by tools like istanbul
coverage
# production
/build
/dist
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
.eslintcache
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
# OSX
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
# flow-typed
flow-typed/npm/*
!flow-typed/npm/module_vx.x.x.js
# App packaged
release
app/main.prod.js
app/main.prod.js.map
app/renderer.prod.js
app/renderer.prod.js.map
app/style.css
app/style.css.map
dist
dll
main.js
main.js.map
.idea
npm-debug.log.*
app/zcashd
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@ -1,12 +0,0 @@
{
"overrides": [
{
"files": [".prettierrc", ".babelrc", ".eslintrc", ".stylelintrc"],
"options": {
"parser": "json"
}
}
],
"singleQuote": true,
"printWidth": 120
}

View File

@ -1,3 +0,0 @@
{
"extends": ["stylelint-config-standard", "stylelint-config-prettier"]
}

View File

@ -1,20 +0,0 @@
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/home/adityapk/Qt/5.13.0/gcc_64/include",
"/home/adityapk/Qt/5.13.0/gcc_64/include/QtCore",
"/home/adityapk/Qt/5.13.0/gcc_64/include/QtWidgets",
"/home/adityapk/Qt/5.13.0/gcc_64/include/QtGui"
],
"defines": [],
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "clang-x64"
}
],
"version": 4
}

View File

@ -3,6 +3,5 @@
"dbaeumer.vscode-eslint",
"dzannotti.vscode-babel-coloring",
"EditorConfig.EditorConfig",
"flowtype.flow-for-vscode"
]
}

View File

@ -11,12 +11,10 @@
".flowconfig": "ignore"
},
"javascript.validate.enable": false,
"javascript.validate.enable": true,
"javascript.format.enable": false,
"typescript.validate.enable": false,
"typescript.validate.enable": true,
"typescript.format.enable": false,
"flow.useNPMPackagedFlow": true,
"search.exclude": {
".git": true,
".eslintcache": true,

View File

@ -27,9 +27,8 @@ module.exports = async function(params) {
appleId: process.env.appleId,
appleIdPassword: process.env.appleIdPassword
});
console.log(`Done notarizing ${appId}`);
} catch (error) {
console.error(error);
}
console.log(`Done notarizing ${appId}`);
};

View File

@ -1,144 +0,0 @@
/*
* @NOTE: Prepend a `~` to css file paths that are in your node_modules
* See https://github.com/webpack-contrib/sass-loader#imports
*/
@import '~@fortawesome/fontawesome-free/css/all.css';
@import '~typeface-roboto/index.css';
html {
overflow: hidden;
}
body {
position: relative;
color: white;
height: 100vh;
background-color: #212124;
font-family: Roboto, Arial, Helvetica, Helvetica Neue, serif;
}
::-webkit-scrollbar {
width: 0.5em;
}
::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px grey;
background-color: rgb(1, 1, 1);
}
::-webkit-scrollbar-thumb {
background-color: white;
outline: 1px solid slategrey;
}
h2 {
margin: 0;
font-size: 2.25rem;
font-weight: bold;
letter-spacing: -0.025em;
color: #fff;
}
p {
font-size: 12px;
}
li {
list-style: none;
}
a {
color: white;
text-decoration: none;
}
input[disabled] {
color: grey;
}
.react-tabs {
-webkit-tap-highlight-color: transparent;
}
.react-tabs__tab-list {
border-bottom: 1px solid #aaa;
margin: 0 0 10px;
padding: 0;
}
.react-tabs__tab {
display: inline-block;
border: 1px solid transparent;
border-bottom: none;
bottom: -1px;
position: relative;
list-style: none;
padding: 6px 12px;
cursor: pointer;
font-size: 16px;
}
.react-tabs__tab--selected {
background: #c3921f;
color: black;
border-radius: 5px 5px 0 0;
}
.react-tabs__tab--disabled {
color: grey;
cursor: default;
}
.react-tabs__tab:focus {
box-shadow: 0 0 5px hsl(208, 99%, 50%);
border-color: hsl(208, 99%, 50%);
outline: none;
}
.react-tabs__tab:focus::after {
content: '';
position: absolute;
height: 5px;
left: -4px;
right: -4px;
bottom: -5px;
background: #fff;
}
.react-tabs__tab-panel {
display: none;
}
.react-tabs__tab-panel--selected {
display: block;
}
img,
img::after,
img::before {
-webkit-user-select: none;
-webkit-user-drag: none;
-webkit-app-region: no-drag;
cursor: default;
}
a,
a::after,
a::before {
-webkit-user-select: none;
-webkit-user-drag: none;
-webkit-app-region: no-drag;
cursor: default;
}
a:hover {
opacity: 0.8;
text-decoration: none;
cursor: pointer;
}
input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}

View File

@ -1,48 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Zecwallet Fullnode</title>
<!-- Allow unsafe only for inline script tags, no external content-->
<script nonce="1u1na">
if (process.env.NODE_ENV === 'Production') {
document.write(
`<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline'; style-src 'self'; " >`
);
}
(() => {
if (!process.env.START_HOT) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = './dist/style.css';
// HACK: Writing the script path should be done with webpack
document.getElementsByTagName('head')[0].appendChild(link);
}
})();
</script>
</head>
<body>
<div id="root"></div>
<script nonce="8SHwb">
{
const scripts = [];
// Dynamically insert the DLL script in development env in the
// renderer process
if (process.env.NODE_ENV === 'development') {
scripts.push('../dll/renderer.dev.dll.js');
}
// Dynamically insert the bundled app script in the renderer process
const port = process.env.PORT || 1212;
scripts.push(
process.env.START_HOT ? `http://localhost:${port}/dist/renderer.dev.js` : './dist/renderer.prod.js'
);
document.write(scripts.map(script => `<script defer src="${script}"><\/script>`).join(''));
}
</script>
</body>
</html>

Binary file not shown.

View File

@ -1,505 +0,0 @@
/* eslint-disable flowtype/no-weak-types */
/* eslint-disable max-classes-per-file */
/* eslint-disable class-methods-use-this */
import hex from 'hex-string';
import _sodium from 'libsodium-wrappers-sumo';
import Store from 'electron-store';
import WebSocket from 'ws';
import AppState, { ConnectedCompanionApp } from './components/AppState';
import Utils from './utils/utils';
// Wormhole code is sha256(sha256(secret_key))
function getWormholeCode(keyHex: string, sodium: any): string {
const key = sodium.from_hex(keyHex);
const pass1 = sodium.crypto_hash_sha256(key);
const pass2 = sodium.to_hex(sodium.crypto_hash_sha256(pass1));
return pass2;
}
// A class that connects to wormhole given a secret key
class WormholeClient {
keyHex: string;
wormholeCode: string;
sodium: any;
wss: WebSocket = null;
listner: CompanionAppListener = null;
keepAliveTimerID: TimerID = null;
constructor(keyHex: string, sodium: any, listner: CompanionAppListener) {
this.keyHex = keyHex;
this.sodium = sodium;
this.listner = listner;
this.wormholeCode = getWormholeCode(keyHex, this.sodium);
this.connect();
}
connect() {
this.wss = new WebSocket('wss://wormhole.zecqtwallet.com:443');
this.wss.on('open', () => {
// On open, register ourself
const reg = { register: getWormholeCode(this.keyHex, this.sodium) };
// No encryption for the register call
this.wss.send(JSON.stringify(reg));
// Now, do a ping every 4 minutes to keep the connection alive.
this.keepAliveTimerID = setInterval(() => {
const ping = { ping: 'ping' };
this.wss.send(JSON.stringify(ping));
}, 4 * 60 * 1000);
});
this.wss.on('message', data => {
this.listner.processIncoming(data, this.keyHex, this.wss);
});
this.wss.on('close', (code, reason) => {
console.log('Socket closed for ', this.keyHex, code, reason);
});
this.wss.on('error', (ws, err) => {
console.log('ws error', err);
});
}
getKeyHex(): string {
return this.keyHex;
}
close() {
if (this.keepAliveTimerID) {
clearInterval(this.keepAliveTimerID);
}
// Close the websocket.
if (this.wss) {
this.wss.close();
}
}
}
// The singleton Companion App listener, that can spawn a wormhole server
// or (multiple) wormhole clients
export default class CompanionAppListener {
sodium = null;
fnGetState: () => AppState = null;
fnSendTransaction: ([], (string, string) => void) => void = null;
fnUpdateConnectedClient: (string, number) => void = null;
permWormholeClient: WormholeClient = null;
tmpWormholeClient: WormholeClient = null;
constructor(
fnGetSate: () => AppState,
fnSendTransaction: ([], (string, string) => void) => void,
fnUpdateConnectedClient: (string, number) => void
) {
this.fnGetState = fnGetSate;
this.fnSendTransaction = fnSendTransaction;
this.fnUpdateConnectedClient = fnUpdateConnectedClient;
}
async setUp() {
await _sodium.ready;
this.sodium = _sodium;
// Create a new wormhole listner
const permKeyHex = this.getEncKey();
if (permKeyHex) {
this.permWormholeClient = new WormholeClient(permKeyHex, this.sodium, this);
}
// At startup, set the last client name/time by loading it
const store = new Store();
const name = store.get('companion/name');
const lastSeen = store.get('companion/lastseen');
if (name && lastSeen) {
const o = new ConnectedCompanionApp();
o.name = name;
o.lastSeen = lastSeen;
this.fnUpdateConnectedClient(o);
}
}
createTmpClient(keyHex: string) {
if (this.tmpWormholeClient) {
this.tmpWormholeClient.close();
}
this.tmpWormholeClient = new WormholeClient(keyHex, this.sodium, this);
}
closeTmpClient() {
if (this.tmpWormholeClient) {
this.tmpWormholeClient.close();
this.tmpWormholeClient = null;
}
}
replacePermClientWithTmp() {
if (this.permWormholeClient) {
this.permWormholeClient.close();
}
// Replace the stored code with the new one
this.permWormholeClient = this.tmpWormholeClient;
this.tmpWormholeClient = null;
this.setEncKey(this.permWormholeClient.getKeyHex());
// Reset local nonce
const store = new Store();
store.delete('companion/localnonce');
}
processIncoming(data: string, keyHex: string, ws: Websocket) {
const dataJson = JSON.parse(data);
// If the wormhole sends some messages, we ignore them
if ('error' in dataJson) {
console.log('Incoming data contains an error message', data);
return;
}
// If the message is a ping, just ignore it
if ('ping' in dataJson) {
return;
}
// Then, check if the message is encrpted
if (!('nonce' in dataJson)) {
const err = { error: 'Encryption error', to: getWormholeCode(keyHex, this.sodium) };
ws.send(JSON.stringify(err));
return;
}
let cmd;
// If decryption passes and this is a tmp wormhole client, then set it as the permanant client
if (this.tmpWormholeClient && keyHex === this.tmpWormholeClient.getKeyHex()) {
const { decrypted, nonce } = this.decryptIncoming(data, keyHex, false);
if (!decrypted) {
console.log('Decryption failed');
const err = { error: 'Encryption error', to: getWormholeCode(keyHex, this.sodium) };
ws.send(JSON.stringify(err));
return;
}
cmd = JSON.parse(decrypted);
// Replace the permanant client
this.replacePermClientWithTmp();
this.updateRemoteNonce(nonce);
} else {
const { decrypted, nonce } = this.decryptIncoming(data, keyHex, true);
if (!decrypted) {
const err = { error: 'Encryption error', to: getWormholeCode(keyHex, this.sodium) };
ws.send(JSON.stringify(err));
console.log('Decryption failed');
return;
}
cmd = JSON.parse(decrypted);
this.updateRemoteNonce(nonce);
}
if (cmd.command === 'getInfo') {
const response = this.doGetInfo(cmd);
ws.send(this.encryptOutgoing(response, keyHex));
} else if (cmd.command === 'getTransactions') {
const response = this.doGetTransactions();
ws.send(this.encryptOutgoing(response, keyHex));
} else if (cmd.command === 'sendTx') {
const response = this.doSendTransaction(cmd, ws);
ws.send(this.encryptOutgoing(response, keyHex));
}
}
// Generate a new secret key
genNewKeyHex(): string {
const keyHex = this.sodium.to_hex(this.sodium.crypto_secretbox_keygen());
return keyHex;
}
getEncKey(): string {
// Get the nonce. Increment and store the nonce for next use
const store = new Store();
const keyHex = store.get('companion/key');
return keyHex;
}
setEncKey(keyHex: string) {
// Get the nonce. Increment and store the nonce for next use
const store = new Store();
store.set('companion/key', keyHex);
}
saveLastClientName(name: string) {
// Save the last client name
const store = new Store();
store.set('companion/name', name);
if (name) {
const now = Date.now();
store.set('companion/lastseen', now);
const o = new ConnectedCompanionApp();
o.name = name;
o.lastSeen = now;
this.fnUpdateConnectedClient(o);
} else {
this.fnUpdateConnectedClient(null);
}
}
disconnectLastClient() {
// Remove the permanant connection
if (this.permWormholeClient) {
this.permWormholeClient.close();
}
this.saveLastClientName(null);
this.setEncKey(null);
}
getRemoteNonce(): string {
const store = new Store();
const nonceHex = store.get('companion/remotenonce');
return nonceHex;
}
updateRemoteNonce(nonce: string) {
if (nonce) {
const store = new Store();
store.set('companion/remotenonce', nonce);
}
}
getLocalNonce(): string {
// Get the nonce. Increment and store the nonce for next use
const store = new Store();
const nonceHex = store.get('companion/localnonce', `01${'00'.repeat(this.sodium.crypto_secretbox_NONCEBYTES - 1)}`);
// Increment nonce
const newNonce = this.sodium.from_hex(nonceHex);
this.sodium.increment(newNonce);
this.sodium.increment(newNonce);
store.set('companion/localnonce', this.sodium.to_hex(newNonce));
return nonceHex;
}
encryptOutgoing(str: string, keyHex: string): string {
if (!keyHex) {
console.log('No secret key');
throw Error('No Secret Key');
}
const nonceHex = this.getLocalNonce();
const nonce = this.sodium.from_hex(nonceHex);
const key = this.sodium.from_hex(keyHex);
const encrypted = this.sodium.crypto_secretbox_easy(str, nonce, key);
const encryptedHex = this.sodium.to_hex(encrypted);
const resp = {
nonce: this.sodium.to_hex(nonce),
payload: encryptedHex,
to: getWormholeCode(keyHex, this.sodium)
};
return JSON.stringify(resp);
}
decryptIncoming(msg: string, keyHex: string, checkNonce: boolean): any {
const msgJson = JSON.parse(msg);
console.log('trying to decrypt', msgJson);
if (!keyHex) {
console.log('No secret key');
throw Error('No Secret Key');
}
const key = this.sodium.from_hex(keyHex);
const nonce = this.sodium.from_hex(msgJson.nonce);
if (checkNonce) {
const prevNonce = this.sodium.from_hex(this.getRemoteNonce());
if (prevNonce && this.sodium.compare(prevNonce, nonce) >= 0) {
return { decrypted: null };
}
}
const cipherText = this.sodium.from_hex(msgJson.payload);
const decrypted = this.sodium.to_string(this.sodium.crypto_secretbox_open_easy(cipherText, nonce, key));
return { decrypted, nonce: msgJson.nonce };
}
doGetInfo(cmd: any): string {
const appState = this.fnGetState();
if (cmd && cmd.name) {
this.saveLastClientName(cmd.name);
}
const saplingAddress = appState.addresses.find(a => Utils.isSapling(a));
const tAddress = appState.addresses.find(a => Utils.isTransparent(a));
const balance = parseFloat(appState.totalBalance.total);
const maxspendable = parseFloat(appState.totalBalance.total);
const maxzspendable = parseFloat(appState.totalBalance.private);
const tokenName = appState.info.currencyName;
const zecprice = parseFloat(appState.info.zecPrice);
const resp = {
version: 1.0,
command: 'getInfo',
saplingAddress,
tAddress,
balance,
maxspendable,
maxzspendable,
tokenName,
zecprice,
serverversion: '1.7.8'
};
return JSON.stringify(resp);
}
doGetTransactions(): string {
const appState = this.fnGetState();
let txlist = [];
if (appState.transactions) {
// Get only the last 20 txns
txlist = appState.transactions.slice(0, 20).map(t => {
let memo = t.detailedTxns && t.detailedTxns.length > 0 ? t.detailedTxns[0].memo : '';
if (memo) {
memo = memo.trimRight();
} else {
memo = '';
}
const txResp = {
type: t.type,
datetime: t.time,
amount: t.amount.toFixed(8),
txid: t.txid,
address: t.address,
memo,
confirmations: t.confirmations
};
return txResp;
});
}
const resp = {
version: 1.0,
command: 'getTransactions',
transactions: txlist
};
return JSON.stringify(resp);
}
doSendTransaction(cmd: any, ws: WebSocket): string {
// "command":"sendTx","tx":{"amount":"0.00019927","to":"zs1pzr7ee53jwa3h3yvzdjf7meruujq84w5rsr5kuvye9qg552kdyz5cs5ywy5hxkxcfvy9wln94p6","memo":""}}
const inpTx = cmd.tx;
const appState = this.fnGetState();
const sendingAmount = parseFloat(inpTx.amount);
const buildError = (reason: string): string => {
const resp = {
errorCode: -1,
errorMessage: `Couldn't send Tx:${reason}`
};
// console.log('sendtx error', resp);
return JSON.stringify(resp);
};
// First, find an address that can send the correct amount.
const fromAddress = appState.addressesWithBalance.find(ab => ab.balance > sendingAmount);
if (!fromAddress) {
return buildError(`No address with sufficient balance to send ${sendingAmount}`);
}
let memo = !inpTx.memo || inpTx.memo.trim() === '' ? null : inpTx.memo;
const textEncoder = new TextEncoder();
memo = memo ? hex.encode(textEncoder.encode(memo)) : '';
// Build a sendJSON object
const sendJSON = [];
sendJSON.push(fromAddress.address);
if (memo) {
sendJSON.push([{ address: inpTx.to, amount: sendingAmount, memo }]);
} else {
sendJSON.push([{ address: inpTx.to, amount: sendingAmount }]);
}
// console.log('sendjson is', sendJSON);
this.fnSendTransaction(sendJSON, (title, msg) => {
let resp;
if (title.startsWith('Success')) {
const arr = msg.split(' ');
const txid = arr && arr.length > 0 && arr[arr.length - 1];
resp = {
version: 1.0,
command: 'sendTxSubmitted',
txid
};
} else {
resp = {
version: 1.0,
command: 'sendTxFailed',
err: msg
};
}
// console.log('Callback sending', resp);
ws.send(this.encryptOutgoing(JSON.stringify(resp)));
});
// After the transaction is submitted, we return an intermediate success.
const resp = {
version: 1.0,
command: 'sendTx',
result: 'success'
};
// console.log('sendtx sending', resp);
return JSON.stringify(resp);
}
}

View File

@ -1,145 +0,0 @@
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable no-plusplus */
/* eslint-disable react/prop-types */
// @flow
import React, { Component } from 'react';
import {
AccordionItemButton,
AccordionItem,
AccordionItemHeading,
AccordionItemPanel,
Accordion
} from 'react-accessible-accordion';
import styles from './Dashboard.module.css';
import cstyles from './Common.module.css';
import { TotalBalance, Info, AddressBalance } from './AppState';
import Utils from '../utils/utils';
import ScrollPane from './ScrollPane';
// $FlowFixMe
export const BalanceBlockHighlight = ({ zecValue, usdValue, currencyName }) => {
const { bigPart, smallPart } = Utils.splitZecAmountIntoBigSmall(zecValue);
return (
<div style={{ padding: '1em' }}>
<div className={[cstyles.highlight, cstyles.xlarge].join(' ')}>
<span>
{currencyName} {bigPart}
</span>
<span className={[cstyles.small, cstyles.zecsmallpart].join(' ')}>{smallPart}</span>
</div>
<div className={[cstyles.sublight, cstyles.small].join(' ')}>{usdValue}</div>
</div>
);
};
// eslint-disable-next-line react/prop-types
const BalanceBlock = ({ zecValue, usdValue, topLabel, currencyName }) => {
const { bigPart, smallPart } = Utils.splitZecAmountIntoBigSmall(zecValue);
return (
<div className={cstyles.padall}>
<div className={[styles.sublight, styles.small].join(' ')}>{topLabel}</div>
<div className={[cstyles.highlight, cstyles.large].join(' ')}>
<span>
{currencyName} {bigPart}
</span>
<span className={[cstyles.small, cstyles.zecsmallpart].join(' ')}>{smallPart}</span>
</div>
<div className={[cstyles.sublight, cstyles.small].join(' ')}>{usdValue}</div>
</div>
);
};
const AddressBalanceItem = ({ currencyName, zecPrice, item }) => {
const { bigPart, smallPart } = Utils.splitZecAmountIntoBigSmall(Math.abs(item.balance));
return (
<AccordionItem key={item.label} className={[cstyles.well, cstyles.margintopsmall].join(' ')} uuid={item.address}>
<AccordionItemHeading>
<AccordionItemButton className={cstyles.accordionHeader}>
<div className={[cstyles.flexspacebetween].join(' ')}>
<div>{Utils.splitStringIntoChunks(item.address, 6).join(' ')}</div>
<div className={[styles.txamount, cstyles.right].join(' ')}>
<div>
<span>
{currencyName} {bigPart}
</span>
<span className={[cstyles.small, cstyles.zecsmallpart].join(' ')}>{smallPart}</span>
</div>
<div className={[cstyles.sublight, cstyles.small, cstyles.padtopsmall].join(' ')}>
{Utils.getZecToUsdString(zecPrice, Math.abs(item.balance))}
</div>
</div>
</div>
</AccordionItemButton>
</AccordionItemHeading>
<AccordionItemPanel />
</AccordionItem>
);
};
type Props = {
totalBalance: TotalBalance,
info: Info,
addressesWithBalance: AddressBalance[]
};
export default class Home extends Component<Props> {
render() {
const { totalBalance, info, addressesWithBalance } = this.props;
return (
<div>
<div className={[cstyles.well, styles.balancebox].join(' ')}>
<BalanceBlockHighlight
zecValue={totalBalance.total}
usdValue={Utils.getZecToUsdString(info.zecPrice, totalBalance.total)}
currencyName={info.currencyName}
/>
<BalanceBlock
topLabel="Shielded"
zecValue={totalBalance.private}
usdValue={Utils.getZecToUsdString(info.zecPrice, totalBalance.private)}
currencyName={info.currencyName}
/>
<BalanceBlock
topLabel="Transparent"
zecValue={totalBalance.transparent}
usdValue={Utils.getZecToUsdString(info.zecPrice, totalBalance.transparent)}
currencyName={info.currencyName}
/>
</div>
<div className={styles.addressbalancecontainer}>
<ScrollPane offsetHeight={200}>
<div className={styles.addressbooklist}>
<div className={[cstyles.flexspacebetween, cstyles.tableheader, cstyles.sublight].join(' ')}>
<div>Address</div>
<div>Balance</div>
</div>
{addressesWithBalance &&
(addressesWithBalance.length === 0 ? (
<div className={[cstyles.center, cstyles.sublight].join(' ')}>No Addresses with a balance</div>
) : (
<Accordion>
{addressesWithBalance
.filter(ab => ab.balance > 0)
.map(ab => (
<AddressBalanceItem
key={ab.address}
item={ab}
currencyName={info.currencyName}
zecPrice={info.zecPrice}
/>
))}
</Accordion>
))}
</div>
</ScrollPane>
</div>
</div>
);
}
}

View File

@ -1,46 +0,0 @@
/* eslint-disable react/prop-types */
import Modal from 'react-modal';
import React from 'react';
import cstyles from './Common.module.css';
export class ErrorModalData {
title: string;
body: string;
modalIsOpen: boolean;
constructor() {
this.modalIsOpen = false;
}
}
export const ErrorModal = ({ title, body, modalIsOpen, closeModal }) => {
return (
<Modal
isOpen={modalIsOpen}
onRequestClose={closeModal}
className={cstyles.modal}
overlayClassName={cstyles.modalOverlay}
>
<div className={[cstyles.verticalflex].join(' ')}>
<div className={cstyles.marginbottomlarge} style={{ textAlign: 'center' }}>
{title}
</div>
<div
className={cstyles.well}
style={{ textAlign: 'center', wordBreak: 'break-all', maxHeight: '400px', overflowY: 'auto' }}
>
{body}
</div>
</div>
<div className={cstyles.buttoncontainer}>
<button type="button" className={cstyles.primarybutton} onClick={closeModal}>
Close
</button>
</div>
</Modal>
);
};

View File

@ -1,495 +0,0 @@
/* eslint-disable jsx-a11y/interactive-supports-focus */
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable max-classes-per-file */
import React, { Component } from 'react';
import { Redirect, withRouter } from 'react-router';
import ini from 'ini';
import fs from 'fs';
import request from 'request';
import progress from 'progress-stream';
import os from 'os';
import path from 'path';
import { remote, ipcRenderer, shell } from 'electron';
import { spawn } from 'child_process';
import { promisify } from 'util';
import routes from '../constants/routes.json';
import { RPCConfig, Info } from './AppState';
import RPC from '../rpc';
import cstyles from './Common.module.css';
import styles from './LoadingScreen.module.css';
import { NO_CONNECTION } from '../utils/utils';
import Logo from '../assets/img/logobig.png';
import zcashdlogo from '../assets/img/zcashdlogo.gif';
const locateZcashConfDir = () => {
if (os.platform() === 'darwin') {
return path.join(remote.app.getPath('appData'), 'Zcash');
}
if (os.platform() === 'linux') {
return path.join(remote.app.getPath('home'), '.zcash');
}
return path.join(remote.app.getPath('appData'), 'Zcash');
};
const locateZcashConf = () => {
if (os.platform() === 'darwin') {
return path.join(remote.app.getPath('appData'), 'Zcash', 'zcash.conf');
}
if (os.platform() === 'linux') {
return path.join(remote.app.getPath('home'), '.zcash', 'zcash.conf');
}
return path.join(remote.app.getPath('appData'), 'Zcash', 'zcash.conf');
};
const locateZcashd = () => {
// const con = remote.getGlobal('console');
// con.log(`App path = ${remote.app.getAppPath()}`);
// con.log(`Unified = ${path.join(remote.app.getAppPath(), '..', 'bin', 'mac', 'zcashd')}`);
if (os.platform() === 'darwin') {
return path.join(remote.app.getAppPath(), '..', 'bin', 'mac', 'zcashd');
}
if (os.platform() === 'linux') {
return path.join(remote.app.getAppPath(), '..', 'bin', 'linux', 'zcashd');
}
return path.join(remote.app.getAppPath(), '..', 'bin', 'win', 'zcashd.exe');
};
const locateZcashParamsDir = () => {
if (os.platform() === 'darwin') {
return path.join(remote.app.getPath('appData'), 'ZcashParams');
}
if (os.platform() === 'linux') {
return path.join(remote.app.getPath('home'), '.zcash-params');
}
return path.join(remote.app.getPath('appData'), 'ZcashParams');
};
type Props = {
setRPCConfig: (rpcConfig: RPCConfig) => void,
setInfo: (info: Info) => void,
history: PropTypes.object.isRequired
};
class LoadingScreenState {
creatingZcashConf: boolean;
connectOverTor: boolean;
enableFastSync: boolean;
currentStatus: string;
loadingDone: boolean;
rpcConfig: RPCConfig | null;
zcashdSpawned: number;
getinfoRetryCount: number;
constructor() {
this.currentStatus = 'Loading...';
this.creatingZcashConf = false;
this.loadingDone = false;
this.zcashdSpawned = 0;
this.getinfoRetryCount = 0;
this.rpcConfig = null;
}
}
class LoadingScreen extends Component<Props, LoadingScreenState> {
constructor(props: Props) {
super(props);
this.state = new LoadingScreenState();
}
componentDidMount() {
(async () => {
const success = await this.ensureZcashParams();
if (success) {
await this.loadZcashConf(true);
await this.setupExitHandler();
}
})();
}
download = (url, dest, name, cb) => {
const file = fs.createWriteStream(dest);
const sendReq = request.get(url);
// verify response code
sendReq.on('response', response => {
if (response.statusCode !== 200) {
return cb(`Response status was ${response.statusCode}`);
}
const totalSize = (parseInt(response.headers['content-length'], 10) / 1024 / 1024).toFixed(0);
const str = progress({ time: 1000 }, pgrs => {
this.setState({
currentStatus: `Downloading ${name}... (${(pgrs.transferred / 1024 / 1024).toFixed(0)} MB / ${totalSize} MB)`
});
});
sendReq.pipe(str).pipe(file);
});
// close() is async, call cb after close completes
file.on('finish', () => file.close(cb));
// check for request errors
sendReq.on('error', err => {
fs.unlink(dest);
return cb(err.message);
});
file.on('error', err => {
// Handle errors
fs.unlink(dest); // Delete the file async. (But we don't check the result)
return cb(err.message);
});
};
ensureZcashParams = async () => {
// Check if the zcash params dir exists and if the params files are present
const dir = locateZcashParamsDir();
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
// Check for the params
const params = [
{ name: 'sapling-output.params', url: 'https://params.zecwallet.co/params/sapling-output.params' },
{ name: 'sapling-spend.params', url: 'https://params.zecwallet.co/params/sapling-spend.params' },
{ name: 'sprout-groth16.params', url: 'https://params.zecwallet.co/params/sprout-groth16.params' }
];
// eslint-disable-next-line no-plusplus
for (let i = 0; i < params.length; i++) {
const p = params[i];
const fileName = path.join(dir, p.name);
if (!fs.existsSync(fileName)) {
// Download and save this file
this.setState({ currentStatus: `Downloading ${p.name}...` });
try {
// eslint-disable-next-line no-await-in-loop
await promisify(this.download)(p.url, fileName, p.name);
} catch (err) {
console.log(`error: ${err}`);
this.setState({ currentStatus: `Error downloading ${p.name}. The error was: ${err}` });
return false;
}
}
}
return true;
};
async loadZcashConf(createIfMissing: boolean) {
// Load the RPC config from zcash.conf file
const zcashLocation = locateZcashConf();
let confValues;
try {
confValues = ini.parse(await fs.promises.readFile(zcashLocation, { encoding: 'utf-8' }));
} catch (err) {
if (createIfMissing) {
this.setState({ creatingZcashConf: true });
return;
}
this.setState({
currentStatus: `Could not create zcash.conf at ${zcashLocation}. This is a bug, please file an issue with Zecwallet`
});
return;
}
// Get the username and password
const rpcConfig = new RPCConfig();
rpcConfig.username = confValues.rpcuser;
rpcConfig.password = confValues.rpcpassword;
if (!rpcConfig.username || !rpcConfig.password) {
this.setState({
currentStatus: (
<div>
<p>Your zcash.conf is missing a &quot;rpcuser&quot; or &quot;rpcpassword&quot;.</p>
<p>
Please add a &quot;rpcuser=some_username&quot; and &quot;rpcpassword=some_password&quot; to your
zcash.conf to enable RPC access
</p>
<p>Your zcash.conf is located at {zcashLocation}</p>
</div>
)
});
return;
}
const isTestnet = (confValues.testnet && confValues.testnet === '1') || false;
const server = confValues.rpcbind || '127.0.0.1';
const port = confValues.rpcport || (isTestnet ? '18232' : '8232');
rpcConfig.url = `http://${server}:${port}`;
this.setState({ rpcConfig });
// And setup the next getinfo
this.setupNextGetInfo();
}
createZcashconf = async () => {
const { connectOverTor, enableFastSync } = this.state;
const dir = locateZcashConfDir();
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
const zcashConfPath = await locateZcashConf();
let confContent = '';
confContent += 'server=1\n';
confContent += 'rpcuser=zecwallet\n';
confContent += `rpcpassword=${Math.random()
.toString(36)
.substring(2, 15)}\n`;
if (connectOverTor) {
confContent += 'proxy=127.0.0.1:9050\n';
}
if (enableFastSync) {
confContent += 'ibdskiptxverification=1\n';
}
await fs.promises.writeFile(zcashConfPath, confContent);
this.setState({ creatingZcashConf: false });
this.loadZcashConf(false);
};
zcashd: ChildProcessWithoutNullStreams | null = null;
setupExitHandler = () => {
// App is quitting, exit zcashd as well
ipcRenderer.on('appquitting', async () => {
if (this.zcashd) {
const { history } = this.props;
const { rpcConfig } = this.state;
this.setState({ currentStatus: 'Waiting for zcashd to exit...' });
history.push(routes.LOADING);
this.zcashd.on('close', () => {
ipcRenderer.send('appquitdone');
});
this.zcashd.on('exit', () => {
ipcRenderer.send('appquitdone');
});
console.log('Sending stop');
setTimeout(() => {
RPC.doRPC('stop', [], rpcConfig);
});
} else {
// And reply that we're all done.
ipcRenderer.send('appquitdone');
}
});
};
startZcashd = async () => {
const { zcashdSpawned } = this.state;
if (zcashdSpawned) {
this.setState({ currentStatus: 'zcashd start failed' });
return;
}
const program = locateZcashd();
console.log(program);
this.zcashd = spawn(program);
this.setState({ zcashdSpawned: 1 });
this.setState({ currentStatus: 'zcashd starting...' });
this.zcashd.on('error', err => {
console.log(`zcashd start error, giving up. Error: ${err}`);
// Set that we tried to start zcashd, and failed
this.setState({ zcashdSpawned: 1 });
// No point retrying.
this.setState({ getinfoRetryCount: 10 });
});
};
setupNextGetInfo() {
setTimeout(() => this.getInfo(), 1000);
}
async getInfo() {
const { rpcConfig, zcashdSpawned, getinfoRetryCount } = this.state;
// Try getting the info.
try {
const info = await RPC.getInfoObject(rpcConfig);
console.log(info);
const { setRPCConfig, setInfo } = this.props;
setRPCConfig(rpcConfig);
setInfo(info);
// This will cause a redirect to the dashboard
this.setState({ loadingDone: true });
} catch (err) {
// Not yet finished loading. So update the state, and setup the next refresh
this.setState({ currentStatus: err });
if (err === NO_CONNECTION && !zcashdSpawned) {
// Try to start zcashd
this.startZcashd();
this.setupNextGetInfo();
}
if (err === NO_CONNECTION && zcashdSpawned && getinfoRetryCount < 10) {
this.setState({ currentStatus: 'Waiting for zcashd to start...' });
const inc = getinfoRetryCount + 1;
this.setState({ getinfoRetryCount: inc });
this.setupNextGetInfo();
}
if (err === NO_CONNECTION && zcashdSpawned && getinfoRetryCount >= 10) {
// Give up
this.setState({
currentStatus: (
<span>
Failed to start zcashd. Giving up! Please look at the debug.log file.
<br />
<span className={cstyles.highlight}>{`${locateZcashConfDir()}/debug.log`}</span>
<br />
Please file an issue with Zecwallet
</span>
)
});
}
if (err !== NO_CONNECTION) {
this.setupNextGetInfo();
}
}
}
handleEnableFastSync = event => {
this.setState({ enableFastSync: event.target.checked });
};
handleTorEnabled = event => {
this.setState({ connectOverTor: event.target.checked });
};
render() {
const { loadingDone, currentStatus, creatingZcashConf, connectOverTor, enableFastSync } = this.state;
// If still loading, show the status
if (!loadingDone) {
return (
<div className={[cstyles.center, styles.loadingcontainer].join(' ')}>
{!creatingZcashConf && (
<div className={cstyles.verticalflex}>
<div style={{ marginTop: '100px' }}>
<img src={Logo} width="200px;" alt="Logo" />
</div>
<div>{currentStatus}</div>
</div>
)}
{creatingZcashConf && (
<div>
<div className={cstyles.verticalflex}>
<div
className={[cstyles.verticalflex, cstyles.center, cstyles.margintoplarge, cstyles.highlight].join(
' '
)}
>
<div className={[cstyles.xlarge].join(' ')}> Welcome To Zecwallet Fullnode!</div>
</div>
<div className={[cstyles.center, cstyles.margintoplarge].join(' ')}>
<img src={zcashdlogo} width="400px" alt="zcashdlogo" />
</div>
<div
className={[cstyles.verticalflex, cstyles.center, cstyles.margintoplarge].join(' ')}
style={{ width: '75%', marginLeft: '15%' }}
>
<div>
Zecwallet Fullnode will download the{' '}
<span className={cstyles.highlight}>entire Zcash Blockchain (~28GB)</span>, which might take several
days to sync. If you want to get started immediately, please consider{' '}
<a
className={cstyles.highlight}
style={{ textDecoration: 'underline' }}
role="link"
onClick={() => shell.openExternal('https://www.zecwallet.co')}
>
Zecwallet Lite
</a>
, which can get you started in under a minute.
</div>
</div>
<div className={cstyles.left} style={{ width: '75%', marginLeft: '15%' }}>
<div className={cstyles.margintoplarge} />
<div className={[cstyles.verticalflex].join(' ')}>
<div>
<input type="checkbox" onChange={this.handleTorEnabled} defaultChecked={connectOverTor} />
&nbsp; Connect over Tor
</div>
<div className={cstyles.sublight}>
Will connect over Tor. Please make sure you have the Tor client installed and listening on port
9050.
</div>
</div>
<div className={cstyles.margintoplarge} />
<div className={[cstyles.verticalflex].join(' ')}>
<div>
<input type="checkbox" onChange={this.handleEnableFastSync} defaultChecked={enableFastSync} />
&nbsp; Enable Fast Sync
</div>
<div className={cstyles.sublight}>
When enabled, Zecwallet will skip some expensive verifications of the zcashd blockchain when
downloading. This option is safe to use if you are creating a brand new wallet.
</div>
</div>
</div>
<div className={cstyles.buttoncontainer}>
<button type="button" className={cstyles.primarybutton} onClick={this.createZcashconf}>
Start Zcash
</button>
</div>
</div>
</div>
)}
</div>
);
}
return <Redirect to={routes.DASHBOARD} />;
}
}
export default withRouter(LoadingScreen);

View File

@ -1,296 +0,0 @@
/* eslint-disable react/prop-types */
import React, { Component, useState, useEffect } from 'react';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import {
Accordion,
AccordionItem,
AccordionItemHeading,
AccordionItemButton,
AccordionItemPanel
} from 'react-accessible-accordion';
import QRCode from 'qrcode.react';
import { shell, clipboard } from 'electron';
import styles from './Receive.module.css';
import cstyles from './Common.module.css';
import Utils from '../utils/utils';
import { AddressBalance, Info, ReceivePageState, AddressBookEntry } from './AppState';
import ScrollPane from './ScrollPane';
const AddressBlock = ({
addressBalance,
label,
currencyName,
zecPrice,
privateKey,
fetchAndSetSinglePrivKey,
viewKey,
fetchAndSetSingleViewKey
}) => {
const { address } = addressBalance;
const [copied, setCopied] = useState(false);
const [timerID, setTimerID] = useState(null);
useEffect(() => {
return () => {
if (timerID) {
clearTimeout(timerID);
}
};
});
const balance = addressBalance.balance || 0;
const openAddress = () => {
if (currencyName === 'TAZ') {
shell.openExternal(`https://chain.so/address/ZECTEST/${address}`);
} else {
shell.openExternal(`https://zcha.in/accounts/${address}`);
}
};
return (
<AccordionItem key={copied} className={[cstyles.well, styles.receiveblock].join(' ')} uuid={address}>
<AccordionItemHeading>
<AccordionItemButton className={cstyles.accordionHeader}>{address}</AccordionItemButton>
</AccordionItemHeading>
<AccordionItemPanel className={[styles.receiveDetail].join(' ')}>
<div className={[cstyles.flexspacebetween].join(' ')}>
<div className={[cstyles.verticalflex, cstyles.marginleft].join(' ')}>
{label && (
<div className={cstyles.margintoplarge}>
<div className={[cstyles.sublight].join(' ')}>Label</div>
<div className={[cstyles.padtopsmall, cstyles.fixedfont].join(' ')}>{label}</div>
</div>
)}
<div className={[cstyles.sublight, cstyles.margintoplarge].join(' ')}>Funds</div>
<div className={[cstyles.padtopsmall].join(' ')}>
{currencyName} {balance}
</div>
<div className={[cstyles.padtopsmall].join(' ')}>{Utils.getZecToUsdString(zecPrice, balance)}</div>
<div className={[cstyles.margintoplarge, cstyles.breakword].join(' ')}>
{privateKey && (
<div>
<div className={[cstyles.sublight].join(' ')}>Private Key</div>
<div
className={[cstyles.breakword, cstyles.padtopsmall, cstyles.fixedfont, cstyles.flex].join(' ')}
style={{ maxWidth: '600px' }}
>
<QRCode value={privateKey} className={[styles.receiveQrcode].join(' ')} />
<div>{privateKey}</div>
</div>
</div>
)}
</div>
<div className={[cstyles.margintoplarge, cstyles.breakword].join(' ')}>
{viewKey && (
<div>
<div className={[cstyles.sublight].join(' ')}>Viewing Key</div>
<div
className={[cstyles.breakword, cstyles.padtopsmall, cstyles.fixedfont, cstyles.flex].join(' ')}
style={{ maxWidth: '600px' }}
>
<QRCode value={viewKey} className={[styles.receiveQrcode].join(' ')} />
<div>{viewKey}</div>
</div>
</div>
)}
</div>
<div>
<button
className={[cstyles.primarybutton, cstyles.margintoplarge].join(' ')}
type="button"
onClick={() => {
clipboard.writeText(address);
setCopied(true);
setTimerID(setTimeout(() => setCopied(false), 5000));
}}
>
{copied ? <span>Copied!</span> : <span>Copy Address</span>}
</button>
{!privateKey && (
<button
className={[cstyles.primarybutton].join(' ')}
type="button"
onClick={() => fetchAndSetSinglePrivKey(address)}
>
Export Private Key
</button>
)}
{Utils.isZaddr(address) && !viewKey && (
<button
className={[cstyles.primarybutton].join(' ')}
type="button"
onClick={() => fetchAndSetSingleViewKey(address)}
>
Export Viewing Key
</button>
)}
{Utils.isTransparent(address) && (
<button className={[cstyles.primarybutton].join(' ')} type="button" onClick={() => openAddress()}>
View on explorer <i className={['fas', 'fa-external-link-square-alt'].join(' ')} />
</button>
)}
</div>
</div>
<div>
<QRCode value={address} className={[styles.receiveQrcode].join(' ')} />
</div>
</div>
</AccordionItemPanel>
</AccordionItem>
);
};
type Props = {
addresses: string[],
addressesWithBalance: AddressBalance[],
addressBook: AddressBookEntry[],
info: Info,
receivePageState: ReceivePageState,
fetchAndSetSinglePrivKey: string => void,
fetchAndSetSingleViewKey: string => void,
createNewAddress: boolean => void,
rerenderKey: number
};
export default class Receive extends Component<Props> {
render() {
const {
addresses,
addressesWithBalance,
addressPrivateKeys,
addressViewKeys,
addressBook,
info,
receivePageState,
fetchAndSetSinglePrivKey,
fetchAndSetSingleViewKey,
createNewAddress,
rerenderKey
} = this.props;
// Convert the addressBalances into a map.
const addressMap = addressesWithBalance.reduce((map, a) => {
// eslint-disable-next-line no-param-reassign
map[a.address] = a.balance;
return map;
}, {});
const zaddrs = addresses
.filter(a => Utils.isSapling(a))
.slice(0, 100)
.map(a => new AddressBalance(a, addressMap[a]));
let defaultZaddr = zaddrs.length ? zaddrs[0].address : '';
if (receivePageState && Utils.isSapling(receivePageState.newAddress)) {
defaultZaddr = receivePageState.newAddress;
// move this address to the front, since the scrollbar will reset when we re-render
zaddrs.sort((x, y) => {
// eslint-disable-next-line prettier/prettier, no-nested-ternary
return x.address === defaultZaddr ? -1 : y.address === defaultZaddr ? 1 : 0
});
}
const taddrs = addresses
.filter(a => Utils.isTransparent(a))
.slice(0, 100)
.map(a => new AddressBalance(a, addressMap[a]));
let defaultTaddr = taddrs.length ? taddrs[0].address : '';
if (receivePageState && Utils.isTransparent(receivePageState.newAddress)) {
defaultTaddr = receivePageState.newAddress;
// move this address to the front, since the scrollbar will reset when we re-render
taddrs.sort((x, y) => {
// eslint-disable-next-line prettier/prettier, no-nested-ternary
return x.address === defaultTaddr ? -1 : y.address === defaultTaddr ? 1 : 0
});
}
const addressBookMap = addressBook.reduce((map, obj) => {
// eslint-disable-next-line no-param-reassign
map[obj.address] = obj.label;
return map;
}, {});
return (
<div>
<div className={styles.receivecontainer}>
<Tabs>
<TabList>
<Tab>Shielded</Tab>
<Tab>Transparent</Tab>
</TabList>
<TabPanel key={`z${rerenderKey}`}>
{/* Change the hardcoded height */}
<ScrollPane offsetHeight={100}>
<Accordion preExpanded={[defaultZaddr]}>
{zaddrs.map(a => (
<AddressBlock
key={a.address}
addressBalance={a}
currencyName={info.currencyName}
label={addressBookMap[a.address]}
zecPrice={info.zecPrice}
privateKey={addressPrivateKeys[a.address]}
viewKey={addressViewKeys[a.address]}
fetchAndSetSinglePrivKey={fetchAndSetSinglePrivKey}
fetchAndSetSingleViewKey={fetchAndSetSingleViewKey}
rerender={this.rerender}
/>
))}
</Accordion>
<button
className={[cstyles.primarybutton, cstyles.margintoplarge, cstyles.marginbottomlarge].join(' ')}
onClick={() => createNewAddress(true)}
type="button"
>
New Shielded Address
</button>
</ScrollPane>
</TabPanel>
<TabPanel key={`t${rerenderKey}`}>
{/* Change the hardcoded height */}
<ScrollPane offsetHeight={100}>
<Accordion preExpanded={[defaultTaddr]}>
{taddrs.map(a => (
<AddressBlock
key={a.address}
addressBalance={a}
currencyName={info.currencyName}
zecPrice={info.zecPrice}
privateKey={addressPrivateKeys[a.address]}
viewKey={addressViewKeys[a.address]}
fetchAndSetSinglePrivKey={fetchAndSetSinglePrivKey}
fetchAndSetSingleViewKey={fetchAndSetSingleViewKey}
rerender={this.rerender}
/>
))}
</Accordion>
<button
className={[cstyles.primarybutton, cstyles.margintoplarge, cstyles.marginbottomlarge].join(' ')}
type="button"
onClick={() => createNewAddress(false)}
>
New Transparent Address
</button>
</ScrollPane>
</TabPanel>
</Tabs>
</div>
</div>
);
}
}

View File

@ -1,636 +0,0 @@
/* eslint-disable no-restricted-globals */
/* eslint-disable no-else-return */
/* eslint-disable radix */
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable react/prop-types */
/* eslint-disable max-classes-per-file */
// @flow
import React, { PureComponent } from 'react';
import Modal from 'react-modal';
import Select from 'react-select';
import TextareaAutosize from 'react-textarea-autosize';
import hex from 'hex-string';
import { withRouter } from 'react-router-dom';
import styles from './Send.module.css';
import cstyles from './Common.module.css';
import { ToAddr, AddressBalance, SendPageState, Info, AddressBookEntry } from './AppState';
import Utils from '../utils/utils';
import ScrollPane from './ScrollPane';
import ArrowUpLight from '../assets/img/arrow_up_dark.png';
import { ErrorModal } from './ErrorModal';
import routes from '../constants/routes.json';
type OptionType = {
value: string,
label: string
};
const Spacer = () => {
return <div style={{ marginTop: '24px' }} />;
};
// $FlowFixMe
const ToAddrBox = ({
toaddr,
zecPrice,
updateToField,
fromAddress,
fromAmount,
setMaxAmount,
setSendButtonEnable,
totalAmountAvailable
}) => {
const isMemoDisabled = !Utils.isZaddr(toaddr.to);
const addressIsValid = toaddr.to === '' || Utils.isZaddr(toaddr.to) || Utils.isTransparent(toaddr.to);
const memoIsValid = toaddr.memo.length <= 512;
let amountError = null;
if (toaddr.amount) {
if (toaddr.amount < 0) {
amountError = 'Amount cannot be negative';
}
if (toaddr.amount > fromAmount) {
amountError = 'Amount Exceeds Balance';
}
if (toaddr.amount < 10 ** -8) {
amountError = 'Amount is too small';
}
const s = toaddr.amount.toString().split('.');
if (s && s.length > 1 && s[1].length > 8) {
amountError = 'Too Many Decimals';
}
}
if (isNaN(toaddr.amount)) {
// Amount is empty
amountError = 'Amount cannot be empty';
}
let buttonstate;
if (
!addressIsValid ||
amountError ||
!memoIsValid ||
toaddr.to === '' ||
parseFloat(toaddr.amount) === 0 ||
fromAmount === 0
) {
buttonstate = false;
} else {
buttonstate = true;
}
setTimeout(() => {
setSendButtonEnable(buttonstate);
}, 10);
const usdValue = Utils.getZecToUsdString(zecPrice, toaddr.amount);
const addReplyTo = () => {
if (toaddr.memo.endsWith(fromAddress)) {
return;
}
if (fromAddress) {
updateToField(toaddr.id, null, null, `${toaddr.memo}\nReply-To:\n${fromAddress}`);
}
};
return (
<div>
<div className={[cstyles.well, cstyles.verticalflex].join(' ')}>
<div className={[cstyles.flexspacebetween].join(' ')}>
<div className={cstyles.sublight}>To</div>
<div className={cstyles.validationerror}>
{addressIsValid ? (
<i className={[cstyles.green, 'fas', 'fa-check'].join(' ')} />
) : (
<span className={cstyles.red}>Invalid Address</span>
)}
</div>
</div>
<input
type="text"
placeholder="Z or T address"
className={cstyles.inputbox}
value={toaddr.to}
onChange={e => updateToField(toaddr.id, e, null, null)}
/>
<Spacer />
<div className={[cstyles.flexspacebetween].join(' ')}>
<div className={cstyles.sublight}>Amount</div>
<div className={cstyles.validationerror}>
{amountError ? <span className={cstyles.red}>{amountError}</span> : <span>{usdValue}</span>}
</div>
</div>
<div className={[cstyles.flexspacebetween].join(' ')}>
<input
type="number"
step="any"
className={cstyles.inputbox}
value={isNaN(toaddr.amount) ? '' : toaddr.amount}
onChange={e => updateToField(toaddr.id, null, e, null)}
/>
<img
className={styles.toaddrbutton}
src={ArrowUpLight}
alt="Max"
onClick={() => setMaxAmount(toaddr.id, totalAmountAvailable)}
/>
</div>
<Spacer />
{isMemoDisabled && <div className={cstyles.sublight}>Memos only for z-addresses</div>}
{!isMemoDisabled && (
<div>
<div className={[cstyles.flexspacebetween].join(' ')}>
<div className={cstyles.sublight}>Memo</div>
<div className={cstyles.validationerror}>
{memoIsValid ? toaddr.memo.length : <span className={cstyles.red}>{toaddr.memo.length}</span>} / 512
</div>
</div>
<TextareaAutosize
className={cstyles.inputbox}
value={toaddr.memo}
disabled={isMemoDisabled}
onChange={e => updateToField(toaddr.id, null, null, e)}
/>
<input type="checkbox" onChange={e => e.target.checked && addReplyTo()} />
Include Reply-To address
</div>
)}
<Spacer />
</div>
<Spacer />
</div>
);
};
function getSendManyJSON(sendPageState: SendPageState, info: Info): [] {
const json = [];
json.push(sendPageState.fromaddr);
json.push(
sendPageState.toaddrs.map(to => {
const textEncoder = new TextEncoder();
const memo = to.memo ? hex.encode(textEncoder.encode(to.memo)) : '';
if (memo === '') {
return { address: to.to, amount: to.amount };
} else {
return { address: to.to, amount: to.amount, memo };
}
})
);
json.push(1); // minconf = 1
json.push(Utils.getDefaultFee(info.latestBlock)); // Control the default fee as well.
console.log('Sending:');
console.log(json);
return json;
}
const ConfirmModalToAddr = ({ toaddr, info }) => {
const { bigPart, smallPart } = Utils.splitZecAmountIntoBigSmall(toaddr.amount);
const memo: string = toaddr.memo ? toaddr.memo : '';
return (
<div className={cstyles.well}>
<div className={[cstyles.flexspacebetween, cstyles.margintoplarge].join(' ')}>
<div className={[styles.confirmModalAddress].join(' ')}>
{Utils.splitStringIntoChunks(toaddr.to, 6).join(' ')}
</div>
<div className={[cstyles.verticalflex, cstyles.right].join(' ')}>
<div className={cstyles.large}>
<div>
<span>
{info.currencyName} {bigPart}
</span>
<span className={[cstyles.small, styles.zecsmallpart].join(' ')}>{smallPart}</span>
</div>
</div>
<div>{Utils.getZecToUsdString(info.zecPrice, toaddr.amount)}</div>
</div>
</div>
<div className={[cstyles.sublight, cstyles.breakword].join(' ')}>{memo}</div>
</div>
);
};
// Internal because we're using withRouter just below
const ConfirmModalInternal = ({
sendPageState,
info,
sendTransaction,
clearToAddrs,
closeModal,
modalIsOpen,
openErrorModal,
history
}) => {
const sendingTotal =
sendPageState.toaddrs.reduce((s, t) => parseFloat(s) + parseFloat(t.amount), 0.0) +
Utils.getDefaultFee(info.latestBlock);
const { bigPart, smallPart } = Utils.splitZecAmountIntoBigSmall(sendingTotal);
const sendButton = () => {
// First, close the confirm modal.
closeModal();
// This will be replaced by either a success TXID or error message that the user
// has to close manually.
openErrorModal('Computing Transaction', 'Please wait...This could take a while');
// Then send the Tx async
(async () => {
const sendJson = getSendManyJSON(sendPageState, info);
let success = false;
try {
success = await sendTransaction(sendJson, openErrorModal);
} catch (err) {
// If there was an error, show the error modal
openErrorModal('Error Sending Transaction', err);
}
// If the Tx was sent, then clear the addresses
if (success) {
clearToAddrs();
// Redirect to dashboard after
history.push(routes.DASHBOARD);
}
})();
};
return (
<Modal
isOpen={modalIsOpen}
onRequestClose={closeModal}
className={styles.confirmModal}
overlayClassName={styles.confirmOverlay}
>
<div className={[cstyles.verticalflex].join(' ')}>
<div className={[cstyles.marginbottomlarge, cstyles.center].join(' ')}>Confirm Transaction</div>
<div className={cstyles.flex}>
<div
className={[
cstyles.highlight,
cstyles.xlarge,
cstyles.flexspacebetween,
cstyles.well,
cstyles.maxwidth
].join(' ')}
>
<div>Total</div>
<div className={[cstyles.right, cstyles.verticalflex].join(' ')}>
<div>
<span>
{info.currencyName} {bigPart}
</span>
<span className={[cstyles.small, styles.zecsmallpart].join(' ')}>{smallPart}</span>
</div>
<div className={cstyles.normal}>{Utils.getZecToUsdString(info.zecPrice, sendingTotal)}</div>
</div>
</div>
</div>
<div className={[cstyles.verticalflex, cstyles.margintoplarge].join(' ')}>
{sendPageState.toaddrs.map(t => (
<ConfirmModalToAddr key={t.to} toaddr={t} info={info} />
))}
</div>
<ConfirmModalToAddr
toaddr={{ to: 'Fee', amount: Utils.getDefaultFee(info.latestBlock), memo: null }}
info={info}
/>
{info && info.disconnected && (
<div className={[cstyles.red, cstyles.margintoplarge].join(' ')}>
You are currently disconnected. This transaction might not work.
</div>
)}
{info && info.verificationprogress < 0.9999 && (
<div className={[cstyles.red, cstyles.margintoplarge].join(' ')}>
You are currently syncing. This transaction might not work.
</div>
)}
<div className={cstyles.buttoncontainer}>
<button type="button" className={cstyles.primarybutton} onClick={() => sendButton()}>
Send
</button>
<button type="button" className={cstyles.primarybutton} onClick={closeModal}>
Cancel
</button>
</div>
</div>
</Modal>
);
};
const ConfirmModal = withRouter(ConfirmModalInternal);
type Props = {
addressesWithBalance: AddressBalance[],
addressBook: AddressBookEntry[],
sendPageState: SendPageState,
sendTransaction: (sendJson: [], (string, string) => void) => void,
setSendPageState: (sendPageState: SendPageState) => void,
openErrorModal: (title: string, body: string) => void,
closeErrorModal: () => void,
info: Info
};
class SendState {
modalIsOpen: boolean;
errorModalIsOpen: boolean;
errorModalTitle: string;
errorModalBody: string;
sendButtonEnabled: boolean;
constructor() {
this.modalIsOpen = false;
this.errorModalIsOpen = false;
this.errorModalBody = '';
this.errorModalTitle = '';
this.sendButtonEnabled = false;
}
}
export default class Send extends PureComponent<Props, SendState> {
constructor(props: Props) {
super(props);
this.state = new SendState();
}
addToAddr = () => {
const { sendPageState, setSendPageState } = this.props;
const newToAddrs = sendPageState.toaddrs.concat(new ToAddr(Utils.getNextToAddrID()));
// Create the new state object
const newState = new SendPageState();
newState.fromaddr = sendPageState.fromaddr;
newState.toaddrs = newToAddrs;
setSendPageState(newState);
};
clearToAddrs = () => {
const { sendPageState, setSendPageState } = this.props;
const newToAddrs = [new ToAddr(Utils.getNextToAddrID())];
// Create the new state object
const newState = new SendPageState();
newState.fromaddr = sendPageState.fromaddr;
newState.toaddrs = newToAddrs;
setSendPageState(newState);
};
changeFrom = (selectedOption: OptionType) => {
const { sendPageState, setSendPageState } = this.props;
// Create the new state object
const newState = new SendPageState();
newState.fromaddr = selectedOption.value;
newState.toaddrs = sendPageState.toaddrs;
setSendPageState(newState);
};
updateToField = (id: number, address: Event | null, amount: Event | null, memo: Event | string | null) => {
const { sendPageState, setSendPageState } = this.props;
const newToAddrs = sendPageState.toaddrs.slice(0);
// Find the correct toAddr
const toAddr = newToAddrs.find(a => a.id === id);
if (address) {
// $FlowFixMe
toAddr.to = address.target.value.replace(/ /g, ''); // Remove spaces
}
if (amount) {
// Check to see the new amount if valid
// $FlowFixMe
const newAmount = parseFloat(amount.target.value);
if (newAmount < 0 || newAmount > 21 * 10 ** 6) {
return;
}
// $FlowFixMe
toAddr.amount = newAmount;
}
if (memo) {
if (typeof memo === 'string') {
toAddr.memo = memo;
} else {
// $FlowFixMe
toAddr.memo = memo.target.value;
}
}
// Create the new state object
const newState = new SendPageState();
newState.fromaddr = sendPageState.fromaddr;
newState.toaddrs = newToAddrs;
setSendPageState(newState);
};
setMaxAmount = (id: number, total: number) => {
const { sendPageState, setSendPageState, info } = this.props;
const newToAddrs = sendPageState.toaddrs.slice(0);
let totalOtherAmount: number = newToAddrs
.filter(a => a.id !== id)
.reduce((s, a) => parseFloat(s) + parseFloat(a.amount), 0);
// Add Fee
totalOtherAmount += Utils.getDefaultFee(info.latestBlock);
// Find the correct toAddr
const toAddr = newToAddrs.find(a => a.id === id);
toAddr.amount = total - totalOtherAmount;
if (toAddr.amount < 0) toAddr.amount = 0;
toAddr.amount = Utils.maxPrecisionTrimmed(toAddr.amount);
// Create the new state object
const newState = new SendPageState();
newState.fromaddr = sendPageState.fromaddr;
newState.toaddrs = newToAddrs;
setSendPageState(newState);
};
setSendButtonEnable = (sendButtonEnabled: boolean) => {
this.setState({ sendButtonEnabled });
};
openModal = () => {
this.setState({ modalIsOpen: true });
};
closeModal = () => {
this.setState({ modalIsOpen: false });
};
getBalanceForAddress = (addr: string, addressesWithBalance: AddressBalance[]): number => {
// Find the addr in addressesWithBalance
const addressBalance: AddressBalance = addressesWithBalance.find(ab => ab.address === addr);
if (!addressBalance) {
return 0;
}
return addressBalance.balance;
};
getLabelForFromAddress = (addr: string, addressesWithBalance: AddressBalance[], currencyName: string) => {
// Find the addr in addressesWithBalance
const { addressBook } = this.props;
const label = addressBook.find(ab => ab.address === addr);
const labelStr = label ? ` [ ${label.label} ]` : '';
const balance = this.getBalanceForAddress(addr, addressesWithBalance);
return `[ ${currencyName} ${balance.toString()} ]${labelStr} ${addr}`;
};
render() {
const { modalIsOpen, errorModalIsOpen, errorModalTitle, errorModalBody, sendButtonEnabled } = this.state;
const { sendPageState, info, openErrorModal, closeErrorModal } = this.props;
const customStyles = {
option: (provided, state) => ({
...provided,
color: state.isSelected ? '#c3921f;' : 'white',
background: '#212124;',
padding: 20
}),
menu: provided => ({
...provided,
background: '#212124;'
}),
control: () => ({
// none of react-select's styles are passed to <Control />
display: 'flex',
alignItems: 'center',
flexWrap: 'flex',
background: '#212124;'
}),
singleValue: (provided, state) => {
const opacity = state.isDisabled ? 0.5 : 1;
const transition = 'opacity 300ms';
return { ...provided, opacity, transition, color: '#ffffff' };
}
};
const { addressesWithBalance, sendTransaction } = this.props;
const sendFromList = addressesWithBalance.map(ab => {
return {
value: ab.address,
label: this.getLabelForFromAddress(ab.address, addressesWithBalance, info.currencyName)
};
});
// Find the fromaddress
let fromaddr = {};
if (sendPageState.fromaddr) {
fromaddr = {
value: sendPageState.fromaddr,
label: this.getLabelForFromAddress(sendPageState.fromaddr, addressesWithBalance, info.currencyName)
};
}
const totalAmountAvailable = this.getBalanceForAddress(fromaddr.value, addressesWithBalance);
return (
<div>
<div className={[cstyles.xlarge, cstyles.padall, cstyles.center].join(' ')}>Send</div>
<div className={styles.sendcontainer}>
<div className={[cstyles.well, cstyles.verticalflex].join(' ')}>
<div className={[cstyles.sublight, cstyles.padbottomsmall].join(' ')}>Send From</div>
<Select
value={fromaddr}
options={sendFromList}
styles={customStyles}
// $FlowFixMe
onChange={this.changeFrom}
/>
</div>
<Spacer />
<ScrollPane offsetHeight={300}>
{sendPageState.toaddrs.map(toaddr => {
return (
<ToAddrBox
key={toaddr.id}
toaddr={toaddr}
zecPrice={info.zecPrice}
updateToField={this.updateToField}
fromAddress={fromaddr.value}
fromAmount={totalAmountAvailable}
setMaxAmount={this.setMaxAmount}
setSendButtonEnable={this.setSendButtonEnable}
totalAmountAvailable={totalAmountAvailable}
/>
);
})}
<div style={{ textAlign: 'right' }}>
<button type="button" onClick={this.addToAddr}>
<i className={['fas', 'fa-plus'].join(' ')} />
</button>
</div>
</ScrollPane>
<div className={cstyles.center}>
<button
type="button"
disabled={!sendButtonEnabled}
className={cstyles.primarybutton}
onClick={this.openModal}
>
Send
</button>
<button type="button" className={cstyles.primarybutton} onClick={this.clearToAddrs}>
Cancel
</button>
</div>
<ConfirmModal
sendPageState={sendPageState}
info={info}
sendTransaction={sendTransaction}
openErrorModal={openErrorModal}
closeModal={this.closeModal}
modalIsOpen={modalIsOpen}
clearToAddrs={this.clearToAddrs}
/>
<ErrorModal
title={errorModalTitle}
body={errorModalBody}
modalIsOpen={errorModalIsOpen}
closeModal={closeErrorModal}
/>
</div>
</div>
);
}
}

View File

@ -1,348 +0,0 @@
/* eslint-disable react/prop-types */
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-static-element-interactions */
import React, { Component } from 'react';
import Modal from 'react-modal';
import dateformat from 'dateformat';
import { shell } from 'electron';
import { withRouter } from 'react-router';
import { BalanceBlockHighlight } from './Dashboard';
import styles from './Transactions.module.css';
import cstyles from './Common.module.css';
import { Transaction, Info } from './AppState';
import ScrollPane from './ScrollPane';
import Utils from '../utils/utils';
import AddressBook from './Addressbook';
import routes from '../constants/routes.json';
const TxModalInternal = ({ modalIsOpen, tx, info, closeModal, currencyName, zecPrice, setSendTo, history }) => {
let txid = '';
let type = '';
let typeIcon = '';
let typeColor = '';
let confirmations = 0;
let detailedTxns = [];
let amount = 0;
let datePart = '';
let timePart = '';
if (tx) {
txid = tx.txid;
type = tx.type;
if (tx.type === 'receive') {
typeIcon = 'fa-arrow-circle-down';
typeColor = 'green';
} else {
typeIcon = 'fa-arrow-circle-up';
typeColor = 'red';
}
datePart = dateformat(tx.time * 1000, 'mmm dd, yyyy');
timePart = dateformat(tx.time * 1000, 'hh:MM tt');
confirmations = tx.confirmations;
detailedTxns = tx.detailedTxns;
amount = Math.abs(tx.amount);
}
const openTxid = () => {
if (currencyName === 'TAZ') {
shell.openExternal(`https://chain.so/tx/ZECTEST/${txid}`);
} else {
shell.openExternal(`https://zcha.in/transactions/${txid}`);
}
};
const doReply = (address: string) => {
setSendTo(new ZcashURITarget(address, Utils.getDefaultFee(info.latestBlock), null));
closeModal();
history.push(routes.SEND);
};
return (
<Modal
isOpen={modalIsOpen}
onRequestClose={closeModal}
className={styles.txmodal}
overlayClassName={styles.txmodalOverlay}
>
<div className={[cstyles.verticalflex].join(' ')}>
<div className={[cstyles.marginbottomlarge, cstyles.center].join(' ')}>Transaction Status</div>
<div className={[cstyles.center].join(' ')}>
<i className={['fas', typeIcon].join(' ')} style={{ fontSize: '96px', color: typeColor }} />
</div>
<div className={[cstyles.center].join(' ')}>
{type}
<BalanceBlockHighlight
zecValue={amount}
usdValue={Utils.getZecToUsdString(zecPrice, Math.abs(amount))}
currencyName={currencyName}
/>
</div>
<div className={[cstyles.flexspacebetween].join(' ')}>
<div>
<div className={[cstyles.sublight].join(' ')}>Time</div>
<div>
{datePart} {timePart}
</div>
</div>
{type === 'send' && (
<div>
<div className={[cstyles.sublight].join(' ')}>Fees</div>
<div>ZEC {Utils.maxPrecisionTrimmed(tx.fee)}</div>
</div>
)}
<div>
<div className={[cstyles.sublight].join(' ')}>Confirmations</div>
<div>{confirmations}</div>
</div>
</div>
<div className={cstyles.margintoplarge} />
<div className={[cstyles.flexspacebetween].join(' ')}>
<div>
<div className={[cstyles.sublight].join(' ')}>TXID</div>
<div>{txid}</div>
</div>
<div className={cstyles.primarybutton} onClick={openTxid}>
View TXID &nbsp;
<i className={['fas', 'fa-external-link-square-alt'].join(' ')} />
</div>
</div>
<div className={cstyles.margintoplarge} />
<hr />
{detailedTxns.map(txdetail => {
const { bigPart, smallPart } = Utils.splitZecAmountIntoBigSmall(Math.abs(txdetail.amount));
let { address } = txdetail;
const { memo } = txdetail;
if (!address) {
address = '(Shielded)';
}
let replyTo = null;
if (tx.type === 'receive' && memo) {
const split = memo.split(/[ :\n\r\t]+/);
if (split && split.length > 0 && Utils.isSapling(split[split.length - 1])) {
replyTo = split[split.length - 1];
}
}
return (
<div key={address} className={cstyles.verticalflex}>
<div className={[cstyles.sublight].join(' ')}>Address</div>
<div>{Utils.splitStringIntoChunks(address, 6).join(' ')}</div>
<div className={cstyles.margintoplarge} />
<div className={[cstyles.sublight].join(' ')}>Amount</div>
<div>
<span>
{currencyName} {bigPart}
</span>
<span className={[cstyles.small, cstyles.zecsmallpart].join(' ')}>{smallPart}</span>
</div>
<div className={cstyles.margintoplarge} />
{memo && (
<div>
<div className={[cstyles.sublight].join(' ')}>Memo</div>
<div className={[cstyles.flexspacebetween].join(' ')}>
<div>{memo}</div>
{replyTo && (
<div className={cstyles.primarybutton} onClick={() => doReply(replyTo)}>
Reply
</div>
)}
</div>
</div>
)}
<hr />
</div>
);
})}
<div className={[cstyles.center, cstyles.margintoplarge].join(' ')}>
<button type="button" className={cstyles.primarybutton} onClick={closeModal}>
Close
</button>
</div>
</div>
</Modal>
);
};
const TxModal = withRouter(TxModalInternal);
const TxItemBlock = ({ transaction, currencyName, zecPrice, txClicked, addressBookMap }) => {
const txDate = new Date(transaction.time * 1000);
const datePart = dateformat(txDate, 'mmm dd, yyyy');
const timePart = dateformat(txDate, 'hh:MM tt');
return (
<div>
<div className={[cstyles.small, cstyles.sublight, styles.txdate].join(' ')}>{datePart}</div>
<div
className={[cstyles.well, styles.txbox].join(' ')}
onClick={() => {
txClicked(transaction);
}}
>
<div className={styles.txtype}>
<div>{transaction.type}</div>
<div className={[cstyles.padtopsmall, cstyles.sublight].join(' ')}>{timePart}</div>
</div>
<div className={styles.txaddressamount}>
{transaction.detailedTxns.map(txdetail => {
const { bigPart, smallPart } = Utils.splitZecAmountIntoBigSmall(Math.abs(txdetail.amount));
let { address } = txdetail;
const { memo } = txdetail;
if (!address) {
address = '(Shielded)';
}
const label = addressBookMap[address] || '';
return (
<div key={address} className={cstyles.padtopsmall}>
<div className={styles.txaddress}>
<div className={cstyles.highlight}>{label}</div>
<div>{Utils.splitStringIntoChunks(address, 6).join(' ')}</div>
<div className={[cstyles.small, cstyles.sublight, cstyles.padtopsmall, styles.txmemo].join(' ')}>
{memo}
</div>
</div>
<div className={[styles.txamount, cstyles.right].join(' ')}>
<div>
<span>
{currencyName} {bigPart}
</span>
<span className={[cstyles.small, cstyles.zecsmallpart].join(' ')}>{smallPart}</span>
</div>
<div className={[cstyles.sublight, cstyles.small, cstyles.padtopsmall].join(' ')}>
{Utils.getZecToUsdString(zecPrice, Math.abs(txdetail.amount))}
</div>
</div>
</div>
);
})}
</div>
</div>
</div>
);
};
type Props = {
transactions: Transaction[],
addressBook: AddressBook[],
info: Info,
setSendTo: (targets: ZcashURITarget[] | ZcashURITarget) => void
};
type State = {
clickedTx: Transaction | null,
modalIsOpen: boolean,
numTxnsToShow: number
};
export default class Transactions extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { clickedTx: null, modalIsOpen: false, numTxnsToShow: 100 };
}
txClicked = (tx: Transaction) => {
// Show the modal
if (!tx) return;
this.setState({ clickedTx: tx, modalIsOpen: true });
};
closeModal = () => {
this.setState({ clickedTx: null, modalIsOpen: false });
};
show100MoreTxns = () => {
const { numTxnsToShow } = this.state;
this.setState({ numTxnsToShow: numTxnsToShow + 100 });
};
render() {
const { transactions, info, addressBook, setSendTo } = this.props;
const { clickedTx, modalIsOpen, numTxnsToShow } = this.state;
const isLoadMoreEnabled = transactions && numTxnsToShow < transactions.length;
const addressBookMap = addressBook.reduce((map, obj) => {
// eslint-disable-next-line no-param-reassign
map[obj.address] = obj.label;
return map;
}, {});
return (
<div>
<div className={[cstyles.xlarge, cstyles.padall, cstyles.center].join(' ')}>Transactions</div>
{/* Change the hardcoded height */}
<ScrollPane offsetHeight={100}>
{/* If no transactions, show the "loading..." text */
!transactions && <div className={[cstyles.center, cstyles.margintoplarge].join(' ')}>Loading...</div>}
{transactions && transactions.length === 0 && (
<div className={[cstyles.center, cstyles.margintoplarge].join(' ')}>No Transactions Yet</div>
)}
{transactions &&
transactions.slice(0, numTxnsToShow).map(t => {
const key = t.type + t.txid + t.address + t.index;
return (
<TxItemBlock
key={key}
transaction={t}
currencyName={info.currencyName}
zecPrice={info.zecPrice}
txClicked={this.txClicked}
addressBookMap={addressBookMap}
/>
);
})}
{isLoadMoreEnabled && (
<div
style={{ marginLeft: '45%', width: '100px' }}
className={cstyles.primarybutton}
onClick={this.show100MoreTxns}
>
Load more
</div>
)}
</ScrollPane>
<TxModal
modalIsOpen={modalIsOpen}
tx={clickedTx}
info={info}
closeModal={this.closeModal}
currencyName={info.currencyName}
zecPrice={info.zecPrice}
setSendTo={setSendTo}
/>
</div>
);
}
}

View File

@ -1,110 +0,0 @@
import React, { PureComponent } from 'react';
import QRCode from 'qrcode.react';
import dateformat from 'dateformat';
import cstyles from './Common.module.css';
import styles from './WormholeConnection.module.css';
import CompanionAppListener from '../companion';
import { ConnectedCompanionApp } from './AppState';
type Props = {
companionAppListener: CompanionAppListener,
connectedCompanionApp: ConnectedCompanionApp | null
};
type State = {
tempKeyHex: string
};
export default class WormholeConnection extends PureComponent<Props, State> {
constructor(props) {
super(props);
this.state = { tempKeyHex: null };
}
componentDidMount() {
// If there is no temp key, create one
const { companionAppListener } = this.props;
const { tempKeyHex } = this.state;
if (!tempKeyHex) {
const newKey = companionAppListener.genNewKeyHex();
companionAppListener.createTmpClient(newKey);
this.setState({ tempKeyHex: newKey });
}
}
componentWillUnmount() {
const { companionAppListener } = this.props;
companionAppListener.closeTmpClient();
}
disconnectCurrentMobile = () => {
const { companionAppListener } = this.props;
companionAppListener.disconnectLastClient();
};
render() {
const { tempKeyHex } = this.state;
const { connectedCompanionApp } = this.props;
const clientName = (connectedCompanionApp && connectedCompanionApp.name) || null;
const lastSeen = (connectedCompanionApp && connectedCompanionApp.lastSeen) || null;
let datePart = null;
let timePart = null;
if (lastSeen) {
const txDate = new Date(lastSeen);
datePart = dateformat(txDate, 'mmm dd, yyyy');
timePart = dateformat(txDate, 'hh:MM tt');
}
const connStr = `ws://127.0.0.1:7070,${tempKeyHex},1`;
return (
<div>
<div className={[cstyles.xlarge, cstyles.padall, cstyles.center].join(' ')}>Connect Mobile App</div>
<div className={styles.qrcodecontainer}>
<div>This is your connection code. Scan this QR code from the Zecwallet Companion App.</div>
<div className={[cstyles.center, cstyles.margintoplarge].join(' ')}>
<QRCode value={connStr} size={256} level="M" className={styles.wormholeqr} />
</div>
<div className={[cstyles.sublight, cstyles.margintoplarge, cstyles.small].join(' ')}>{connStr}</div>
</div>
<div className={styles.appinfocontainer}>
<div className={styles.appinfo}>
{clientName && (
<div>
<div className={cstyles.flexspacebetween}>
<div style={{ flex: 1 }} className={cstyles.sublight}>
Current App Connected:
</div>
<div style={{ flex: 1 }}>{clientName}</div>
</div>
<div style={{ display: 'flex' }}>
<div style={{ flex: 1 }} className={cstyles.sublight}>
Last Seen:
</div>
<div style={{ flex: 1 }}>
{datePart} {timePart}
</div>
</div>
<div className={cstyles.margintoplarge}>
<button type="button" className={cstyles.primarybutton} onClick={this.disconnectCurrentMobile}>
Disconnect
</button>
</div>
</div>
)}
{!clientName && <div>No Companion App Connected</div>}
</div>
</div>
</div>
);
}
}

View File

@ -1,22 +0,0 @@
.wormholeqr {
width: 400px;
padding: 10px;
background-color: white;
}
.qrcodecontainer {
margin: 20px;
text-align: center;
}
.appinfocontainer {
width: 50%;
margin-left: 25%;
margin-top: 20px;
}
.appinfo {
margin-top: 20px;
border: 1px solid grey;
padding: 16px;
}

View File

@ -1,15 +0,0 @@
// @flow
import * as React from 'react';
type Props = {
children: React.Node
};
export default class App extends React.Component<Props> {
props: Props;
render() {
const { children } = this.props;
return <>{children}</>;
}
}

View File

@ -1,13 +0,0 @@
// @flow
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import { hot } from 'react-hot-loader/root';
import Routes from '../Routes';
const Root = () => (
<Router>
<Routes />
</Router>
);
export default hot(Root);

View File

@ -1,14 +0,0 @@
import React, { Fragment } from 'react';
import { render } from 'react-dom';
import { AppContainer as ReactHotAppContainer } from 'react-hot-loader';
import Root from './containers/Root';
import './app.global.css';
const AppContainer = process.env.PLAIN_HMR ? Fragment : ReactHotAppContainer;
render(
<AppContainer>
<Root />
</AppContainer>,
document.getElementById('root')
);

View File

@ -1,152 +0,0 @@
/* eslint global-require: off */
/**
* This module executes inside of electron's main process. You can start
* electron renderer process from here and communicate with the other processes
* through IPC.
*
* When running `yarn build` or `yarn build-main`, this file is compiled to
* `./app/main.prod.js` using webpack. This gives us some performance wins.
*
* @flow
*/
import { app, shell, BrowserWindow, ipcMain } from 'electron';
import log from 'electron-log';
import MenuBuilder from './menu';
export default class AppUpdater {
constructor() {
log.transports.file.level = 'info';
}
}
let mainWindow = null;
if (process.env.NODE_ENV === 'production') {
const sourceMapSupport = require('source-map-support');
sourceMapSupport.install();
}
if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') {
require('electron-debug')();
}
const installExtensions = async () => {
const installer = require('electron-devtools-installer');
const forceDownload = !!process.env.UPGRADE_EXTENSIONS;
const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS'];
return Promise.all(extensions.map(name => installer.default(installer[name], forceDownload))).catch(console.log);
};
let waitingForClose = false;
let proceedToClose = false;
const createWindow = async () => {
if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') {
await installExtensions();
}
mainWindow = new BrowserWindow({
show: false,
width: 1300,
height: 728,
minHeight: 500,
minWidth: 1100,
webPreferences: {
// Allow node integration because we're only loading local content here.
nodeIntegration: true
}
});
mainWindow.loadURL(`file://${__dirname}/app.html`);
app.on('web-contents-created', (event, contents) => {
contents.on('new-window', async (eventInner, navigationUrl) => {
// In this example, we'll ask the operating system
// to open this event's url in the default browser.
console.log('attempting to open window', navigationUrl);
eventInner.preventDefault();
await shell.openExternal(navigationUrl);
});
});
// @TODO: Use 'ready-to-show' event
// https://github.com/electron/electron/blob/master/docs/api/browser-window.md#using-ready-to-show-event
mainWindow.webContents.on('did-finish-load', () => {
if (!mainWindow) {
throw new Error('"mainWindow" is not defined');
}
if (process.env.START_MINIMIZED) {
mainWindow.minimize();
} else {
mainWindow.show();
mainWindow.focus();
}
});
mainWindow.on('close', event => {
// If we are clear to close, then return and allow everything to close
if (proceedToClose) {
console.log('proceed to close, so closing');
return;
}
// If we're already waiting for close, then don't allow another close event to actually close the window
if (waitingForClose) {
console.log('Waiting for close... Timeout in 10s');
event.preventDefault();
return;
}
waitingForClose = true;
event.preventDefault();
ipcMain.on('appquitdone', () => {
waitingForClose = false;
proceedToClose = true;
app.quit();
});
// $FlowFixMe
mainWindow.webContents.send('appquitting');
// Failsafe, timeout after 10 seconds
setTimeout(() => {
waitingForClose = false;
proceedToClose = true;
console.log('Timeout, quitting');
app.quit();
}, 10 * 1000);
});
mainWindow.on('closed', () => {
mainWindow = null;
});
const menuBuilder = new MenuBuilder(mainWindow);
menuBuilder.buildMenu();
// Remove this if your app does not use auto updates
// eslint-disable-next-line
new AppUpdater();
};
/**
* Add event listeners...
*/
app.on('window-all-closed', () => {
app.quit();
});
app.on('ready', createWindow);
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) createWindow();
});

View File

@ -1 +0,0 @@
/*! http://mths.be/fromcodepoint v0.1.0 by @mathias */

View File

@ -1,326 +0,0 @@
// @flow
import { app, Menu, shell, BrowserWindow } from 'electron';
export default class MenuBuilder {
mainWindow: BrowserWindow;
constructor(mainWindow: BrowserWindow) {
this.mainWindow = mainWindow;
}
buildMenu() {
// if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') {
// // this.setupDevelopmentEnvironment();
// }
const template = process.platform === 'darwin' ? this.buildDarwinTemplate() : this.buildDefaultTemplate();
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
const selectionMenu = Menu.buildFromTemplate([{ role: 'copy' }, { type: 'separator' }, { role: 'selectall' }]);
const inputMenu = Menu.buildFromTemplate([
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' },
{ type: 'separator' },
{ role: 'selectall' }
]);
this.mainWindow.webContents.on('context-menu', (e, props) => {
const { selectionText, isEditable } = props;
if (isEditable) {
inputMenu.popup(this.mainWindow);
} else if (selectionText && selectionText.trim() !== '') {
selectionMenu.popup(this.mainWindow);
} else if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') {
const { x, y } = props;
Menu.buildFromTemplate([
{
label: 'Inspect element',
click: () => {
this.mainWindow.inspectElement(x, y);
}
}
]).popup(this.mainWindow);
}
});
return menu;
}
buildDarwinTemplate() {
const { mainWindow } = this;
const subMenuAbout = {
label: 'Zecwallet Fullnode',
submenu: [
{
label: 'About Zecwallet Fullnode',
selector: 'orderFrontStandardAboutPanel:',
click: () => {
mainWindow.webContents.send('about');
}
},
{ type: 'separator' },
{ label: 'Services', submenu: [] },
{ type: 'separator' },
{
label: 'Hide Zecwallet Fullnode',
accelerator: 'Command+H',
selector: 'hide:'
},
{
label: 'Hide Others',
accelerator: 'Command+Shift+H',
selector: 'hideOtherApplications:'
},
{ label: 'Show All', selector: 'unhideAllApplications:' },
{ type: 'separator' },
{
label: 'Quit',
accelerator: 'Command+Q',
click: () => {
app.quit();
}
}
]
};
const subMenuFile = {
label: 'File',
submenu: [
{
label: '&Pay URI',
accelerator: 'Ctrl+P',
click: () => {
mainWindow.webContents.send('payuri');
}
},
{
label: '&Import Private Keys',
click: () => {
mainWindow.webContents.send('import');
}
},
{
label: '&Export All Private Keys',
click: () => {
mainWindow.webContents.send('exportall');
}
},
{ type: 'separator' },
{
label: 'Export All &Transactions',
click: () => {
mainWindow.webContents.send('exportalltx');
}
}
]
};
const subMenuEdit = {
label: 'Edit',
submenu: [
{ label: 'Undo', accelerator: 'Command+Z', selector: 'undo:' },
{ label: 'Redo', accelerator: 'Shift+Command+Z', selector: 'redo:' },
{ type: 'separator' },
{ label: 'Cut', accelerator: 'Command+X', selector: 'cut:' },
{ label: 'Copy', accelerator: 'Command+C', selector: 'copy:' },
{ label: 'Paste', accelerator: 'Command+V', selector: 'paste:' },
{
label: 'Select All',
accelerator: 'Command+A',
selector: 'selectAll:'
}
]
};
const subMenuViewDev = {
label: 'View',
submenu: [
{
label: 'Connect Mobile App',
click: () => {
this.mainWindow.webContents.send('connectmobile');
}
},
{
label: 'zcashd info',
click: () => {
this.mainWindow.webContents.send('zcashd');
}
},
{ type: 'separator' },
{
label: 'Toggle Developer Tools',
accelerator: 'Alt+Command+I',
click: () => {
this.mainWindow.toggleDevTools();
}
}
]
};
const subMenuViewProd = {
label: 'View',
submenu: [
{
label: 'Connect Mobile App',
click: () => {
this.mainWindow.webContents.send('connectmobile');
}
},
{
label: 'zcashd info',
click: () => {
this.mainWindow.webContents.send('zcashd');
}
}
]
};
const subMenuWindow = {
label: 'Window',
submenu: [
{
label: 'Minimize',
accelerator: 'Command+M',
selector: 'performMiniaturize:'
},
{ label: 'Close', accelerator: 'Command+W', selector: 'performClose:' },
{ type: 'separator' },
{ label: 'Bring All to Front', selector: 'arrangeInFront:' }
]
};
const subMenuHelp = {
label: 'Help',
submenu: [
{
label: 'Donate',
click() {
mainWindow.webContents.send('donate');
}
},
{
label: 'Check github.com for updates',
click() {
shell.openExternal('https://github.com/zcashfoundation/zecwallet/releases');
}
},
{
label: 'File a bug...',
click() {
shell.openExternal('https://github.com/zcashfoundation/zecwallet/issues');
}
}
]
};
const subMenuView = process.env.NODE_ENV === 'development' ? subMenuViewDev : subMenuViewProd;
return [subMenuAbout, subMenuFile, subMenuEdit, subMenuView, subMenuWindow, subMenuHelp];
}
buildDefaultTemplate() {
const { mainWindow } = this;
const templateDefault = [
{
label: '&File',
submenu: [
{
label: '&Pay URI',
accelerator: 'Ctrl+P',
click: () => {
mainWindow.webContents.send('payuri');
}
},
{
label: '&Import Private Keys...',
click: () => {
mainWindow.webContents.send('import');
}
},
{
label: '&Export All Private Keys',
click: () => {
mainWindow.webContents.send('exportall');
}
},
{ type: 'separator' },
{
label: 'Export All &Transactions',
click: () => {
mainWindow.webContents.send('exportalltx');
}
},
{
label: '&Close',
accelerator: 'Ctrl+W',
click: () => {
this.mainWindow.close();
}
}
]
},
{
label: '&View',
submenu: [
// {
// label: 'Toggle &Developer Tools',
// accelerator: 'Alt+Ctrl+I',
// click: () => {
// this.mainWindow.toggleDevTools();
// }
// },
{
label: 'Connect Mobile App',
click: () => {
this.mainWindow.webContents.send('connectmobile');
}
},
{
label: 'zcashd info',
click: () => {
this.mainWindow.webContents.send('zcashd');
}
}
]
},
{
label: 'Help',
submenu: [
{
label: 'About Zecwallet Fullnode',
click: () => {
mainWindow.webContents.send('about');
}
},
{
label: 'Donate',
click() {
mainWindow.webContents.send('donate');
}
},
{
label: 'Check github.com for updates',
click() {
shell.openExternal('https://github.com/zcashfoundation/zecwallet/releases');
}
},
{
label: 'File a bug...',
click() {
shell.openExternal('https://github.com/zcashfoundation/zecwallet/issues');
}
}
]
}
];
return templateDefault;
}
}

5
app/package-lock.json generated
View File

@ -1,5 +0,0 @@
{
"name": "electron-react-boilerplate",
"version": "0.18.1",
"lockfileVersion": 1
}

View File

@ -1,18 +0,0 @@
{
"name": "zecwallet",
"productName": "Zecwallet Fullnode",
"version": "1.7.8",
"description": "Zecwallet Fullnode",
"main": "./main.prod.js",
"author": {
"name": "Aditya Kulkarni",
"email": "aditya@zecwallet.co",
"url": "https://github.com/adityapk00/zecwallet-electron"
},
"scripts": {
"electron-rebuild": "node -r ../internals/scripts/BabelRegister.js ../internals/scripts/ElectronRebuild.js",
"postinstall": "yarn electron-rebuild"
},
"license": "MIT",
"dependencies": {}
}

View File

@ -1,549 +0,0 @@
/* eslint-disable max-classes-per-file */
import axios from 'axios';
import _ from 'underscore';
import hex from 'hex-string';
import { TotalBalance, AddressBalance, Transaction, RPCConfig, TxDetail, Info } from './components/AppState';
import Utils, { NO_CONNECTION } from './utils/utils';
import SentTxStore from './utils/SentTxStore';
const parseMemo = (memoHex: string): string | null => {
if (!memoHex || memoHex.length < 2) return null;
// First, check if this is a memo (first byte is less than 'f6' (246))
if (parseInt(memoHex.substr(0, 2), 16) >= 246) return null;
// Else, parse as Hex string
const textDecoder = new TextDecoder();
const memo = textDecoder.decode(hex.decode(memoHex));
if (memo === '') return null;
return memo;
};
class OpidMonitor {
opid: string;
fnOpenSendErrorModal: (string, string) => void;
}
export default class RPC {
rpcConfig: RPCConfig;
fnSetInfo: Info => void;
fnSetTotalBalance: TotalBalance => void;
fnSetAddressesWithBalance: (AddressBalance[]) => void;
fnSetTransactionsList: (Transaction[]) => void;
fnSetAllAddresses: (string[]) => void;
fnSetZecPrice: number => void;
fnSetDisconnected: string => void;
opids: Set<OpidMonitor>;
refreshTimerID: TimerID;
opTimerID: TimerID;
priceTimerID: TimerID;
constructor(
fnSetTotalBalance: TotalBalance => void,
fnSetAddressesWithBalance: (AddressBalance[]) => void,
fnSetTransactionsList: (Transaction[]) => void,
fnSetAllAddresses: (string[]) => void,
fnSetInfo: Info => void,
fnSetZecPrice: number => void,
fnSetDisconnected: () => void
) {
this.fnSetTotalBalance = fnSetTotalBalance;
this.fnSetAddressesWithBalance = fnSetAddressesWithBalance;
this.fnSetTransactionsList = fnSetTransactionsList;
this.fnSetAllAddresses = fnSetAllAddresses;
this.fnSetInfo = fnSetInfo;
this.fnSetZecPrice = fnSetZecPrice;
this.fnSetDisconnected = fnSetDisconnected;
this.opids = new Set();
}
async configure(rpcConfig: RPCConfig) {
this.rpcConfig = rpcConfig;
if (!this.refreshTimerID) {
this.refreshTimerID = setTimeout(() => this.refresh(), 1000);
}
if (!this.opTimerID) {
this.opTimerID = setTimeout(() => this.refreshOpStatus(), 1000);
}
if (!this.priceTimerID) {
this.priceTimerID = setTimeout(() => this.getZecPrice(), 1000);
}
}
setupNextFetch(lastBlockHeight: number) {
this.refreshTimerID = setTimeout(() => this.refresh(lastBlockHeight), 60 * 1000);
}
static async doRPC(method: string, params: [], rpcConfig: RPCConfig) {
const { url, username, password } = rpcConfig;
const response = await new Promise((resolve, reject) => {
axios(url, {
data: {
jsonrpc: '2.0',
id: 'curltest',
method,
params
},
method: 'POST',
auth: {
username,
password
}
})
.then(r => resolve(r.data))
.catch(err => {
const e = { ...err };
if (e.response && e.response.data) {
reject(e.response.data.error.message);
} else {
// eslint-disable-next-line prefer-promise-reject-errors
reject(NO_CONNECTION);
}
});
});
return response;
}
async refresh(lastBlockHeight: number) {
let latestBlockHeight;
try {
latestBlockHeight = await this.fetchInfo();
} catch (err) {
// If we caught an error, there's something wrong with the connection.
this.fnSetDisconnected(`${err}`);
return;
}
// Temporary fix for zcashd stalls at 903001 issue (https://github.com/zcash/zcash/issues/4620)
if (latestBlockHeight === 903001) {
console.log('Looks like stalled');
// If it is stalled at this height after 5 seconds, we will do invalidate / reconsider
setTimeout(async () => {
const newHeight = await this.fetchInfo();
if (newHeight !== 903001) {
return;
}
console.log('Confirmed stalled');
// First, clear the banned peers
await RPC.doRPC('clearbanned', [], this.rpcConfig);
// Then invalidate block
await RPC.doRPC(
'invalidateblock',
['00000000006e4d348d0addad1b43ae09744f9c76a0724be4a3f5e08bdb1121ac'],
this.rpcConfig
);
console.log('Invalidated block');
// And then, 2 seconds later, reconsider the block
setTimeout(async () => {
await RPC.doRPC(
'reconsiderblock',
['00000000006e4d348d0addad1b43ae09744f9c76a0724be4a3f5e08bdb1121ac'],
this.rpcConfig
);
console.log('Reconsidered block');
}, 2 * 1000);
}, 5 * 1000);
}
if (!lastBlockHeight || lastBlockHeight < latestBlockHeight) {
try {
const balP = this.fetchTotalBalance();
const abP = this.fetchTandZAddressesWithBalances();
const txns = this.fetchTandZTransactions();
const addrs = this.fetchAllAddresses();
await balP;
await abP;
await txns;
await addrs;
// All done, set up next fetch
console.log(`Finished full refresh at ${latestBlockHeight}`);
} catch (err) {
// If we caught an error, there's something wrong with the connection.
this.fnSetDisconnected(`${err}`);
return;
}
} else {
// Still at the latest block
console.log('Already have latest block, waiting for next refresh');
}
this.setupNextFetch(latestBlockHeight);
}
// Special method to get the Info object. This is used both internally and by the Loading screen
static async getInfoObject(rpcConfig: RPCConfig) {
const infoResult = await RPC.doRPC('getinfo', [], rpcConfig);
const info = new Info();
info.testnet = infoResult.result.testnet;
info.latestBlock = infoResult.result.blocks;
info.connections = infoResult.result.connections;
info.version = infoResult.result.version;
info.currencyName = info.testnet ? 'TAZ' : 'ZEC';
info.zecPrice = null; // Setting this to null will copy over the existing price
info.disconnected = false;
const blkInfoResult = await RPC.doRPC('getblockchaininfo', [], rpcConfig);
info.verificationProgress = blkInfoResult.result.verificationprogress;
const solps = await RPC.doRPC('getnetworksolps', [], rpcConfig);
info.solps = solps.result;
return info;
}
async doImportPrivKey(key: string, rescan: boolean) {
// Z address
if (key.startsWith('SK') || key.startsWith('secret')) {
try {
const r = await RPC.doRPC('z_importkey', [key, rescan ? 'yes' : 'no'], this.rpcConfig);
console.log(r.result);
return '';
} catch (err) {
return err;
}
} else if (key.startsWith('zxview')) {
try {
const r = await RPC.doRPC('z_importviewingkey', [key, rescan ? 'yes' : 'no'], this.rpcConfig);
console.log(r.result);
return '';
} catch (err) {
return err;
}
} else {
try {
const r = await RPC.doRPC('importprivkey', [key, '', rescan], this.rpcConfig);
console.log(r.result);
return '';
} catch (err) {
return err;
}
}
}
async fetchInfo(): number {
const info = await RPC.getInfoObject(this.rpcConfig);
this.fnSetInfo(info);
return info.latestBlock;
}
// This method will get the total balances
async fetchTotalBalance() {
const response = await RPC.doRPC('z_gettotalbalance', [0], this.rpcConfig);
const balance = new TotalBalance();
balance.total = response.result.total;
balance.private = response.result.private;
balance.transparent = response.result.transparent;
this.fnSetTotalBalance(balance);
}
async createNewAddress(zaddress: boolean) {
if (zaddress) {
const newaddress = await RPC.doRPC('z_getnewaddress', ['sapling'], this.rpcConfig);
return newaddress.result;
// eslint-disable-next-line no-else-return
} else {
const newaddress = await RPC.doRPC('getnewaddress', [''], this.rpcConfig);
return newaddress.result;
}
}
async getPrivKeyAsString(address: string): string {
let method = '';
if (Utils.isZaddr(address)) {
method = 'z_exportkey';
} else if (Utils.isTransparent(address)) {
method = 'dumpprivkey';
}
const response = await RPC.doRPC(method, [address], this.rpcConfig);
return response.result;
}
async getViewKeyAsString(address: string): string {
let method = '';
if (Utils.isZaddr(address)) {
method = 'z_exportviewingkey';
} else {
return '';
}
const response = await RPC.doRPC(method, [address], this.rpcConfig);
return response.result;
}
// Fetch all addresses and their associated balances
async fetchTandZAddressesWithBalances() {
const zresponse = RPC.doRPC('z_listunspent', [0], this.rpcConfig);
const tresponse = RPC.doRPC('listunspent', [0], this.rpcConfig);
// Do the Z addresses
// response.result has all the unspent notes.
const unspentNotes = (await zresponse).result;
const zgroups = _.groupBy(unspentNotes, 'address');
const zaddresses = Object.keys(zgroups).map(address => {
const balance = zgroups[address].reduce((prev, obj) => prev + obj.amount, 0);
return new AddressBalance(address, Number(balance.toFixed(8)));
});
// Do the T addresses
const unspentTXOs = (await tresponse).result;
const tgroups = _.groupBy(unspentTXOs, 'address');
const taddresses = Object.keys(tgroups).map(address => {
const balance = tgroups[address].reduce((prev, obj) => prev + obj.amount, 0);
return new AddressBalance(address, Number(balance.toFixed(8)));
});
const addresses = zaddresses.concat(taddresses);
this.fnSetAddressesWithBalance(addresses);
}
// Fetch all T and Z transactions
async fetchTandZTransactions() {
const tresponse = await RPC.doRPC('listtransactions', [], this.rpcConfig);
const zaddressesPromise = RPC.doRPC('z_listaddresses', [], this.rpcConfig);
const senttxstorePromise = SentTxStore.loadSentTxns();
const ttxlist = tresponse.result.map(tx => {
const transaction = new Transaction();
transaction.address = tx.address;
transaction.type = tx.category;
transaction.amount = tx.amount;
transaction.fee = Math.abs(tx.fee || 0);
transaction.confirmations = tx.confirmations;
transaction.txid = tx.txid;
transaction.time = tx.time;
transaction.detailedTxns = [new TxDetail()];
transaction.detailedTxns[0].address = tx.address;
transaction.detailedTxns[0].amount = tx.amount;
return transaction;
});
// Now get Z txns
const zaddresses = await zaddressesPromise;
const alltxnsPromise = zaddresses.result.map(async zaddr => {
// For each zaddr, get the list of incoming transactions
const incomingTxns = await RPC.doRPC('z_listreceivedbyaddress', [zaddr, 0], this.rpcConfig);
const txns = incomingTxns.result
.filter(itx => !itx.change)
.map(incomingTx => {
return {
address: zaddr,
txid: incomingTx.txid,
memo: parseMemo(incomingTx.memo),
amount: incomingTx.amount,
index: incomingTx.outindex
};
});
return txns;
});
const alltxns = (await Promise.all(alltxnsPromise)).flat();
// Now, for each tx in the array, call gettransaction
const ztxlist = await Promise.all(
alltxns.map(async tx => {
const txresponse = await RPC.doRPC('gettransaction', [tx.txid], this.rpcConfig);
const transaction = new Transaction();
transaction.address = tx.address;
transaction.type = 'receive';
transaction.amount = tx.amount;
transaction.confirmations = txresponse.result.confirmations;
transaction.txid = tx.txid;
transaction.time = txresponse.result.time;
transaction.index = tx.index || 0;
transaction.detailedTxns = [new TxDetail()];
transaction.detailedTxns[0].address = tx.address;
transaction.detailedTxns[0].amount = tx.amount;
// eslint-disable-next-line no-control-regex
transaction.detailedTxns[0].memo = tx.memo ? tx.memo.replace(/\u0000/g, '') : tx.memo;
return transaction;
})
);
// Get transactions from the sent tx store
const sentTxns = await senttxstorePromise;
// Now concat the t and z transactions, and call the update function again
const alltxlist = ttxlist
.concat(ztxlist)
.concat(sentTxns)
.sort((tx1, tx2) => {
if (tx1.time && tx2.time) {
return tx2.time - tx1.time;
}
return tx1.confirmations - tx2.confirmations;
});
this.fnSetTransactionsList(alltxlist);
}
// Get all Addresses, including T and Z addresses
async fetchAllAddresses() {
const zaddrsPromise = RPC.doRPC('z_listaddresses', [], this.rpcConfig);
const taddrs2Promise = RPC.doRPC('listaddressgroupings', [], this.rpcConfig);
const allZ = (await zaddrsPromise).result;
const t2r = await taddrs2Promise;
const t2 = t2r.result.flatMap(a => a.flatMap(aa => aa[0]));
const allT = t2.filter(a => Utils.isTransparent(a));
this.fnSetAllAddresses(allZ.concat(allT));
}
// Send a transaction using the already constructed sendJson structure
async sendTransaction(sendJson: [], fnOpenSendErrorModal: (string, string) => void): boolean {
try {
const opid = (await RPC.doRPC('z_sendmany', sendJson, this.rpcConfig)).result;
const monitor = new OpidMonitor();
monitor.opid = opid;
monitor.fnOpenSendErrorModal = fnOpenSendErrorModal;
this.addOpidToMonitor(monitor);
return true;
} catch (err) {
// TODO Show a modal with the error
console.log(`Error sending Tx: ${err}`);
throw err;
}
}
// Start monitoring the given opid
async addOpidToMonitor(monitor: OpidMonitor) {
this.opids.add(monitor);
this.refreshOpStatus();
}
setupNextOpidSatusFetch() {
if (this.opids.size > 0) {
this.opTimerID = setTimeout(() => this.refreshOpStatus(), 2000); // 2 sec
} else {
this.opTimerID = null;
}
}
async refreshOpStatus() {
if (this.opids.size > 0) {
// Get all the operation statuses.
[...this.opids].map(async monitor => {
try {
const resultJson = await RPC.doRPC('z_getoperationstatus', [[monitor.opid]], this.rpcConfig);
const result = resultJson.result[0];
if (result.status === 'success') {
const { txid } = result.result;
monitor.fnOpenSendErrorModal(
'Successfully Broadcast Transaction',
`Transaction was successfully broadcast. TXID: ${txid}`
);
this.opids.delete(monitor);
// And force a refresh to update the balances etc...
this.refresh(null);
} else if (result.status === 'failed') {
monitor.fnOpenSendErrorModal(
'Error Sending Transaction',
`Opid ${monitor.opid} Failed. ${result.error.message}`
);
this.opids.delete(monitor);
}
} catch (err) {
// If we can't get a response for this OPID, then just forget it and move on
this.opids.delete(monitor);
}
});
}
this.setupNextOpidSatusFetch();
}
setupNextZecPriceRefresh(retryCount: number, timeout: number) {
// Every hour
this.priceTimerID = setTimeout(() => this.getZecPrice(retryCount), timeout);
}
async getZecPrice(retryCount: number) {
if (!retryCount) {
// eslint-disable-next-line no-param-reassign
retryCount = 0;
}
try {
const response = await new Promise((resolve, reject) => {
axios('https://api.coincap.io/v2/rates/zcash', {
method: 'GET'
})
.then(r => resolve(r.data))
.catch(err => {
reject(err);
});
});
const zecData = response.data;
if (zecData) {
this.fnSetZecPrice(zecData.rateUsd);
this.setupNextZecPriceRefresh(0, 1000 * 60 * 60); // Every hour
} else {
this.fnSetZecPrice(null);
let timeout = 1000 * 60; // 1 minute
if (retryCount > 5) {
timeout = 1000 * 60 * 60; // an hour later
}
this.setupNextZecPriceRefresh(retryCount + 1, timeout);
}
} catch (err) {
console.log(err);
this.fnSetZecPrice(null);
let timeout = 1000 * 60; // 1 minute
if (retryCount > 5) {
timeout = 1000 * 60 * 60; // an hour later
}
this.setupNextZecPriceRefresh(retryCount + 1, timeout);
}
}
}

View File

@ -1,37 +0,0 @@
import fs from 'fs';
import path from 'path';
import { remote } from 'electron';
import { AddressBookEntry } from '../components/AppState';
// Utility class to save / read the address book.
export default class AddressbookImpl {
static async getFileName() {
const dir = path.join(remote.app.getPath('appData'), 'zecwallet');
if (!fs.existsSync(dir)) {
await fs.promises.mkdir(dir);
}
const fileName = path.join(dir, 'AddressBook.json');
return fileName;
}
// Write the address book to disk
static async writeAddressBook(ab: AddressBookEntry[]) {
const fileName = await this.getFileName();
await fs.promises.writeFile(fileName, JSON.stringify(ab));
}
// Read the address book
static async readAddressBook(): AddressBookEntry[] {
const fileName = await this.getFileName();
try {
return JSON.parse(await fs.promises.readFile(fileName));
} catch (err) {
// File probably doesn't exist, so return nothing
console.log(err);
return [];
}
}
}

View File

@ -1,50 +0,0 @@
import os from 'os';
import path from 'path';
import fs from 'fs';
import { remote } from 'electron';
import { Transaction, TxDetail } from '../components/AppState';
export default class SentTxStore {
static locateSentTxStore() {
if (os.platform() === 'darwin') {
return path.join(remote.app.getPath('appData'), 'Zcash', 'senttxstore.dat');
}
if (os.platform() === 'linux') {
return path.join(
remote.app.getPath('home'),
'.local',
'share',
'zec-qt-wallet-org',
'zec-qt-wallet',
'senttxstore.dat'
);
}
return path.join(remote.app.getPath('appData'), 'Zcash', 'senttxstore.dat');
}
static async loadSentTxns(): Transaction[] {
try {
const sentTx = JSON.parse(await fs.promises.readFile(SentTxStore.locateSentTxStore()));
return sentTx.map(s => {
const transction = new Transaction();
transction.type = s.type;
transction.amount = s.amount;
transction.address = s.from;
transction.txid = s.txid;
transction.time = s.datetime;
transction.detailedTxns = [new TxDetail()];
transction.detailedTxns[0].address = s.address;
transction.detailedTxns[0].amount = s.amount;
transction.detailedTxns[0].memo = s.memo;
return transction;
});
} catch (err) {
// If error for whatever reason (most likely, file not found), just return an empty array
return [];
}
}
}

View File

@ -1,101 +0,0 @@
import { parseZcashURI } from './uris';
test('ZIP321 case 1', () => {
const targets = parseZcashURI(
'zcash:ztestsapling10yy2ex5dcqkclhc7z7yrnjq2z6feyjad56ptwlfgmy77dmaqqrl9gyhprdx59qgmsnyfska2kez?amount=1&memo=VGhpcyBpcyBhIHNpbXBsZSBtZW1vLg&message=Thank%20you%20for%20your%20purchase'
);
expect(targets.length).toBe(1);
expect(targets[0].address).toBe(
'ztestsapling10yy2ex5dcqkclhc7z7yrnjq2z6feyjad56ptwlfgmy77dmaqqrl9gyhprdx59qgmsnyfska2kez'
);
expect(targets[0].message).toBe('Thank you for your purchase');
expect(targets[0].label).toBeUndefined();
expect(targets[0].amount).toBe(1);
expect(targets[0].memoString).toBe('This is a simple memo.');
});
test('ZIP321 case 2', () => {
const targets = parseZcashURI(
'zcash:?address=tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU&amount=123.456&address.1=ztestsapling10yy2ex5dcqkclhc7z7yrnjq2z6feyjad56ptwlfgmy77dmaqqrl9gyhprdx59qgmsnyfska2kez&amount.1=0.789&memo.1=VGhpcyBpcyBhIHVuaWNvZGUgbWVtbyDinKjwn6aE8J-PhvCfjok'
);
expect(targets.length).toBe(2);
expect(targets[0].address).toBe('tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU');
expect(targets[0].message).toBeUndefined();
expect(targets[0].label).toBeUndefined();
expect(targets[0].amount).toBe(123.456);
expect(targets[0].memoString).toBeUndefined();
expect(targets[0].memoBase64).toBeUndefined();
expect(targets[1].address).toBe(
'ztestsapling10yy2ex5dcqkclhc7z7yrnjq2z6feyjad56ptwlfgmy77dmaqqrl9gyhprdx59qgmsnyfska2kez'
);
expect(targets[1].message).toBeUndefined();
expect(targets[1].label).toBeUndefined();
expect(targets[1].amount).toBe(0.789);
expect(targets[1].memoString).toBe('This is a unicode memo ✨🦄🏆🎉');
});
test('coinbase URI', () => {
const targets = parseZcashURI('zcash:tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU');
expect(targets.length).toBe(1);
expect(targets[0].message).toBeUndefined();
expect(targets[0].label).toBeUndefined();
expect(targets[0].amount).toBeUndefined();
expect(targets[0].memoString).toBeUndefined();
expect(targets[0].memoBase64).toBeUndefined();
});
test('Plain URI', () => {
const targets = parseZcashURI('tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU');
expect(targets.length).toBe(1);
expect(targets[0].message).toBeUndefined();
expect(targets[0].label).toBeUndefined();
expect(targets[0].amount).toBeUndefined();
expect(targets[0].memoString).toBeUndefined();
expect(targets[0].memoBase64).toBeUndefined();
});
test('bad uris', () => {
// bad protocol
let error = parseZcashURI('badprotocol:tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU?amount=123.456');
expect(typeof error).toBe('string');
// bad address
error = parseZcashURI('zcash:badaddress?amount=123.456');
expect(typeof error).toBe('string');
// no address
error = parseZcashURI('zcash:?amount=123.456');
expect(typeof error).toBe('string');
// bad param name
error = parseZcashURI('zcash:tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU?badparam=3');
expect(typeof error).toBe('string');
// index=1 doesn't have amount
error = parseZcashURI(
'zcash:tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU?amount=2&address.1=tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU'
);
expect(typeof error).toBe('string');
// duplicate param
error = parseZcashURI('zcash:tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU?amount=3&amount=3');
expect(typeof error).toBe('string');
// bad index
error = parseZcashURI(
'zcash:tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU?amount=2&address.a=tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU&amount.a=3'
);
expect(typeof error).toBe('string');
// index=1 is missing
error = parseZcashURI(
'zcash:tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU?amount=0.1&address.2=tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU&amount.2=2'
);
expect(typeof error).toBe('string');
});

View File

@ -1,4 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

View File

@ -1,39 +0,0 @@
image: Visual Studio 2017
platform:
- x64
environment:
matrix:
- nodejs_version: 10
cache:
- '%LOCALAPPDATA%/Yarn'
- node_modules
- app/node_modules
- flow-typed
- '%USERPROFILE%\.electron'
matrix:
fast_finish: true
build: off
version: '{build}'
shallow_clone: true
clone_depth: 1
install:
- ps: Install-Product node $env:nodejs_version x64
- set CI=true
- yarn
test_script:
- yarn package-ci
- yarn lint
# - yarn flow
- yarn test
- yarn build-e2e
- yarn test-e2e

View File

@ -1,66 +0,0 @@
/* eslint global-require: off */
const developmentEnvironments = ['development', 'test'];
const developmentPlugins = [require('react-hot-loader/babel')];
const productionPlugins = [
require('babel-plugin-dev-expression'),
// babel-preset-react-optimize
require('@babel/plugin-transform-react-constant-elements'),
require('@babel/plugin-transform-react-inline-elements'),
require('babel-plugin-transform-react-remove-prop-types')
];
module.exports = api => {
// see docs about api at https://babeljs.io/docs/en/config-files#apicache
const development = api.env(developmentEnvironments);
return {
presets: [
[
require('@babel/preset-env'),
{
targets: { electron: require('electron/package.json').version }
}
],
require('@babel/preset-flow'),
[require('@babel/preset-react'), { development }]
],
plugins: [
// Stage 0
require('@babel/plugin-proposal-function-bind'),
// Stage 1
require('@babel/plugin-proposal-export-default-from'),
require('@babel/plugin-proposal-logical-assignment-operators'),
[require('@babel/plugin-proposal-optional-chaining'), { loose: false }],
[
require('@babel/plugin-proposal-pipeline-operator'),
{ proposal: 'minimal' }
],
[
require('@babel/plugin-proposal-nullish-coalescing-operator'),
{ loose: false }
],
require('@babel/plugin-proposal-do-expressions'),
// Stage 2
[require('@babel/plugin-proposal-decorators'), { legacy: true }],
require('@babel/plugin-proposal-function-sent'),
require('@babel/plugin-proposal-export-namespace-from'),
require('@babel/plugin-proposal-numeric-separator'),
require('@babel/plugin-proposal-throw-expressions'),
// Stage 3
require('@babel/plugin-syntax-dynamic-import'),
require('@babel/plugin-syntax-import-meta'),
[require('@babel/plugin-proposal-class-properties'), { loose: true }],
require('@babel/plugin-proposal-json-strings'),
...(development ? developmentPlugins : productionPlugins)
]
};
};

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1 +0,0 @@
echo "VERSION=1.7.8" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append

View File

@ -1,3 +0,0 @@
#!/bin/bash
VERSION="1.7.8"
echo "VERSION=$VERSION" >> $GITHUB_ENV

View File

@ -1,47 +0,0 @@
#!/bin/bash
# Accept the variables as command line arguments as well
POSITIONAL=()
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
-v|--version)
APP_VERSION="$2"
shift # past argument
shift # past value
;;
*) # unknown option
POSITIONAL+=("$1") # save it in an array for later
shift # past argument
;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters
if [ -z $APP_VERSION ]; then echo "APP_VERSION is not set"; exit 1; fi
# Store the hash and signatures here
cd release
rm -rf signatures
mkdir signatures
# Remove previous signatures/hashes
rm -f sha256sum-$APP_VERSION.txt
rm -f signatures-$APP_VERSION.zip
# sha256sum the binaries
sha256sum Zecwallet*$APP_VERSION* > sha256sum-$APP_VERSION.txt
OIFS="$IFS"
IFS=$'\n'
for i in `find ./ -iname "Zecwallet*$APP_VERSION*" -o -iname "sha256sum-$APP_VERSION.txt"`; do
echo "Signing" "$i"
gpg --batch --output "signatures/$i.sig" --detach-sig "$i"
done
cp sha256sum-$APP_VERSION.txt signatures/
cp ../configs/SIGNATURES_README signatures/
zip -r signatures-$APP_VERSION.zip signatures/

Binary file not shown.

Binary file not shown.

View File

@ -1,12 +0,0 @@
This directory contains the hashes and signatures for ZecWallet
Verify the hashes by running:
sha256sum -c sha256sum-vX.Y.Z.txt
Verify signatures:
1. First, import the public key (Available on GitHub
at https://github.com/ZcashFoundation/zecwallet/blob/master/public_key.asc)
gpg --import public_key.asc
2. Verify signature
gpg --verify <filename.sig> <downloaded-filename-to-verify>

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
</dict>
</plist>

View File

@ -1,51 +0,0 @@
/**
* Base webpack config used across other specific configs
*/
import path from 'path';
import webpack from 'webpack';
import { dependencies as externals } from '../app/package.json';
export default {
externals: [...Object.keys(externals || {}), 'bufferutil', 'utf-8-validate'],
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true
}
}
}
]
},
output: {
path: path.join(__dirname, '..', 'app'),
// https://github.com/webpack/webpack/issues/1114
libraryTarget: 'commonjs2'
},
/**
* Determine the array of extensions that should be used to resolve modules.
*/
resolve: {
extensions: ['.js', '.jsx', '.json'],
modules: [path.join(__dirname, '..', 'app'), 'node_modules'],
alias: {
ws: path.resolve(path.join(__dirname, '..', 'node_modules/ws/index.js'))
}
},
plugins: [
new webpack.EnvironmentPlugin({
NODE_ENV: 'production'
}),
new webpack.NamedModulesPlugin()
]
};

View File

@ -1,4 +0,0 @@
/* eslint import/no-unresolved: off, import/no-self-import: off */
require('@babel/register');
module.exports = require('./webpack.config.renderer.dev.babel').default;

View File

@ -1,73 +0,0 @@
/**
* Webpack config for production electron main process
*/
import path from 'path';
import webpack from 'webpack';
import merge from 'webpack-merge';
import TerserPlugin from 'terser-webpack-plugin';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import baseConfig from './webpack.config.base';
import CheckNodeEnv from '../internals/scripts/CheckNodeEnv';
CheckNodeEnv('production');
export default merge.smart(baseConfig, {
devtool: 'source-map',
mode: 'production',
target: 'electron-main',
entry: './app/main.dev',
output: {
path: path.join(__dirname, '..'),
filename: './app/main.prod.js'
},
optimization: {
minimizer: process.env.E2E_BUILD
? []
: [
new TerserPlugin({
parallel: true,
sourceMap: true,
cache: true
})
]
},
plugins: [
new BundleAnalyzerPlugin({
analyzerMode:
process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled',
openAnalyzer: process.env.OPEN_ANALYZER === 'true'
}),
/**
* Create global constants which can be configured at compile time.
*
* Useful for allowing different behaviour between development builds and
* release builds
*
* NODE_ENV should be production so that modules do not perform certain
* development checks
*/
new webpack.EnvironmentPlugin({
NODE_ENV: 'production',
DEBUG_PROD: false,
START_MINIMIZED: false
})
],
/**
* Disables webpack processing of __dirname and __filename.
* If you run the bundle in node.js it falls back to these values of node.js.
* https://github.com/webpack/webpack/issues/2010
*/
node: {
__dirname: false,
__filename: false
}
});

View File

@ -1,280 +0,0 @@
/* eslint global-require: off, import/no-dynamic-require: off */
/**
* Build config for development electron renderer process that uses
* Hot-Module-Replacement
*
* https://webpack.js.org/concepts/hot-module-replacement/
*/
import path from 'path';
import fs from 'fs';
import webpack from 'webpack';
import chalk from 'chalk';
import merge from 'webpack-merge';
import { spawn, execSync } from 'child_process';
import baseConfig from './webpack.config.base';
import CheckNodeEnv from '../internals/scripts/CheckNodeEnv';
// When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's
// at the dev webpack config is not accidentally run in a production environment
if (process.env.NODE_ENV === 'production') {
CheckNodeEnv('development');
}
const port = process.env.PORT || 1212;
const publicPath = `http://localhost:${port}/dist`;
const dll = path.join(__dirname, '..', 'dll');
const manifest = path.resolve(dll, 'renderer.json');
const requiredByDLLConfig = module.parent.filename.includes('webpack.config.renderer.dev.dll');
/**
* Warn if the DLL is not built
*/
if (!requiredByDLLConfig && !(fs.existsSync(dll) && fs.existsSync(manifest))) {
console.log(
chalk.black.bgYellow.bold('The DLL files are missing. Sit back while we build them for you with "yarn build-dll"')
);
execSync('yarn build-dll');
}
export default merge.smart(baseConfig, {
devtool: 'inline-source-map',
mode: 'development',
target: 'electron-renderer',
entry: [
...(process.env.PLAIN_HMR ? [] : ['react-hot-loader/patch']),
`webpack-dev-server/client?http://localhost:${port}/`,
'webpack/hot/only-dev-server',
require.resolve('../app/index')
],
output: {
publicPath: `http://localhost:${port}/dist/`,
filename: 'renderer.dev.js'
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true
}
}
},
{
test: /\.global\.css$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
sourceMap: true
}
}
]
},
{
test: /^((?!\.global).)*\.css$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]__[hash:base64:5]'
},
sourceMap: true,
importLoaders: 1
}
}
]
},
// SASS support - compile all .global.scss files and pipe it to style.css
{
test: /\.global\.(scss|sass)$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
sourceMap: true
}
},
{
loader: 'sass-loader'
}
]
},
// SASS support - compile all other .scss files and pipe it to style.css
{
test: /^((?!\.global).)*\.(scss|sass)$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]__[hash:base64:5]'
},
sourceMap: true,
importLoaders: 1
}
},
{
loader: 'sass-loader'
}
]
},
// WOFF Font
{
test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
mimetype: 'application/font-woff'
}
}
},
// WOFF2 Font
{
test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
mimetype: 'application/font-woff'
}
}
},
// TTF Font
{
test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
mimetype: 'application/octet-stream'
}
}
},
// EOT Font
{
test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
use: 'file-loader'
},
// SVG Font
{
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
mimetype: 'image/svg+xml'
}
}
},
// Common Image Formats
{
test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/,
use: 'url-loader'
}
]
},
resolve: {
alias: {
'react-dom': '@hot-loader/react-dom',
ws: path.resolve(path.join(__dirname, '..', 'node_modules/ws/index.js'))
}
},
plugins: [
requiredByDLLConfig
? null
: new webpack.DllReferencePlugin({
context: path.join(__dirname, '..', 'dll'),
manifest: require(manifest),
sourceType: 'var'
}),
new webpack.HotModuleReplacementPlugin({
multiStep: true
}),
new webpack.NoEmitOnErrorsPlugin(),
/**
* Create global constants which can be configured at compile time.
*
* Useful for allowing different behaviour between development builds and
* release builds
*
* NODE_ENV should be production so that modules do not perform certain
* development checks
*
* By default, use 'development' as NODE_ENV. This can be overridden with
* 'staging', for example, by changing the ENV variables in the npm scripts
*/
new webpack.EnvironmentPlugin({
NODE_ENV: 'development'
}),
new webpack.LoaderOptionsPlugin({
debug: true
})
],
node: {
__dirname: false,
__filename: false
},
devServer: {
port,
publicPath,
compress: true,
noInfo: true,
stats: 'errors-only',
inline: true,
lazy: false,
hot: true,
headers: { 'Access-Control-Allow-Origin': '*' },
contentBase: path.join(__dirname, 'dist'),
watchOptions: {
aggregateTimeout: 300,
ignored: /node_modules/,
poll: 100
},
historyApiFallback: {
verbose: true,
disableDotRule: false
},
before() {
if (process.env.START_HOT) {
console.log('Starting Main Process...');
spawn('npm', ['run', 'start-main-dev'], {
shell: true,
env: process.env,
stdio: 'inherit'
})
.on('close', code => process.exit(code))
.on('error', spawnError => console.error(spawnError));
}
}
}
});

View File

@ -1,74 +0,0 @@
/* eslint global-require: off, import/no-dynamic-require: off */
/**
* Builds the DLL for development electron renderer process
*/
import webpack from 'webpack';
import path from 'path';
import merge from 'webpack-merge';
import baseConfig from './webpack.config.base';
import { dependencies } from '../package.json';
import CheckNodeEnv from '../internals/scripts/CheckNodeEnv';
CheckNodeEnv('development');
const dist = path.join(__dirname, '..', 'dll');
export default merge.smart(baseConfig, {
context: path.join(__dirname, '..'),
devtool: 'eval',
mode: 'development',
target: 'electron-renderer',
externals: ['fsevents', 'crypto-browserify'],
/**
* Use `module` from `webpack.config.renderer.dev.js`
*/
module: require('./webpack.config.renderer.dev.babel').default.module,
entry: {
renderer: Object.keys(dependencies || {})
},
output: {
library: 'renderer',
path: dist,
filename: '[name].dev.dll.js',
libraryTarget: 'var'
},
plugins: [
new webpack.DllPlugin({
path: path.join(dist, '[name].json'),
name: '[name]'
}),
/**
* Create global constants which can be configured at compile time.
*
* Useful for allowing different behaviour between development builds and
* release builds
*
* NODE_ENV should be production so that modules do not perform certain
* development checks
*/
new webpack.EnvironmentPlugin({
NODE_ENV: 'development'
}),
new webpack.LoaderOptionsPlugin({
debug: true,
options: {
context: path.join(__dirname, '..', 'app'),
output: {
path: path.join(__dirname, '..', 'dll')
}
}
})
]
});

View File

@ -1,218 +0,0 @@
/**
* Build config for electron renderer process
*/
import path from 'path';
import webpack from 'webpack';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import OptimizeCSSAssetsPlugin from 'optimize-css-assets-webpack-plugin';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import merge from 'webpack-merge';
import TerserPlugin from 'terser-webpack-plugin';
import baseConfig from './webpack.config.base';
import CheckNodeEnv from '../internals/scripts/CheckNodeEnv';
CheckNodeEnv('production');
export default merge.smart(baseConfig, {
devtool: 'source-map',
mode: 'production',
target: 'electron-renderer',
entry: path.join(__dirname, '..', 'app/index'),
output: {
path: path.join(__dirname, '..', 'app/dist'),
publicPath: './dist/',
filename: 'renderer.prod.js'
},
module: {
rules: [
// Extract all .global.css to style.css as is
{
test: /\.global\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: './'
}
},
{
loader: 'css-loader',
options: {
sourceMap: true
}
}
]
},
// Pipe other styles through css modules and append to style.css
{
test: /^((?!\.global).)*\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader
},
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]__[hash:base64:5]'
},
sourceMap: true
}
}
]
},
// Add SASS support - compile all .global.scss files and pipe it to style.css
{
test: /\.global\.(scss|sass)$/,
use: [
{
loader: MiniCssExtractPlugin.loader
},
{
loader: 'css-loader',
options: {
sourceMap: true,
importLoaders: 1
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
}
]
},
// Add SASS support - compile all other .scss files and pipe it to style.css
{
test: /^((?!\.global).)*\.(scss|sass)$/,
use: [
{
loader: MiniCssExtractPlugin.loader
},
{
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]__[local]__[hash:base64:5]'
},
importLoaders: 1,
sourceMap: true
}
},
{
loader: 'sass-loader',
options: {
sourceMap: true
}
}
]
},
// WOFF Font
{
test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
mimetype: 'application/font-woff'
}
}
},
// WOFF2 Font
{
test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
mimetype: 'application/font-woff'
}
}
},
// TTF Font
{
test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
mimetype: 'application/octet-stream'
}
}
},
// EOT Font
{
test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
use: 'file-loader'
},
// SVG Font
{
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
use: {
loader: 'url-loader',
options: {
limit: 10000,
mimetype: 'image/svg+xml'
}
}
},
// Common Image Formats
{
test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/,
use: 'url-loader'
}
]
},
optimization: {
minimizer: process.env.E2E_BUILD
? []
: [
new TerserPlugin({
parallel: true,
sourceMap: true,
cache: true
}),
new OptimizeCSSAssetsPlugin({
cssProcessorOptions: {
map: {
inline: false,
annotation: true
}
}
})
]
},
plugins: [
/**
* Create global constants which can be configured at compile time.
*
* Useful for allowing different behaviour between development builds and
* release builds
*
* NODE_ENV should be production so that modules do not perform certain
* development checks
*/
new webpack.EnvironmentPlugin({
NODE_ENV: 'production'
}),
new MiniCssExtractPlugin({
filename: 'style.css'
}),
new BundleAnalyzerPlugin({
analyzerMode:
process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled',
openAnalyzer: process.env.OPEN_ANALYZER === 'true'
})
]
});

View File

@ -1,3 +0,0 @@
declare module 'module' {
declare module.exports: any;
}

View File

@ -1,3 +0,0 @@
// @flow
declare export default { [key: string]: string }

View File

@ -1,2 +0,0 @@
// @flow
declare export default string

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1 +0,0 @@
export default 'test-file-stub';

View File

@ -1,5 +0,0 @@
const path = require('path');
require('@babel/register')({
cwd: path.join(__dirname, '..', '..')
});

View File

@ -1,35 +0,0 @@
// @flow
// Check if the renderer and main bundles are built
import path from 'path';
import chalk from 'chalk';
import fs from 'fs';
function CheckBuildsExist() {
const mainPath = path.join(__dirname, '..', '..', 'app', 'main.prod.js');
const rendererPath = path.join(
__dirname,
'..',
'..',
'app',
'dist',
'renderer.prod.js'
);
if (!fs.existsSync(mainPath)) {
throw new Error(
chalk.whiteBright.bgRed.bold(
'The main process is not built yet. Build it by running "yarn build-main"'
)
);
}
if (!fs.existsSync(rendererPath)) {
throw new Error(
chalk.whiteBright.bgRed.bold(
'The renderer process is not built yet. Build it by running "yarn build-renderer"'
)
);
}
}
CheckBuildsExist();

View File

@ -1,51 +0,0 @@
// @flow
import fs from 'fs';
import chalk from 'chalk';
import { execSync } from 'child_process';
import { dependencies } from '../../package.json';
(() => {
if (!dependencies) return;
const dependenciesKeys = Object.keys(dependencies);
const nativeDeps = fs
.readdirSync('node_modules')
.filter(folder => fs.existsSync(`node_modules/${folder}/binding.gyp`));
try {
// Find the reason for why the dependency is installed. If it is installed
// because of a devDependency then that is okay. Warn when it is installed
// because of a dependency
const { dependencies: dependenciesObject } = JSON.parse(
execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString()
);
const rootDependencies = Object.keys(dependenciesObject);
const filteredRootDependencies = rootDependencies.filter(rootDependency =>
dependenciesKeys.includes(rootDependency)
);
if (filteredRootDependencies.length > 0) {
const plural = filteredRootDependencies.length > 1;
console.log(`
${chalk.whiteBright.bgYellow.bold(
'Webpack does not work with native dependencies.'
)}
${chalk.bold(filteredRootDependencies.join(', '))} ${
plural ? 'are native dependencies' : 'is a native dependency'
} and should be installed inside of the "./app" folder.
First uninstall the packages from "./package.json":
${chalk.whiteBright.bgGreen.bold('yarn remove your-package')}
${chalk.bold(
'Then, instead of installing the package to the root "./package.json":'
)}
${chalk.whiteBright.bgRed.bold('yarn add your-package')}
${chalk.bold('Install the package to "./app/package.json"')}
${chalk.whiteBright.bgGreen.bold('cd ./app && yarn add your-package')}
Read more about native dependencies at:
${chalk.bold(
'https://github.com/electron-react-boilerplate/electron-react-boilerplate/wiki/Module-Structure----Two-package.json-Structure'
)}
`);
process.exit(1);
}
} catch (e) {
console.log('Native dependencies could not be checked');
}
})();

View File

@ -1,17 +0,0 @@
// @flow
import chalk from 'chalk';
export default function CheckNodeEnv(expectedEnv: string) {
if (!expectedEnv) {
throw new Error('"expectedEnv" not set');
}
if (process.env.NODE_ENV !== expectedEnv) {
console.log(
chalk.whiteBright.bgRed.bold(
`"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config`
)
);
process.exit(2);
}
}

View File

@ -1,19 +0,0 @@
// @flow
import chalk from 'chalk';
import detectPort from 'detect-port';
(function CheckPortInUse() {
const port: string = process.env.PORT || '1212';
detectPort(port, (err: ?Error, availablePort: number) => {
if (port !== String(availablePort)) {
throw new Error(
chalk.whiteBright.bgRed.bold(
`Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 yarn dev`
)
);
} else {
process.exit(0);
}
});
})();

View File

@ -1,7 +0,0 @@
// @flow
if (!/yarn\.js$/.test(process.env.npm_execpath || '')) {
console.warn(
"\u001b[33mYou don't seem to be using yarn. This could produce unexpected results.\u001b[39m"
);
}

View File

@ -1,31 +0,0 @@
// @flow
import path from 'path';
import { execSync } from 'child_process';
import fs from 'fs';
import { dependencies } from '../../app/package.json';
(() => {
const nodeModulesPath = path.join(
__dirname,
'..',
'..',
'app',
'node_modules'
);
if (
Object.keys(dependencies || {}).length > 0 &&
fs.existsSync(nodeModulesPath)
) {
const electronRebuildCmd =
'../node_modules/.bin/electron-rebuild --parallel --force --types prod,dev,optional --module-dir .';
const cmd =
process.platform === 'win32'
? electronRebuildCmd.replace(/\//g, '\\')
: electronRebuildCmd;
execSync(cmd, {
cwd: path.join(__dirname, '..', '..', 'app'),
stdio: 'inherit'
});
}
})();

View File

@ -3,163 +3,57 @@
"productName": "Zecwallet Fullnode",
"version": "1.7.8",
"description": "Zecwallet Fullnode (Electron version)",
"private": true,
"main": "./public/electron.js",
"homepage": "./",
"dependencies": {
"@fortawesome/fontawesome-free": "^6.1.1",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^0.27.2",
"dateformat": "^5.0.3",
"fs": "^0.0.1-security",
"ini": "^3.0.0",
"js-base64": "^3.7.2",
"path": "^0.12.7",
"qrcode.react": "^3.0.2",
"querystring": "^0.2.1",
"react": "^18.2.0",
"react-accessible-accordion": "^5.0.0",
"react-dom": "^18.2.0",
"react-modal": "^3.15.1",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.1",
"react-select": "^5.3.2",
"react-tabs": "^5.1.0",
"react-textarea-autosize": "^8.3.4",
"typeface-roboto": "^1.1.13",
"typescript": "^4.7.3",
"underscore": "^1.13.4",
"url": "^0.11.0",
"web-vitals": "^2.1.4"
},
"scripts": {
"build": "concurrently \"yarn build-main\" \"yarn build-renderer\"",
"build-dll": "cross-env NODE_ENV=development webpack --config ./configs/webpack.config.renderer.dev.dll.babel.js --colors",
"build-e2e": "cross-env E2E_BUILD=true yarn build",
"build-main": "cross-env NODE_ENV=production webpack --config ./configs/webpack.config.main.prod.babel.js --colors",
"build-renderer": "cross-env NODE_ENV=production webpack --config ./configs/webpack.config.renderer.prod.babel.js --colors",
"dev": "cross-env START_HOT=1 node -r @babel/register ./internals/scripts/CheckPortInUse.js && cross-env START_HOT=1 yarn start-renderer-dev",
"electron-rebuild": "electron-rebuild --parallel --force --types prod,dev,optional --module-dir app",
"flow": "flow",
"flow-typed": "rimraf flow-typed/npm && flow-typed install --overwrite || true",
"lint": "cross-env NODE_ENV=development eslint --cache --format=pretty .",
"lint-fix": "yarn --silent lint --fix; exit 0",
"lint-styles": "stylelint --ignore-path .eslintignore '**/*.*(css|scss)' --syntax scss",
"lint-styles-fix": "yarn --silent lint-styles --fix; exit 0",
"package": "yarn build && electron-builder build --publish never",
"package-all": "yarn build && electron-builder build -mwl",
"package-ci": "yarn postinstall && yarn build && electron-builder --publish always",
"package-mac": "yarn build && electron-builder build --mac --publish=never",
"package-linux": "yarn build && electron-builder build --linux --publish=never",
"package-win": "yarn build && electron-builder build --win --x64 --publish=never",
"postinstall": "node -r @babel/register internals/scripts/CheckNativeDep.js && yarn flow-typed && electron-builder install-app-deps && yarn build-dll && opencollective-postinstall",
"postlint-fix": "prettier --ignore-path .eslintignore --single-quote --write '**/*.{js,jsx,json,html,css,less,scss,yml}'",
"postlint-styles-fix": "prettier --ignore-path .eslintignore --single-quote --write '**/*.{css,scss}'",
"preinstall": "node ./internals/scripts/CheckYarn.js",
"prestart": "yarn build",
"start": "cross-env NODE_ENV=production electron ./app/main.prod.js",
"start-main-dev": "cross-env START_HOT=1 NODE_ENV=development electron -r @babel/register ./app/main.dev.js --remote-debugging-port=9223",
"start-renderer-dev": "cross-env NODE_ENV=development webpack-dev-server --config configs/webpack.config.renderer.dev.babel.js",
"test": "jest"
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"electron:prod": "yarn build && cross-env NODE_ENV=production && electron build/electron.js",
"electron:start": "concurrently -k \"cross-env BROWSER=none yarn start\" \"wait-on http://localhost:3000 && electronmon .\"",
"package-mac": "yarn build && electron-builder -m -c.extraMetadata.main=build/electron.js",
"package-win": "yarn build && electron-builder -w -c.extraMetadata.main=build/electron.js",
"package-linux": "yarn build && electron-builder -l -c.extraMetadata.main=build/electron.js"
},
"lint-staged": {
"*.{js,jsx}": [
"cross-env NODE_ENV=development eslint --cache --format=pretty",
"git add"
],
"{*.json,.{babelrc,eslintrc,prettierrc,stylelintrc}}": [
"prettier --ignore-path .eslintignore --parser json --write",
"git add"
],
"*.{css,scss}": [
"stylelint --ignore-path .eslintignore --syntax scss --fix",
"prettier --ignore-path .eslintignore --single-quote --write",
"git add"
],
"*.{html,md,yml}": [
"prettier --ignore-path .eslintignore --single-quote --write",
"git add"
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"build": {
"productName": "Zecwallet Fullnode",
"appId": "co.zecwallet.fullnode",
"afterAllArtifactBuild": "./afterSignHook.js",
"files": [
"dist/",
"node_modules/",
"app.html",
"main.prod.js",
"main.prod.js.map",
"package.json"
],
"dmg": {
"contents": [
{
"x": 130,
"y": 220
},
{
"x": 410,
"y": 220,
"type": "link",
"path": "/Applications"
}
]
},
"win": {
"icon": "./resources/icon.ico",
"extraFiles": [
{
"from": "bin/win",
"to": "resources/bin/win",
"filter": [
"**/**"
]
}
],
"target": [
"zip",
"msi"
]
},
"mac": {
"category": "public.app-category.productivity",
"type": "distribution",
"hardenedRuntime": true,
"entitlements": "./configs/entitlements.mac.inherit.plist",
"target": [
"dmg"
],
"icon": "./resources/icon.icns",
"extraFiles": [
{
"from": "bin/mac",
"to": "resources/bin/mac",
"filter": [
"**/*"
]
}
]
},
"deb": {
"depends": [
"gconf2",
"gconf-service",
"libnotify4",
"libappindicator1",
"libxtst6",
"libnss3"
],
"artifactName": "Zecwallet_Fullnode_${version}_${arch}.deb"
},
"linux": {
"category": "Development",
"icon": "./resources/icon.icns",
"extraFiles": [
{
"from": "bin/linux",
"to": "resources/bin/linux",
"filter": [
"**/**"
]
}
],
"target": [
"deb",
"AppImage"
],
"desktop": {
"Name": "ZecWallet",
"Comment": "Full node and wallet for Zcash",
"GenericName": "Wallet",
"Type": "Application",
"StartupNotify": true,
"StartupWMClass": "zecwallet",
"Categories": "Utility;",
"MimeType": "x-scheme-handler/zcash;",
"Keywords": "zecwallet;"
}
},
"directories": {
"buildResources": "resources",
"output": "release"
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/adityapk00/zecwallet-electron.git"
"url": "git+https://github.com/zcashfoundation/zecwallet.git"
},
"author": {
"name": "Aditya Kulkarni",
@ -174,171 +68,55 @@
}
],
"license": "MIT",
"bugs": {
"url": "https://github.com/adityapk00/zecwallet-electron/issues"
"build": {
"productName": "Zecwallet Fullnode",
"appId": "co.zecwallet.fullnode",
"afterAllArtifactBuild": "./afterSignHook.js",
"files": [
"build/**/*",
"node_modules/**/*"
],
"directories": {
"buildResources": "public"
},
"mac": {
"target": "dmg"
},
"win": {
"target": [
"zip",
"msi"
]
},
"linux": {
"target": "deb"
}
},
"keywords": [
"electron",
"react",
"flow",
"sass",
"webpack"
],
"homepage": "https://github.com/adityapk00/zecwallet-electron#readme",
"jest": {
"testURL": "http://localhost/",
"moduleNameMapper": {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/internals/mocks/fileMock.js",
"\\.(css|less|sass|scss)$": "identity-obj-proxy"
},
"moduleFileExtensions": [
"js",
"jsx",
"json"
"browserslist": {
"production": [
"last 1 electron version"
],
"moduleDirectories": [
"node_modules",
"app/node_modules"
],
"transform": {
"^.+\\.jsx?$": "babel-jest"
},
"setupFiles": [
"./internals/scripts/CheckBuildsExist.js"
"development": [
"last 1 electron version"
]
},
"devDependencies": {
"@babel/core": "^7.7.5",
"@babel/plugin-proposal-class-properties": "^7.7.4",
"@babel/plugin-proposal-decorators": "^7.7.4",
"@babel/plugin-proposal-do-expressions": "^7.7.4",
"@babel/plugin-proposal-export-default-from": "^7.7.4",
"@babel/plugin-proposal-export-namespace-from": "^7.7.4",
"@babel/plugin-proposal-function-bind": "^7.7.4",
"@babel/plugin-proposal-function-sent": "^7.7.4",
"@babel/plugin-proposal-json-strings": "^7.7.4",
"@babel/plugin-proposal-logical-assignment-operators": "^7.7.4",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.7.4",
"@babel/plugin-proposal-numeric-separator": "^7.7.4",
"@babel/plugin-proposal-optional-chaining": "^7.7.5",
"@babel/plugin-proposal-pipeline-operator": "^7.7.4",
"@babel/plugin-proposal-throw-expressions": "^7.7.4",
"@babel/plugin-syntax-dynamic-import": "^7.7.4",
"@babel/plugin-syntax-import-meta": "^7.7.4",
"@babel/plugin-transform-react-constant-elements": "^7.7.4",
"@babel/plugin-transform-react-inline-elements": "^7.7.4",
"@babel/preset-env": "^7.7.6",
"@babel/preset-flow": "^7.7.4",
"@babel/preset-react": "^7.7.4",
"@babel/register": "^7.7.4",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "^10.0.3",
"babel-jest": "^24.9.0",
"babel-loader": "^8.0.6",
"babel-plugin-dev-expression": "^0.2.2",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"chalk": "^3.0.0",
"concurrently": "^5.0.1",
"cross-env": "^6.0.3",
"cross-spawn": "^7.0.1",
"css-loader": "^3.3.2",
"detect-port": "^1.3.0",
"electron": "7.2.4",
"electron-builder": "^22.2.0",
"electron-devtools-installer": "^2.2.4",
"electron-notarize": "^0.2.1",
"electron-rebuild": "^1.8.8",
"enzyme": "^3.7.0",
"enzyme-adapter-react-16": "^1.7.0",
"enzyme-to-json": "^3.3.4",
"eslint": "^6.7.2",
"eslint-config-airbnb": "^18.0.1",
"eslint-config-erb": "^0.1.1",
"eslint-config-prettier": "^6.6.0",
"eslint-formatter-pretty": "^3.0.0",
"eslint-import-resolver-webpack": "^0.12.0",
"eslint-plugin-compat": "^3.3.0",
"eslint-plugin-flowtype": "^4.5.2",
"eslint-plugin-import": "^2.19.1",
"eslint-plugin-jest": "^23.1.1",
"eslint-plugin-jsx-a11y": "6.2.3",
"eslint-plugin-prettier": "^3.1.1",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "^7.17.0",
"eslint-plugin-testcafe": "^0.2.1",
"fbjs-scripts": "^1.2.0",
"file-loader": "^5.0.2",
"flow-bin": "^0.113.0",
"flow-runtime": "^0.17.0",
"flow-typed": "^2.6.2",
"husky": "^3.1.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^26.5.3",
"lint-staged": "^9.5.0",
"mini-css-extract-plugin": "^0.8.0",
"node-sass": "^4.13.0",
"opencollective-postinstall": "^2.0.2",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"prettier": "^1.19.1",
"react-test-renderer": "^16.12.0",
"redux-logger": "^3.0.6",
"rimraf": "^3.0.0",
"sass-loader": "^8.0.0",
"sinon": "^7.5.0",
"spectron": "^9.0.0",
"style-loader": "^1.0.1",
"stylelint": "^12.0.0",
"stylelint-config-prettier": "^8.0.0",
"stylelint-config-standard": "^19.0.0",
"terser-webpack-plugin": "^2.3.0",
"url-loader": "^3.0.0",
"webpack": "^4.41.2",
"webpack-bundle-analyzer": "^3.6.0",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.9.0",
"webpack-merge": "^4.2.2",
"yarn": "^1.22.0"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^5.12.0",
"@hot-loader/react-dom": "^16.11.0",
"axios": "^0.21.1",
"dateformat": "^3.0.3",
"electron-debug": "^3.0.1",
"electron-log": "^4.0.0",
"electron-store": "^5.1.1",
"hex-string": "^1.0.3",
"history": "^4.10.1",
"ini": "^1.3.5",
"js-base64": "^3.5.2",
"js-sha256": "^0.9.0",
"libsodium-wrappers-sumo": "^0.7.6",
"progress-stream": "^2.0.0",
"qrcode.react": "^1.0.0",
"react": "^16.12.0",
"react-accessible-accordion": "^3.0.1",
"react-dom": "^16.12.0",
"react-hot-loader": "^4.12.18",
"react-modal": "^3.11.1",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
"react-select": "^3.0.8",
"react-tabs": "^3.1.0",
"react-textarea-autosize": "^7.1.2",
"source-map-support": "^0.5.16",
"typeface-roboto": "^0.0.75",
"underscore": "^1.9.2",
"ws": "^7.2.1"
},
"devEngines": {
"node": ">=7.x",
"npm": ">=4.x",
"yarn": ">=0.21.3"
},
"browserslist": "electron 1.6",
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
"@types/dateformat": "^5.0.0",
"@types/ini": "^1.3.31",
"@types/jest": "^27.5.2",
"@types/js-base64": "^3.3.1",
"@types/node": "^16.11.41",
"@types/react": "^18.0.14",
"@types/react-dom": "^18.0.5",
"@types/react-modal": "^3.13.1",
"@types/react-router-dom": "^5.3.3",
"@types/underscore": "^1.11.4",
"concurrently": "^7.2.2",
"cross-env": "^7.0.3",
"electron": "^19.0.4",
"electron-builder": "^23.0.3",
"electron-notarize": "^1.2.1",
"electronmon": "^2.0.2",
"wait-on": "^6.0.1"
}
}

165
public/electron.js Normal file
View File

@ -0,0 +1,165 @@
// Module to control the application lifecycle and the native browser window.
const { app, BrowserWindow, protocol, ipcMain, dialog } = require("electron");
const path = require("path");
const url = require("url");
const axios = require("axios");
const fs = require("fs");
ipcMain.handle("getAppPath", (_, pathType) => {
return app.getPath(pathType);
});
ipcMain.handle("pathJoin_IPC", (_, dir, filename) => {
return path.join(dir, filename);
});
ipcMain.handle("doRPC_IPC", async (_, config, method, params) => {
const response = await new Promise((resolve, reject) => {
axios(config.url, {
data: {
jsonrpc: "2.0",
id: "curltest",
method: method,
params: params,
},
method: "POST",
auth: {
username: config.username,
password: config.password,
},
})
.then((r) => resolve(r.data))
.catch((err) => {
const e = { ...err };
console.log(
`Caught error: ${e.response.toString()} - ${
e.response ? e.response.data.toString() : ""
}`
);
if (e.response && e.response.data) {
reject(e.response.data.toString());
} else {
reject("NO_CONNECTION");
}
});
});
return response;
});
ipcMain.handle("getZecPrice_IPC", async () => {
const response = await new Promise((resolve, reject) => {
axios("https://api.coincap.io/v2/rates/zcash", {
method: "GET",
})
.then((r) => resolve(r.data))
.catch((err) => {
reject(err);
});
});
return response;
});
ipcMain.handle(
"showSaveDialog_IPC",
async (_, title, defaultPath, filters, properties) => {
return dialog.showSaveDialog({ title, defaultPath, filters, properties });
}
);
ipcMain.handle("writefile_IPC", async (_, filename, content) => {
return fs.promises.writeFile(filename, content);
});
ipcMain.handle("readfile_IPC", async (_, filename) => {
return JSON.parse(await fs.promises.readFile(filename));
});
// Create the native browser window.
function createWindow() {
const mainWindow = new BrowserWindow({
width: 2400,
height: 800,
// Set the path of an additional "preload" script that can be used to
// communicate between node-land and browser-land.
webPreferences: {
preload: path.join(__dirname, "preload.js"),
},
});
// In production, set the initial browser path to the local bundle generated
// by the Create React App build process.
// In development, set it to localhost to allow live/hot-reloading.
const appURL = app.isPackaged
? url.format({
pathname: path.join(__dirname, "index.html"),
protocol: "file:",
slashes: true,
})
: "http://127.0.0.1:3000";
mainWindow.loadURL(appURL);
// Automatically open Chrome's DevTools in development mode.
if (!app.isPackaged) {
mainWindow.webContents.openDevTools();
}
}
// Setup a local proxy to adjust the paths of requested files when loading
// them from the local production bundle (e.g.: local fonts, etc...).
function setupLocalFilesNormalizerProxy() {
protocol.registerHttpProtocol(
"file",
(request, callback) => {
const url = request.url.substr(8);
callback({ path: path.normalize(`${__dirname}/${url}`) });
},
(error) => {
if (error) console.error("Failed to register protocol");
}
);
}
// This method will be called when Electron has finished its initialization and
// is ready to create the browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
createWindow();
setupLocalFilesNormalizerProxy();
app.on("activate", function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
// Quit when all windows are closed, except on macOS.
// There, it's common for applications and their menu bar to stay active until
// the user quits explicitly with Cmd + Q.
app.on("window-all-closed", function () {
if (process.platform !== "darwin") {
app.quit();
}
});
// If your app has no need to navigate or only needs to navigate to known pages,
// it is a good idea to limit navigation outright to that known scope,
// disallowing any other kinds of navigation.
const allowedNavigationDestinations = "https://my-electron-app.com";
app.on("web-contents-created", (event, contents) => {
contents.on("will-navigate", (event, navigationUrl) => {
console.log("About to navigate!");
const parsedUrl = new URL(navigationUrl);
if (!allowedNavigationDestinations.includes(parsedUrl.origin)) {
event.preventDefault();
}
});
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Some files were not shown because too many files have changed in this diff Show More