@ -0,0 +1,20 @@
|
||||
[ignore]
|
||||
.*/node_modules/bitcoinjs-lib-zcash/.*
|
||||
.*/node_modules/bitcoinjs-lib/.*
|
||||
.*/node_modules/hd-wallet/.*
|
||||
.*/node_modules/protobufjs-old-fixed-webpack/src/bower.json
|
||||
.*/node_modules/trezor-link/lib/.*
|
||||
.*/_old/.*
|
||||
|
||||
[libs]
|
||||
|
||||
|
||||
|
||||
[options]
|
||||
esproposal.class_static_fields=enable
|
||||
esproposal.class_instance_fields=enable
|
||||
esproposal.export_star_as=enable
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue
|
||||
esproposal.decorators=ignore
|
||||
module.name_mapper='.*\(.less\)' -> 'CSSModule'
|
||||
module.system=haste
|
@ -0,0 +1,13 @@
|
||||
|
||||
|
||||
printf "\n-- DEPLOY START -----------------------\n"
|
||||
|
||||
yarn run build
|
||||
|
||||
printf "\n-- COPYING FILES ----------------------\n"
|
||||
|
||||
cd build
|
||||
rsync -avz --delete -e ssh . admin@dev.sldev.cz:~/experiments/www
|
||||
cd ../
|
||||
|
||||
printf "\n-- COMPLETE ---------------------------\n"
|
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 137 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 7.3 KiB |
After Width: | Height: | Size: 4.8 KiB |
@ -0,0 +1,81 @@
|
||||
{
|
||||
"coins1": [
|
||||
{
|
||||
"name": "Ethereum Ropsten",
|
||||
"symbol": "eth",
|
||||
"network": "ropsten-eth",
|
||||
"shortcut": "eth",
|
||||
"bip44": "m/44'/60'/0'/0",
|
||||
"defaultGasPrice": 64,
|
||||
"defaultGasLimit": 21000,
|
||||
"defaultGasLimitTokens": 200000,
|
||||
"backends": [
|
||||
{
|
||||
"name": "TREZOR Wallet - Ethereum",
|
||||
"urls": [
|
||||
"https://ropsten.infura.io/QGyVKozSUEh2YhL4s2G4",
|
||||
"http://10.34.2.5:8545"
|
||||
],
|
||||
"explorer": "https://blockexplorer.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
"coins": [
|
||||
{
|
||||
"name": "Ethereum Ropsten",
|
||||
"symbol": "eth",
|
||||
"network": "ropsten-eth",
|
||||
"shortcut": "eth",
|
||||
"bip44": "m/44'/60'/0'/0",
|
||||
"defaultGasPrice": 64,
|
||||
"defaultGasLimit": 21000,
|
||||
"defaultGasLimitTokens": 200000,
|
||||
"backends": [
|
||||
{
|
||||
"name": "TREZOR Wallet - Ethereum",
|
||||
"urls": [
|
||||
"https://ropsten.infura.io/QGyVKozSUEh2YhL4s2G4",
|
||||
"http://10.34.2.5:8545"
|
||||
],
|
||||
"explorer": "https://blockexplorer.com"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Ethereum Rinkeby",
|
||||
"symbol": "etc",
|
||||
"network": "ropsten-eth",
|
||||
"shortcut": "etc",
|
||||
"bip44": "m/44'/61'/0'/0",
|
||||
"defaultGasPrice": 64,
|
||||
"defaultGasLimit": 21000,
|
||||
"defaultGasLimitTokens": 200000,
|
||||
"backends": [
|
||||
{
|
||||
"name": "TREZOR Wallet - Ethereum",
|
||||
"urls": [
|
||||
"https://rinkeby.infura.io/QGyVKozSUEh2YhL4s2G4",
|
||||
"http://10.34.2.5:8545"
|
||||
],
|
||||
"explorer": "https://blockexplorer.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
"fiatValueTickers": [
|
||||
|
||||
],
|
||||
|
||||
"bridge": {
|
||||
"url": "https://localback.net:21324",
|
||||
"configUrl": "data/config_signed.bin",
|
||||
"latestUrl": "data/bridge/latest.txt"
|
||||
},
|
||||
"extensionId": "jcjjhjgimijdkoamemaghajlhegmoclj",
|
||||
"storageVersion": "1.1.0",
|
||||
"metadataVersion": "1.0.0"
|
||||
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
[
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"name":"",
|
||||
"type":"string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{"name":"_spender", "type":"address"},
|
||||
{"name":"_value","type":"uint256"}
|
||||
],
|
||||
"name":"approve",
|
||||
"outputs":[
|
||||
{"name":"success","type":"bool"}
|
||||
],
|
||||
"payable":false,
|
||||
"stateMutability":"nonpayable",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":true,
|
||||
"inputs":[],
|
||||
"name":"totalSupply",
|
||||
"outputs":[
|
||||
{"name":"","type":"uint256"}
|
||||
],
|
||||
"payable":false,
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":false,
|
||||
"inputs":[
|
||||
{"name":"_from","type":"address"},
|
||||
{"name":"_to","type":"address"},
|
||||
{"name":"_value","type":"uint256"}
|
||||
],
|
||||
"name":"transferFrom",
|
||||
"outputs":[
|
||||
{"name":"success","type":"bool"}
|
||||
],
|
||||
"payable":false,
|
||||
"stateMutability":"nonpayable",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":true,
|
||||
"inputs":[],
|
||||
"name":"decimals",
|
||||
"outputs":[
|
||||
{"name":"","type":"uint8"}
|
||||
],
|
||||
"payable":false,
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":true,
|
||||
"inputs":[],
|
||||
"name":"version",
|
||||
"outputs":[
|
||||
{"name":"","type":"string"}
|
||||
],
|
||||
"payable":false,
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},
|
||||
{
|
||||
"constant":false,
|
||||
"inputs":[
|
||||
{"name":"_to","type":"address"},
|
||||
{"name":"_value","type":"uint256"}
|
||||
],
|
||||
"name":"transfer",
|
||||
"outputs":[
|
||||
{"name":"success","type":"bool"}
|
||||
],
|
||||
"payable":false,
|
||||
"stateMutability":"nonpayable",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":false,
|
||||
"inputs":[
|
||||
{"name":"_spender","type":"address"},
|
||||
{"name":"_value","type":"uint256"},
|
||||
{"name":"_extraData","type":"bytes"}
|
||||
],
|
||||
"name":"approveAndCall",
|
||||
"outputs":[
|
||||
{"name":"success","type":"bool"}
|
||||
],
|
||||
"payable":false,
|
||||
"stateMutability":"nonpayable",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"constant":true,
|
||||
"inputs":[
|
||||
{"name":"_owner","type":"address"},
|
||||
{"name":"_spender","type":"address"}
|
||||
],
|
||||
"name":"allowance",
|
||||
"outputs":[
|
||||
{"name":"remaining","type":"uint256"}
|
||||
],
|
||||
"payable":false,
|
||||
"stateMutability":"view",
|
||||
"type":"function"
|
||||
},
|
||||
{
|
||||
"inputs":[],
|
||||
"payable":false,
|
||||
"stateMutability":"nonpayable",
|
||||
"type":"constructor"
|
||||
},
|
||||
{
|
||||
"payable":false,
|
||||
"stateMutability":"nonpayable",
|
||||
"type":"fallback"
|
||||
},{
|
||||
"anonymous":false,
|
||||
"inputs":[
|
||||
{"indexed":true,"name":"_from","type":"address"},
|
||||
{"indexed":true,"name":"_to","type":"address"},
|
||||
{"indexed":false,"name":"_value","type":"uint256"}
|
||||
],
|
||||
"name":"Transfer",
|
||||
"type":"event"
|
||||
},{
|
||||
"anonymous":false,
|
||||
"inputs":[
|
||||
{"indexed":true,"name":"_owner","type":"address"},
|
||||
{"indexed":true,"name":"_spender","type":"address"},
|
||||
{"indexed":false,"name":"_value","type":"uint256"}
|
||||
],
|
||||
"name":"Approval",
|
||||
"type":"event"
|
||||
}
|
||||
]
|
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 137 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 658 KiB |
After Width: | Height: | Size: 7.3 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 7.3 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 4.8 KiB |
@ -0,0 +1,106 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import * as ACCOUNT from './constants/account';
|
||||
|
||||
import { initialState } from '../reducers/AccountDetailReducer';
|
||||
import { findSelectedDevice } from '../reducers/TrezorConnectReducer';
|
||||
|
||||
import type { State } from '../reducers/AccountDetailReducer';
|
||||
import type { Discovery } from '../reducers/DiscoveryReducer';
|
||||
|
||||
export const init = (): any => {
|
||||
return (dispatch, getState): void => {
|
||||
|
||||
const { location } = getState().router;
|
||||
const urlParams = location.params;
|
||||
|
||||
const selected = findSelectedDevice( getState().connect );
|
||||
if (!selected) return;
|
||||
|
||||
|
||||
const state: State = {
|
||||
index: parseInt(urlParams.address),
|
||||
checksum: selected.checksum,
|
||||
coin: urlParams.coin,
|
||||
location: location.pathname
|
||||
};
|
||||
|
||||
dispatch({
|
||||
type: ACCOUNT.INIT,
|
||||
state: state
|
||||
});
|
||||
|
||||
|
||||
// let discoveryProcess: ?Discovery = getState().discovery.find(d => d.checksum === selected.checksum && d.coin === currentAccount.coin);
|
||||
// const discovering: boolean = (!discoveryProcess || !discoveryProcess.completed);
|
||||
|
||||
// const state: State = {
|
||||
// ...initialState,
|
||||
// loaded: true,
|
||||
// checksum: currentAccount.checksum,
|
||||
// address: currentAccount.address,
|
||||
// coin: urlParams.coin,
|
||||
// balance: currentAccount.balance,
|
||||
|
||||
// discovering
|
||||
// };
|
||||
|
||||
// dispatch({
|
||||
// type: ACCOUNT.INIT,
|
||||
// state
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
export const update = (newProps: any): any => {
|
||||
return (dispatch, getState): void => {
|
||||
|
||||
const {
|
||||
accountDetail,
|
||||
connect,
|
||||
discovery,
|
||||
accounts,
|
||||
router
|
||||
} = getState();
|
||||
|
||||
const isLocationChanged: boolean = newProps.location.pathname !== accountDetail.location;
|
||||
|
||||
if (isLocationChanged) {
|
||||
dispatch({
|
||||
type: ACCOUNT.INIT,
|
||||
state: {
|
||||
...accountDetail,
|
||||
location: newProps.location.pathname,
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// update comes from device
|
||||
// const device = connect.devices.find(d => d.checksum === accountDetail.checksum);
|
||||
// if (accountDetail.detail !== device) {
|
||||
// console.warn("DEV UPDATE!!!!")
|
||||
// }
|
||||
|
||||
// const discoveryProcess = discovery.find(d => d.checksum === device.checksum && d.coin === accountDetail.coin);
|
||||
|
||||
// const account = accounts.find(a => a.checksum === accountDetail.checksum && a.index === accountDetail.addressIndex && a.coin === accountDetail.coin);
|
||||
// if (account && !accountDetail.address) {
|
||||
// // update current address
|
||||
// console.warn("ACC UPDATE!!!!")
|
||||
// }
|
||||
|
||||
|
||||
// isDeviceChanged
|
||||
// isDiscoveryChanged
|
||||
}
|
||||
}
|
||||
|
||||
export const dispose = (device: any): any => {
|
||||
return (dispatch, getState): void => {
|
||||
dispatch({
|
||||
type: ACCOUNT.DISPOSE,
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
export const ON_RESIZE: string = 'ON_RESIZE';
|
||||
export const ON_BEFORE_UNLOAD: string = 'app__on_before_unload';
|
||||
export const TOGGLE_DEVICE_DROPDOWN: string = 'TOGGLE_DEVICE_DROPDOWN';
|
||||
export const RESIZE_CONTAINER: string = 'RESIZE_CONTAINER';
|
||||
|
||||
export const onResize = (): any => {
|
||||
return {
|
||||
type: ON_RESIZE
|
||||
}
|
||||
}
|
||||
|
||||
export const onBeforeUnload = (): any => {
|
||||
return {
|
||||
type: ON_BEFORE_UNLOAD
|
||||
}
|
||||
}
|
||||
|
||||
export const resizeAppContainer = (opened: boolean): any => {
|
||||
return {
|
||||
type: RESIZE_CONTAINER,
|
||||
opened
|
||||
}
|
||||
}
|
||||
|
||||
export const toggleDeviceDropdown = (opened: boolean): any => {
|
||||
return {
|
||||
type: TOGGLE_DEVICE_DROPDOWN,
|
||||
opened
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
export const ON_RESIZE: string = 'ON_RESIZE';
|
||||
|
||||
export const onResize = (): void => {
|
||||
return {
|
||||
type: ON_RESIZE
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import * as CONNECT from './constants/TrezorConnect';
|
||||
import * as ADDRESS from './constants/Address';
|
||||
import * as TOKEN from './constants/Token';
|
||||
import * as DISCOVERY from './constants/Discovery';
|
||||
import * as STORAGE from './constants/LocalStorage';
|
||||
import { httpRequest } from '../utils/networkUtils';
|
||||
|
||||
export function loadData(): any {
|
||||
return async (dispatch, getState) => {
|
||||
|
||||
// check if local storage is available
|
||||
// let available: boolean = true;
|
||||
// if (typeof window.localStorage === 'undefined') {
|
||||
// available = false;
|
||||
// } else {
|
||||
// try {
|
||||
// window.localStorage.setItem('ethereum_wallet', true);
|
||||
// } catch (error) {
|
||||
// available = false;
|
||||
// }
|
||||
// }
|
||||
|
||||
dispatch( loadTokensFromJSON() );
|
||||
}
|
||||
}
|
||||
|
||||
export function loadTokensFromJSON(): any {
|
||||
return async (dispatch, getState) => {
|
||||
try {
|
||||
const appConfig = await httpRequest('data/appConfig.json', 'json');
|
||||
const ethTokens = await httpRequest('data/ethTokens.json', 'json');
|
||||
const ethERC20 = await httpRequest('data/ethERC20.json', 'json');
|
||||
|
||||
const devices: ?string = get('devices');
|
||||
console.log("GET23", JSON.parse(devices))
|
||||
if (devices) {
|
||||
dispatch({
|
||||
type: CONNECT.DEVICE_FROM_STORAGE,
|
||||
payload: JSON.parse(devices)
|
||||
})
|
||||
}
|
||||
|
||||
const accounts: ?string = get('accounts');
|
||||
if (accounts) {
|
||||
dispatch({
|
||||
type: ADDRESS.FROM_STORAGE,
|
||||
payload: JSON.parse(accounts)
|
||||
})
|
||||
}
|
||||
|
||||
const tokens: ?string = get('tokens');
|
||||
if (tokens) {
|
||||
dispatch({
|
||||
type: TOKEN.FROM_STORAGE,
|
||||
payload: JSON.parse(tokens)
|
||||
})
|
||||
}
|
||||
|
||||
const discovery: ?string = get('discovery');
|
||||
if (discovery) {
|
||||
dispatch({
|
||||
type: DISCOVERY.FROM_STORAGE,
|
||||
payload: JSON.parse(discovery)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
dispatch({
|
||||
type: STORAGE.READY,
|
||||
appConfig,
|
||||
ethTokens,
|
||||
ethERC20
|
||||
})
|
||||
|
||||
} catch(error) {
|
||||
dispatch({
|
||||
type: STORAGE.ERROR,
|
||||
error
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const save = (key: string, value: string): any => {
|
||||
return (dispatch, getState) => {
|
||||
if (typeof window.localStorage !== 'undefined') {
|
||||
//console.log("SAVEE!!!!", key, value)
|
||||
try {
|
||||
window.localStorage.setItem(key, value);
|
||||
} catch (error) {
|
||||
// available = false;
|
||||
console.error("ERROR: " + error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const get = (key: string): ?string => {
|
||||
if (typeof window.localStorage !== 'undefined') {
|
||||
try {
|
||||
console.log("GETTT", JSON.parse(window.localStorage.getItem(key)))
|
||||
return window.localStorage.getItem(key);
|
||||
} catch (error) {
|
||||
// available = false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import TrezorConnect from 'trezor-connect';
|
||||
import * as RECEIVE from './constants/receive';
|
||||
import * as NOTIFICATION from './constants/notification';
|
||||
|
||||
import { initialState } from '../reducers/ReceiveReducer';
|
||||
import type { State } from '../reducers/ReceiveReducer';
|
||||
import { findSelectedDevice } from '../reducers/TrezorConnectReducer';
|
||||
|
||||
|
||||
export const init = (): any => {
|
||||
return (dispatch, getState): void => {
|
||||
const { location } = getState().router;
|
||||
const urlParams = location.params;
|
||||
|
||||
const selected = findSelectedDevice( getState().connect );
|
||||
if (!selected) return;
|
||||
|
||||
const state: State = {
|
||||
...initialState,
|
||||
checksum: selected.checksum,
|
||||
accountIndex: parseInt(urlParams.address),
|
||||
coin: urlParams.coin,
|
||||
location: location.pathname,
|
||||
};
|
||||
|
||||
dispatch({
|
||||
type: RECEIVE.INIT,
|
||||
state: state
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const update = (newProps: any): any => {
|
||||
return (dispatch, getState): void => {
|
||||
const {
|
||||
receive,
|
||||
router
|
||||
} = getState();
|
||||
|
||||
const isLocationChanged: boolean = router.location.pathname !== receive.location;
|
||||
if (isLocationChanged) {
|
||||
dispatch( init() );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const dispose = (address: string): any => {
|
||||
return {
|
||||
type: RECEIVE.DISPOSE
|
||||
}
|
||||
}
|
||||
|
||||
export const showUnverifiedAddress = () => {
|
||||
return {
|
||||
type: RECEIVE.SHOW_UNVERIFIED_ADDRESS
|
||||
}
|
||||
}
|
||||
|
||||
export const showAddress = (address_n: string): any => {
|
||||
return async (dispatch, getState) => {
|
||||
|
||||
const selected = findSelectedDevice(getState().connect);
|
||||
|
||||
if (selected && !selected.connected) {
|
||||
dispatch({
|
||||
type: RECEIVE.REQUEST_UNVERIFIED,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await TrezorConnect.ethereumGetAddress({
|
||||
device: {
|
||||
path: selected.path,
|
||||
instance: selected.instance,
|
||||
state: selected.checksum
|
||||
},
|
||||
address_n
|
||||
});
|
||||
|
||||
if (response && response.success) {
|
||||
dispatch({
|
||||
type: RECEIVE.SHOW_ADDRESS
|
||||
})
|
||||
} else {
|
||||
// TODO: handle invalid pin?
|
||||
dispatch({
|
||||
type: NOTIFICATION.ADD,
|
||||
payload: {
|
||||
type: 'error',
|
||||
title: 'Veryfying address error',
|
||||
message: response.data.error,
|
||||
cancelable: true,
|
||||
actions: [
|
||||
{
|
||||
label: 'Try again',
|
||||
callback: () => {
|
||||
dispatch(showAddress(address_n))
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,282 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import EthereumjsUtil from 'ethereumjs-util';
|
||||
import * as ACTIONS from './index';
|
||||
import * as SUMMARY from './constants/summary';
|
||||
import * as TOKEN from './constants/Token';
|
||||
import * as ADDRESS from './constants/Address';
|
||||
import { resolveAfter } from '../utils/promiseUtils';
|
||||
import { getTokenInfoAsync, getTokenBalanceAsync } from './Web3Actions';
|
||||
|
||||
import { initialState } from '../reducers/SummaryReducer';
|
||||
import type { State } from '../reducers/SummaryReducer';
|
||||
import { findSelectedDevice } from '../reducers/TrezorConnectReducer';
|
||||
|
||||
|
||||
export const init = (): any => {
|
||||
return (dispatch, getState): void => {
|
||||
const { location } = getState().router;
|
||||
const urlParams = location.params;
|
||||
|
||||
const selected = findSelectedDevice( getState().connect );
|
||||
if (!selected) return;
|
||||
|
||||
const state: State = {
|
||||
...initialState,
|
||||
checksum: selected.checksum,
|
||||
accountIndex: parseInt(urlParams.address),
|
||||
coin: urlParams.coin,
|
||||
location: location.pathname,
|
||||
};
|
||||
|
||||
dispatch({
|
||||
type: SUMMARY.INIT,
|
||||
state: state
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const update = (newProps: any): any => {
|
||||
return (dispatch, getState): void => {
|
||||
const {
|
||||
summary,
|
||||
router
|
||||
} = getState();
|
||||
|
||||
const isLocationChanged: boolean = router.location.pathname !== summary.location;
|
||||
if (isLocationChanged) {
|
||||
dispatch( init() );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const dispose = (address: string): any => {
|
||||
return {
|
||||
type: SUMMARY.DISPOSE
|
||||
}
|
||||
}
|
||||
|
||||
export const onDetailsToggle = (): any => {
|
||||
return {
|
||||
type: SUMMARY.DETAILS_TOGGLE
|
||||
}
|
||||
}
|
||||
|
||||
// export const init = (address: string): any => {
|
||||
// return (dispatch, getState): void => {
|
||||
// const { location } = getState().router;
|
||||
// const urlParams = location.params;
|
||||
|
||||
// const selected = findSelectedDevice(getState().connect);
|
||||
// const accounts = getState().accounts;
|
||||
// // const currentAccount = accounts.find(a => a.index === parseInt(urlParams.address) && a.coin === urlParams.coin && a.deviceId === urlParams.device && a.loaded);
|
||||
// const currentAccount = accounts.find(a => a.index === parseInt(urlParams.address) && a.coin === urlParams.coin && a.checksum === selected.checksum);
|
||||
// if (!currentAccount) {
|
||||
// console.log("STATER", getState())
|
||||
// // account not found
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const web3instance = getState().web3.find(w3 => w3.coin === urlParams.coin);
|
||||
// if (!web3instance) {
|
||||
// // no backend for this coin
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const state: State = {
|
||||
// ...initialState,
|
||||
// loaded: true,
|
||||
// address: currentAccount.address,
|
||||
// coin: urlParams.coin,
|
||||
// balance: currentAccount.balance,
|
||||
// };
|
||||
|
||||
|
||||
// dispatch({
|
||||
// type: SUMMARY.INIT,
|
||||
// state
|
||||
// });
|
||||
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
export const loadTokens = (input: string): any => {
|
||||
return async (dispatch, getState): Promise<any> => {
|
||||
|
||||
if (input.length < 1) return null;
|
||||
|
||||
const { ethTokens } = getState().localStorage;
|
||||
|
||||
const value = input.toLowerCase();
|
||||
const result = ethTokens.filter(t =>
|
||||
t.symbol.toLowerCase().indexOf(value) >= 0 ||
|
||||
t.address.toLowerCase().indexOf(value) >= 0 ||
|
||||
t.name.toLowerCase().indexOf(value) >= 0
|
||||
);
|
||||
//const result = ethTokens.filter(t => t.symbol.toLowerCase().indexOf(lower) >= 0);
|
||||
|
||||
console.log("RESULT!", result.length, result)
|
||||
|
||||
if (result.length > 0) {
|
||||
return { options: result };
|
||||
} else {
|
||||
const web3instance = getState().web3.find(w3 => w3.coin === 'eth');
|
||||
|
||||
const info = await getTokenInfoAsync(web3instance.erc20, input);
|
||||
info.address = input;
|
||||
|
||||
console.log("FETCH", info)
|
||||
|
||||
if (info) {
|
||||
return {
|
||||
options: [ info ]
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//await resolveAfter(300000);
|
||||
//await resolveAfter(3000);
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export const selectToken = (token: any, account: any): any => {
|
||||
return async (dispatch, getState): Promise<any> => {
|
||||
|
||||
console.warn("ADD", token, account)
|
||||
|
||||
const web3instance = getState().web3.find(w3 => w3.coin === account.coin);
|
||||
|
||||
dispatch({
|
||||
type: TOKEN.ADD,
|
||||
payload: {
|
||||
...token,
|
||||
ethAddress: account.address,
|
||||
checksum: account.checksum
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: load token balance
|
||||
const tokenBalance = await getTokenBalanceAsync(web3instance.erc20, token.address, account.address);
|
||||
dispatch({
|
||||
type: TOKEN.SET_BALANCE,
|
||||
payload: {
|
||||
ethAddress: account.address,
|
||||
address: token.address,
|
||||
balance: tokenBalance.toString()
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const onTokenSearch = (search: string): any => {
|
||||
return {
|
||||
type: ACTIONS.TOKENS_SEARCH,
|
||||
search
|
||||
}
|
||||
}
|
||||
|
||||
export const onCustomTokenToggle = (): any => {
|
||||
return {
|
||||
type: ACTIONS.TOKENS_CUSTOM_TOGGLE
|
||||
}
|
||||
}
|
||||
|
||||
export const onCustomTokenAddressChange = (value: string): any => {
|
||||
// todo:
|
||||
// -validate addres
|
||||
// - if adress is ok, try to fetch token info
|
||||
// - check if addres does not exist in predefined coins
|
||||
// return {
|
||||
// type: ACTIONS.TOKENS_CUSTOM_ADDRESS_CHANGE,
|
||||
// value
|
||||
// }
|
||||
|
||||
return async (dispatch, getState) => {
|
||||
|
||||
const valid: boolean = EthereumjsUtil.isValidAddress(value);
|
||||
if (valid) {
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.TOKENS_CUSTOM_ADDRESS_CHANGE,
|
||||
value,
|
||||
valid,
|
||||
fetching: true
|
||||
});
|
||||
|
||||
const { web3, abi } = getState().web3;
|
||||
const contract = web3.eth.contract(abi).at(value);
|
||||
|
||||
contract.name.call((error, name) => {
|
||||
if (error) {
|
||||
// TODO: skip
|
||||
}
|
||||
contract.symbol.call((error, symbol) => {
|
||||
if (error) {
|
||||
// TODO: skip
|
||||
}
|
||||
|
||||
contract.decimals.call((error, decimals) => {
|
||||
console.log("fetched!", name, symbol, decimals)
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
|
||||
} else {
|
||||
dispatch({
|
||||
type: ACTIONS.TOKENS_CUSTOM_ADDRESS_CHANGE,
|
||||
value,
|
||||
valid
|
||||
});
|
||||
}
|
||||
|
||||
console.log("VALID!!!", valid);
|
||||
}
|
||||
}
|
||||
|
||||
export const onCustomTokenNameChange = (value: string): any => {
|
||||
return {
|
||||
type: ACTIONS.TOKENS_CUSTOM_NAME_CHANGE,
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
export const onCustomTokenShortcutChange = (value: string): any => {
|
||||
return {
|
||||
type: ACTIONS.TOKENS_CUSTOM_SHORTCUT_CHANGE,
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
export const onCustomTokenDecimalChange = (value: string): any => {
|
||||
return {
|
||||
type: ACTIONS.TOKENS_CUSTOM_DECIMAL_CHANGE,
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
export const onCustomTokenAdd = (): any => {
|
||||
return {
|
||||
type: ACTIONS.TOKENS_CUSTOM_ADD
|
||||
}
|
||||
}
|
@ -1,260 +0,0 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import TrezorConnect, { UI } from 'trezor-connect';
|
||||
import * as ACTIONS from './index';
|
||||
|
||||
//import wallet from 'ethereumjs-wallet';
|
||||
//import hdkey from 'ethereumjs-wallet/hdkey';
|
||||
import HDKey from 'hdkey';
|
||||
import ethUtil from 'ethereumjs-util';
|
||||
import EthereumjsTx from 'ethereumjs-tx';
|
||||
import * as ethereumUtils from '../utils/ethUtils';
|
||||
import { hexToString, stringToHex } from '../utils/formatUtils';
|
||||
import * as Web3Actions from './Web3Actions';
|
||||
|
||||
export function onSelectDevice2(path: string): any {
|
||||
return {
|
||||
type: ACTIONS.ON_SELECT_DEVICE,
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
export function discover(txData): any {
|
||||
return async function (dispatch) {
|
||||
let response = await TrezorConnect.getPublicKey({ path: "m/44'/60'/0'/0", confirmation: false });
|
||||
dispatch({
|
||||
type: 'create_account',
|
||||
xpub: response.data.xpub,
|
||||
publicKey: response.data.publicKey,
|
||||
chainCode: response.data.chainCode,
|
||||
path: response.data.path
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function signTx(txData): any {
|
||||
return async function (dispatch) {
|
||||
|
||||
console.log("RESP2", txData)
|
||||
|
||||
//txData.nonce = "0x01";
|
||||
//txData.gasPrice = "0x02540be400";
|
||||
// txData.gasLimit = "0x5208";
|
||||
// txData.value = "0x5af3107a4000";
|
||||
|
||||
let response = await TrezorConnect.ethereumSignTransaction({
|
||||
//path: "m/44'/60'/0'/0/0",
|
||||
address_n: txData.address_n,
|
||||
nonce: ethereumUtils.strip(txData.nonce),
|
||||
gas_price: ethereumUtils.strip(txData.gasPrice),
|
||||
gas_limit: ethereumUtils.strip(txData.gasLimit),
|
||||
to: ethereumUtils.strip(txData.to),
|
||||
value: ethereumUtils.strip(txData.value),
|
||||
data: txData.data,
|
||||
chain_id: txData.chainId
|
||||
});
|
||||
|
||||
txData.r = '0x' + response.data.r;
|
||||
txData.s = '0x' + response.data.s;
|
||||
txData.v = web3.toHex(response.data.v);
|
||||
|
||||
console.log("RESP2", response, txData)
|
||||
|
||||
const tx = new EthereumjsTx(txData);
|
||||
var signedTx = '0x' + tx.serialize().toString('hex');
|
||||
|
||||
|
||||
const rawTx2 = {
|
||||
"nonce":"0x01",
|
||||
"gasPrice":"0x02540be400",
|
||||
"gasLimit":"0x5208",
|
||||
"to":"0x7314e0f1C0e28474bDb6be3E2c3E0453255188f8",
|
||||
"value":"0x5af3107a4000",
|
||||
"data":"",
|
||||
"chainId":3,
|
||||
"v":"0x2a",
|
||||
"r":"0x210af4e1698f0437125424ac378da7304dea94dde34cbb57b62624069ceae969",
|
||||
"s":"0x74c885c3d32330d63e32dff3b79a302ae5ed9b9abcf6e903fd32cbb91d94b6df"
|
||||
}
|
||||
|
||||
var tx2 = new EthereumjsTx(rawTx2);
|
||||
var signedTx2 = '0x' + tx2.serialize().toString('hex');
|
||||
|
||||
console.log(signedTx)
|
||||
console.log(signedTx2)
|
||||
console.log(signedTx === signedTx2)
|
||||
console.log(txData, rawTx2)
|
||||
|
||||
// web3.eth.sendRawTransaction(signedTx, function(a1, a2){
|
||||
// console.log("SIGNEEED", a1, a2)
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
||||
export function onSelectDevice(): any {
|
||||
return async function (dispatch, getState) {
|
||||
dispatch(Web3Actions.composeTransaction());
|
||||
}
|
||||
}
|
||||
export function onSelectDeviceWeb3(): any {
|
||||
return async function (dispatch, getState) {
|
||||
|
||||
const { web3 } = getState().web3;
|
||||
|
||||
console.log("WEB3333", web3)
|
||||
|
||||
let resp = await TrezorConnect.getPublicKey({ path: "m/44'/60'/0'/0", confirmation: false });
|
||||
|
||||
let hdk = new HDKey();
|
||||
hdk.publicKey = new Buffer(resp.data.publicKey, 'hex');
|
||||
hdk.chainCode = new Buffer(resp.data.chainCode, 'hex');
|
||||
|
||||
var derivedKey = hdk.derive("m/0");
|
||||
|
||||
var address = ethUtil.publicToAddress(derivedKey.publicKey, true);
|
||||
|
||||
// balance 0.100100000000000000 eth
|
||||
var txData = {
|
||||
address_n: [
|
||||
(44 | 0x80000000) >>> 0,
|
||||
(60 | 0x80000000) >>> 0,
|
||||
(0 | 0x80000000) >>> 0,
|
||||
0, 0
|
||||
],
|
||||
to: '0x7314e0f1C0e28474bDb6be3E2c3E0453255188f8',
|
||||
value: web3.toHex(web3.toWei('0.0001', 'ether')),
|
||||
data: '',
|
||||
chainId: 3
|
||||
}
|
||||
|
||||
web3.eth.getTransactionCount('0x' + address.toString('hex'), (error, result) => {
|
||||
txData.nonce = web3.toHex(result);
|
||||
const gasOptions = {
|
||||
to: txData.to,
|
||||
data: txData.data
|
||||
}
|
||||
web3.eth.estimateGas(gasOptions, (error, result) => {
|
||||
txData.gasLimit = web3.toHex(result);
|
||||
|
||||
web3.eth.getGasPrice(function(error, result){
|
||||
if (error) throw error;
|
||||
|
||||
txData.gasPrice = web3.toHex(result);
|
||||
console.warn("gesgas", error, result.toString(10), txData)
|
||||
|
||||
dispatch( signTx(txData) );
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// web3.eth.getTransactionCount('0x' + address.toString('hex'), (error, result) => {
|
||||
|
||||
// if (error) throw error;
|
||||
|
||||
// console.log("getTransactionCount", result)
|
||||
|
||||
// });
|
||||
|
||||
// let gas = {
|
||||
// to: "0x7314e0f1C0e28474bDb6be3E2c3E0453255188f8",
|
||||
// data: ""
|
||||
// };
|
||||
|
||||
// web3.eth.estimateGas(gas, function(error, result){
|
||||
// console.warn("estimategas", error, result, result.toString(10))
|
||||
|
||||
// web3.eth.getGasPrice(function(error, result){
|
||||
// console.warn("gesgas", error, result.toString(10) result.)
|
||||
// })
|
||||
|
||||
// });
|
||||
|
||||
|
||||
//console.log("SIGNEEED", signedTx);
|
||||
|
||||
|
||||
//console.log("HDK", derivedKey, address.toString('hex'), rawTx, txData, hexToString(txData.value))
|
||||
//console.log("HDK", derivedKey, hexToString(txData.value), txData )
|
||||
|
||||
// const hd = hdkey.fromExtendedKey(resp.data.xpub);
|
||||
// console.log("HDKI!", hd)
|
||||
|
||||
|
||||
// var balance = web3.eth.getBalance('0x' + address.toString('hex'), function(error, result){
|
||||
// if(!error)
|
||||
// console.log("res", result, result.toString(10) )
|
||||
// else
|
||||
// console.error("erro", error);
|
||||
// });
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// web3.eth.sendRawTransaction(signedTx, function(a1, a2){
|
||||
// console.log("SIGNEEED", a1, a2)
|
||||
// })
|
||||
//console.log(web3.eth)
|
||||
//web3.eth.sendRawTransaction(signedTx).on('receipt', console.log);
|
||||
|
||||
// web3.eth.getTransactionCount('0x' + address.toString('hex'), function(error, result) {
|
||||
// //web3.eth.getTransactionCount("0x11f4d0A3c12e86B4b5F39B213F7E19D048276DAe", function(error, result) {
|
||||
// console.log("getTransactionCount", error, result)
|
||||
// })
|
||||
|
||||
// web3.eth.getAccounts(console.log)
|
||||
|
||||
// const nonce = '03'; // note - it is hex, not number!!!
|
||||
// const gas_price = '098bca5a00';
|
||||
// const gas_limit = 'a43f';
|
||||
// const to = 'e0b7927c4af23765cb51314a0e0521a9645f0e2a';
|
||||
// // var value = '01'; // in hexadecimal, in wei - this is 1 wei
|
||||
// const value = '010000000000000000'; // in hexadecimal, in wei - this is about 18 ETC
|
||||
// const data = 'a9059cbb000000000000000000000000dc7359317ef4cc723a3980213a013c0433a338910000000000000000000000000000000000000000000000000000000001312d00'; // some contract data
|
||||
// // var data = null // for no data
|
||||
// const chain_id = 1; // 1 for ETH, 61 for ETC
|
||||
|
||||
// let resp2 = await TrezorConnect.ethereumSignTransaction({
|
||||
// address_n,
|
||||
// nonce,
|
||||
// gas_price,
|
||||
// gas_limit,
|
||||
// to,
|
||||
// value,
|
||||
// data,
|
||||
// chain_id
|
||||
// });
|
||||
|
||||
|
||||
|
||||
|
||||
// old fallback
|
||||
// this.signEthereumTx = function (
|
||||
// address_n,
|
||||
// nonce,
|
||||
// gas_price,
|
||||
// gas_limit,
|
||||
// to,
|
||||
// value,
|
||||
// data,
|
||||
// chain_id,
|
||||
// callback,
|
||||
// requiredFirmware
|
||||
// )
|
||||
|
||||
// var coinbase = web3.eth.coinbase;
|
||||
// var balance = web3.eth.getBalance(coinbase);
|
||||
// console.log(balance.toString(10));
|
||||
|
||||
// dispatch({
|
||||
// type: 'DDD'
|
||||
// });
|
||||
}
|
||||
}
|
@ -1,95 +1,710 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import TrezorConnect, { UI } from 'trezor-connect';
|
||||
import TrezorConnect, { UI, DEVICE, DEVICE_EVENT, UI_EVENT } from 'trezor-connect';
|
||||
import * as ACTIONS from './index';
|
||||
import * as ADDRESS from './constants/Address';
|
||||
import * as TOKEN from './constants/Token';
|
||||
import * as CONNECT from './constants/TrezorConnect';
|
||||
import * as DISCOVERY from './constants/Discovery';
|
||||
import * as NOTIFICATION from './constants/notification';
|
||||
|
||||
//import wallet from 'ethereumjs-wallet';
|
||||
//import hdkey from 'ethereumjs-wallet/hdkey';
|
||||
import HDKey from 'hdkey';
|
||||
import EthereumjsUtil from 'ethereumjs-util';
|
||||
import EthereumjsTx from 'ethereumjs-tx';
|
||||
|
||||
import { Address } from '../reducers/AddressesReducer';
|
||||
import { getBalance } from '../services/Web3Service';
|
||||
//import { getBalance } from '../services/Web3Service';
|
||||
import { getBalance } from './Web3Actions';
|
||||
import { getTransactionHistory } from '../services/EtherscanService';
|
||||
|
||||
import { push } from 'react-router-redux';
|
||||
|
||||
export function onSelectDevice2(path: string): any {
|
||||
return {
|
||||
type: ACTIONS.ON_SELECT_DEVICE,
|
||||
path
|
||||
import { init as initWeb3, getNonce, getBalanceAsync, getTokenBalanceAsync } from './Web3Actions';
|
||||
|
||||
import type { Discovery } from '../reducers/DiscoveryReducer';
|
||||
import { resolveAfter } from '../utils/promiseUtils';
|
||||
import { getAccounts } from '../utils/reducerUtils';
|
||||
import { findSelectedDevice, isSavedDevice } from '../reducers/TrezorConnectReducer';
|
||||
|
||||
|
||||
|
||||
export const init = (): any => {
|
||||
return async (dispatch, getState): Promise<void> => {
|
||||
// set listeners
|
||||
TrezorConnect.on(DEVICE_EVENT, (event: DeviceMessage): void => {
|
||||
dispatch({
|
||||
type: event.type,
|
||||
device: event.data
|
||||
});
|
||||
});
|
||||
|
||||
const version: Object = TrezorConnect.getVersion();
|
||||
if (version.type === 'library') {
|
||||
// handle UI events only if TrezorConnect isn't using popup
|
||||
TrezorConnect.on(UI_EVENT, (type: string, data: any): void => {
|
||||
// post event to reducers
|
||||
dispatch({
|
||||
type,
|
||||
data
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await TrezorConnect.init({
|
||||
transport_reconnect: true,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
dispatch( initWeb3() );
|
||||
}, 2000)
|
||||
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: CONNECT.INITIALIZATION_ERROR,
|
||||
error
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function remove(devicePath): any {
|
||||
// called after backend was initialized
|
||||
// set listeners for connect/disconnect
|
||||
export const postInit = (): any => {
|
||||
return (dispatch, getState): void => {
|
||||
const handleDeviceConnect = (device) => {
|
||||
dispatch( initConnectedDevice(device) );
|
||||
}
|
||||
|
||||
// const handleDeviceDisconnect = (device) => {
|
||||
// // remove addresses and discovery from state
|
||||
// // dispatch( remove(device) );
|
||||
// }
|
||||
|
||||
TrezorConnect.on(DEVICE.CONNECT, handleDeviceConnect);
|
||||
TrezorConnect.on(DEVICE.CONNECT_UNACQUIRED, handleDeviceConnect);
|
||||
|
||||
// TrezorConnect.on(DEVICE.DISCONNECT, handleDeviceDisconnect);
|
||||
// TrezorConnect.on(DEVICE.CONNECT_UNACQUIRED, handleDeviceDisconnect);
|
||||
|
||||
// possible race condition:
|
||||
// devices were connected before Web3 initialized. force DEVICE.CONNECT event on them
|
||||
const { devices } = getState().connect;
|
||||
|
||||
if (devices.length > 0) {
|
||||
const unacquired = devices.find(d => d.unacquired);
|
||||
if (unacquired) {
|
||||
handleDeviceConnect(unacquired);
|
||||
} else {
|
||||
const latest = devices.sort((a, b) => {
|
||||
if (!a.ts || !b.ts) {
|
||||
return -1;
|
||||
} else {
|
||||
return a.ts > b.ts ? 1 : -1;
|
||||
}
|
||||
});
|
||||
|
||||
console.log("LATEST", latest)
|
||||
}
|
||||
}
|
||||
for (let d of devices) {
|
||||
handleDeviceConnect(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const initConnectedDevice = (device: any): any => {
|
||||
return (dispatch, getState): void => {
|
||||
|
||||
const selected = findSelectedDevice(getState().connect);
|
||||
if (device.unacquired && selected && selected.path !== device.path && !selected.connected) {
|
||||
dispatch( onSelectDevice(device) );
|
||||
} else if (!selected) {
|
||||
dispatch( onSelectDevice(device) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// selection from Aside dropdown
|
||||
// or after acquiring device
|
||||
export function onSelectDevice(device: any): any {
|
||||
return (dispatch, getState): void => {
|
||||
// || device.isUsedElsewhere
|
||||
if (device.unacquired) {
|
||||
dispatch( push(`/device/${ device.path }/acquire`) );
|
||||
} else if (device.features.bootloader_mode) {
|
||||
dispatch( push(`/device/${ device.path }/bootloader`) );
|
||||
} else if (device.instance) {
|
||||
dispatch( push(`/device/${ device.features.device_id }:${ device.instance }`) );
|
||||
} else {
|
||||
//dispatch( push(`/device/${ device.features.device_id }/coin/etc/address/0`) );
|
||||
dispatch( push(`/device/${ device.features.device_id }`) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: as TrezorConnect method
|
||||
const __getDeviceState = async (path, instance): Promise<any> => {
|
||||
return await TrezorConnect.getPublicKey({
|
||||
device: {
|
||||
path,
|
||||
instance
|
||||
},
|
||||
// selectedDevice: path,
|
||||
instance: instance,
|
||||
path: "m/1'/0'/0'",
|
||||
confirmation: false
|
||||
});
|
||||
}
|
||||
|
||||
export const switchToFirstAvailableDevice = (): any => {
|
||||
return async (dispatch, getState): Promise<void> => {
|
||||
|
||||
const { devices } = getState().connect;
|
||||
if (devices.length > 0) {
|
||||
// TODO: Priority:
|
||||
// 1. Unacquired
|
||||
// 2. First connected
|
||||
// 3. Saved with latest timestamp
|
||||
// 4. First from the list
|
||||
const unacquired = devices.find(d => d.unacquired);
|
||||
if (unacquired) {
|
||||
dispatch( initConnectedDevice(unacquired) );
|
||||
} else {
|
||||
const latest = devices.sort((a, b) => {
|
||||
if (!a.ts || !b.ts) {
|
||||
return -1;
|
||||
} else {
|
||||
return a.ts > b.ts ? 1 : -1;
|
||||
}
|
||||
});
|
||||
dispatch( initConnectedDevice(devices[0]) );
|
||||
}
|
||||
|
||||
} else {
|
||||
dispatch( push('/') );
|
||||
dispatch({
|
||||
type: CONNECT.SELECT_DEVICE,
|
||||
payload: null
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const getSelectedDeviceState = (): any => {
|
||||
return async (dispatch, getState): Promise<void> => {
|
||||
const selected = findSelectedDevice(getState().connect);
|
||||
console.warn("init selected", selected)
|
||||
if (selected
|
||||
&& selected.connected
|
||||
&& !selected.unacquired
|
||||
&& !selected.acquiring
|
||||
&& !selected.checksum) {
|
||||
|
||||
const response = await __getDeviceState(selected.path, selected.instance);
|
||||
|
||||
if (response && response.success) {
|
||||
dispatch({
|
||||
type: CONNECT.AUTH_DEVICE,
|
||||
device: selected,
|
||||
checksum: response.data.xpub
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: NOTIFICATION.ADD,
|
||||
payload: {
|
||||
type: 'error',
|
||||
title: 'Authentification error',
|
||||
message: response.data.error,
|
||||
cancelable: true,
|
||||
actions: [
|
||||
{
|
||||
label: 'Try again',
|
||||
callback: () => {
|
||||
dispatch( getSelectedDeviceState() );
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const deviceDisconnect = (device: any): any => {
|
||||
return async (dispatch, getState): Promise<void> => {
|
||||
|
||||
if (!device || !device.features) return null;
|
||||
|
||||
const selected = findSelectedDevice(getState().connect);
|
||||
if (selected && selected.features.device_id === device.features.device_id) {
|
||||
stopDiscoveryProcess(selected);
|
||||
}
|
||||
|
||||
const affected = getState().connect.devices.filter(d => d.features && d.checksum && !d.remember && d.features.device_id === device.features.device_id);
|
||||
if (affected.length > 0) {
|
||||
dispatch({
|
||||
type: CONNECT.REMEMBER_REQUEST,
|
||||
device,
|
||||
allInstances: affected
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// if (selected && selected.checksum) {
|
||||
// if (device.features && device.features.device_id === selected.features.device_id) {
|
||||
// stopDiscoveryProcess(selected);
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// // stop running discovery process on this device
|
||||
// if (selected && selected.path === device.path){
|
||||
// if (selected.checksum) {
|
||||
// stopDiscoveryProcess(selected);
|
||||
// }
|
||||
// }
|
||||
|
||||
// // check if disconnected device was remembered before.
|
||||
// // request modal if not
|
||||
// const affected = getState().connect.devices.filter(d => d.path === device.path && d.checksum && !device.remember);
|
||||
|
||||
|
||||
// check if reload is needed
|
||||
if (!selected) {
|
||||
dispatch( switchToFirstAvailableDevice() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const coinChanged = (coin: ?string): any => {
|
||||
return (dispatch, getState): void => {
|
||||
const selected = findSelectedDevice(getState().connect);
|
||||
dispatch( stopDiscoveryProcess(selected) );
|
||||
|
||||
if (coin) {
|
||||
dispatch( startDiscoveryProcess(selected, coin) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function acquire(): any {
|
||||
return async (dispatch, getState) => {
|
||||
|
||||
const { addresses } = getState().addresses;
|
||||
const availableAddresses = addresses.filter(a => a.devicePath !== devicePath);
|
||||
const selected = findSelectedDevice(getState().connect);
|
||||
|
||||
const saved = getState().connect.devices.map(d => {
|
||||
if (d.checksum) {
|
||||
return {
|
||||
instance: d.instance,
|
||||
checksum: d.checksum
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
//const response = await __acquire(selected.path, selected.instance);
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.ADDRESS_DELETE,
|
||||
addresses: availableAddresses
|
||||
type: CONNECT.START_ACQUIRING,
|
||||
device: selected
|
||||
});
|
||||
|
||||
const response = await TrezorConnect.getFeatures({
|
||||
device: {
|
||||
path: selected.path,
|
||||
}
|
||||
});
|
||||
|
||||
const selected2 = findSelectedDevice(getState().connect);
|
||||
dispatch({
|
||||
type: CONNECT.STOP_ACQUIRING,
|
||||
device: selected2
|
||||
});
|
||||
|
||||
if (response && response.success) {
|
||||
dispatch({
|
||||
type: DEVICE.ACQUIRED,
|
||||
// checksum: response
|
||||
})
|
||||
} else {
|
||||
// TODO: handle invalid pin?
|
||||
console.log("-errror ack", response)
|
||||
|
||||
dispatch({
|
||||
type: NOTIFICATION.ADD,
|
||||
payload: {
|
||||
type: 'error',
|
||||
title: 'Acquire device error',
|
||||
message: response.data.error,
|
||||
cancelable: true,
|
||||
actions: [
|
||||
{
|
||||
label: 'Try again',
|
||||
callback: () => {
|
||||
dispatch(acquire())
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const forgetDevice = (device: any) => {
|
||||
return (dispatch: any, getState: any): any => {
|
||||
|
||||
// find accounts associated with this device
|
||||
const accounts: Array<any> = getState().accounts.find(a => a.checksum === device.checksum);
|
||||
|
||||
|
||||
// find discovery processes associated with this device
|
||||
const discovery: Array<any> = getState().discovery.find(d => d.checksum === device.checksum);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// called from Aside - device menu (forget single instance)
|
||||
export const forget = (device: any) => {
|
||||
return {
|
||||
type: CONNECT.FORGET_REQUEST,
|
||||
device
|
||||
};
|
||||
}
|
||||
|
||||
export const duplicateDevice = (device: any) => {
|
||||
return async (dispatch: any, getState: any): Promise<void> => {
|
||||
dispatch({
|
||||
type: CONNECT.TRY_TO_DUPLICATE,
|
||||
device
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function discover(devicePath): any {
|
||||
export const onDuplicateDevice = () => {
|
||||
return async (dispatch: any, getState: any): Promise<void> => {
|
||||
const selected = findSelectedDevice(getState().connect);
|
||||
dispatch(onSelectDevice(selected));
|
||||
}
|
||||
}
|
||||
|
||||
export const beginDiscoveryProcess = (device: any, coin: string): any => {
|
||||
return async (dispatch, getState) => {
|
||||
|
||||
const { web3 } = getState().web3;
|
||||
const { config } = getState().localStorage;
|
||||
const coinToDiscover = config.coins.find(c => c.symbol === coin);
|
||||
|
||||
// TODO: validate device checksum
|
||||
// const checksum = await __acquire(device.path, device.instance);
|
||||
// if (checksum && checksum.success) {
|
||||
// if (checksum.data.xpub !== device.checksum) {
|
||||
// console.error("Incorrect checksum!");
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// acquire and hold session
|
||||
// get xpub from TREZOR
|
||||
const response = await TrezorConnect.getPublicKey({
|
||||
device: {
|
||||
path: device.path,
|
||||
instance: device.instance,
|
||||
state: device.checksum
|
||||
},
|
||||
path: coinToDiscover.bip44,
|
||||
confirmation: false,
|
||||
keepSession: true
|
||||
});
|
||||
|
||||
if (!response.success) {
|
||||
// TODO: check message
|
||||
console.warn("DISCO ERROR", response)
|
||||
dispatch({
|
||||
type: NOTIFICATION.ADD,
|
||||
payload: {
|
||||
type: 'error',
|
||||
title: 'Discovery error',
|
||||
message: response.data.error,
|
||||
cancelable: true,
|
||||
actions: [
|
||||
{
|
||||
label: 'Try again',
|
||||
callback: () => {
|
||||
dispatch(startDiscoveryProcess(device, coin))
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await TrezorConnect.getPublicKey({ path: "m/44'/60'/0'/0", confirmation: false });
|
||||
// TODO: check for interruption
|
||||
|
||||
// TODO: handle response error
|
||||
const basePath: Array<number> = response.data.path;
|
||||
const hdKey = new HDKey();
|
||||
hdKey.publicKey = new Buffer(response.data.publicKey, 'hex');
|
||||
hdKey.chainCode = new Buffer(response.data.chainCode, 'hex');
|
||||
|
||||
const loop = async (index: number) => {
|
||||
const derivedKey = hdKey.derive(`m/${index}`);
|
||||
const path = basePath.concat(index);
|
||||
const ethAddress: string = '0x' + EthereumjsUtil.publicToAddress(derivedKey.publicKey, true).toString('hex');
|
||||
const address = new Address(devicePath, index, path, ethAddress);
|
||||
// send data to reducer
|
||||
dispatch({
|
||||
type: DISCOVERY.START,
|
||||
coin: coinToDiscover.shortcut,
|
||||
device,
|
||||
xpub: response.data.publicKey,
|
||||
basePath,
|
||||
hdKey,
|
||||
});
|
||||
|
||||
dispatch( startDiscoveryProcess(device, coin) );
|
||||
}
|
||||
}
|
||||
|
||||
export const discoverAddress = (device: any, discoveryProcess: Discovery): any => {
|
||||
return async (dispatch, getState) => {
|
||||
|
||||
const derivedKey = discoveryProcess.hdKey.derive(`m/${discoveryProcess.accountIndex}`);
|
||||
const path = discoveryProcess.basePath.concat(discoveryProcess.accountIndex);
|
||||
const publicAddress: string = EthereumjsUtil.publicToAddress(derivedKey.publicKey, true).toString('hex');
|
||||
const ethAddress: string = EthereumjsUtil.toChecksumAddress(publicAddress);
|
||||
const coin = discoveryProcess.coin;
|
||||
|
||||
dispatch({
|
||||
type: ADDRESS.CREATE,
|
||||
device,
|
||||
coin,
|
||||
index: discoveryProcess.accountIndex,
|
||||
path,
|
||||
address: ethAddress
|
||||
});
|
||||
|
||||
// TODO: check if address was created before
|
||||
|
||||
// verify address with TREZOR
|
||||
const verifyAddress = await TrezorConnect.ethereumGetAddress({
|
||||
device: {
|
||||
path: device.path,
|
||||
instance: device.instance,
|
||||
state: device.checksum
|
||||
},
|
||||
address_n: path,
|
||||
showOnTrezor: false
|
||||
});
|
||||
if (discoveryProcess.interrupted) return;
|
||||
|
||||
if (verifyAddress && verifyAddress.success) {
|
||||
//const trezorAddress: string = '0x' + verifyAddress.data.message.address;
|
||||
const trezorAddress: string = EthereumjsUtil.toChecksumAddress(verifyAddress.data.message.address);
|
||||
if (trezorAddress !== ethAddress) {
|
||||
// throw inconsistent state error
|
||||
console.warn("Inconsistent state", trezorAddress, ethAddress);
|
||||
|
||||
dispatch({
|
||||
type: NOTIFICATION.ADD,
|
||||
payload: {
|
||||
type: 'error',
|
||||
title: 'Address validation error',
|
||||
message: `Addresses are different. ${ trezorAddress } : ${ ethAddress }`,
|
||||
cancelable: true,
|
||||
actions: [
|
||||
{
|
||||
label: 'Try again',
|
||||
callback: () => {
|
||||
dispatch(startDiscoveryProcess(device, discoveryProcess.coin))
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// handle TREZOR communication error
|
||||
dispatch({
|
||||
type: ACTIONS.ADDRESS_CREATE,
|
||||
devicePath,
|
||||
address
|
||||
})
|
||||
type: NOTIFICATION.ADD,
|
||||
payload: {
|
||||
type: 'error',
|
||||
title: 'Address validation error',
|
||||
message: verifyAddress.data.error,
|
||||
cancelable: true,
|
||||
actions: [
|
||||
{
|
||||
label: 'Try again',
|
||||
callback: () => {
|
||||
dispatch(startDiscoveryProcess(device, discoveryProcess.coin))
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const balance = await getBalance(ethAddress);
|
||||
const web3instance = getState().web3.find(w3 => w3.coin === coin);
|
||||
|
||||
const balance = await getBalanceAsync(web3instance.web3, ethAddress);
|
||||
if (discoveryProcess.interrupted) return;
|
||||
dispatch({
|
||||
type: ADDRESS.SET_BALANCE,
|
||||
address: ethAddress,
|
||||
balance: web3instance.web3.fromWei(balance.toString(), 'ether')
|
||||
});
|
||||
|
||||
const userTokens = [];
|
||||
// const userTokens = [
|
||||
// { symbol: 'T01', address: '0x58cda554935e4a1f2acbe15f8757400af275e084' },
|
||||
// { symbol: 'Lahod', address: '0x3360d0ee34a49d9ac34dce88b000a2903f2806ee' },
|
||||
// ];
|
||||
|
||||
for (let i = 0; i < userTokens.length; i++) {
|
||||
const tokenBalance = await getTokenBalanceAsync(web3instance.erc20, userTokens[i].address, ethAddress);
|
||||
if (discoveryProcess.interrupted) return;
|
||||
dispatch({
|
||||
type: ACTIONS.ADDRESS_SET_BALANCE,
|
||||
address,
|
||||
balance: web3.fromWei(balance.toString(), 'ether')
|
||||
type: TOKEN.SET_BALANCE,
|
||||
tokenName: userTokens[i].symbol,
|
||||
ethAddress: ethAddress,
|
||||
tokenAddress: userTokens[i].address,
|
||||
balance: tokenBalance.toString()
|
||||
})
|
||||
}
|
||||
|
||||
const nonce = await getNonce(web3instance.web3, ethAddress);
|
||||
if (discoveryProcess.interrupted) return;
|
||||
dispatch({
|
||||
type: ADDRESS.SET_NONCE,
|
||||
address: ethAddress,
|
||||
nonce: nonce
|
||||
});
|
||||
|
||||
const addressIsEmpty = nonce < 1 && !balance.greaterThan(0);
|
||||
|
||||
if (!addressIsEmpty) {
|
||||
//dispatch( startDiscoveryProcess(device, discoveryProcess.coin) );
|
||||
dispatch( discoverAddress(device, discoveryProcess) );
|
||||
} else {
|
||||
// release acquired sesssion
|
||||
await TrezorConnect.getPublicKey({
|
||||
device: {
|
||||
path: device.path,
|
||||
instance: device.instance,
|
||||
state: device.checksum
|
||||
},
|
||||
path: "m/44'/60'/0'/0",
|
||||
confirmation: false,
|
||||
keepSession: false
|
||||
});
|
||||
if (discoveryProcess.interrupted) return;
|
||||
|
||||
dispatch({
|
||||
type: DISCOVERY.COMPLETE,
|
||||
device,
|
||||
coin
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function startDiscoveryProcess(device: any, coin: string, ignoreCompleted?: boolean): any {
|
||||
return (dispatch, getState) => {
|
||||
|
||||
const selected = findSelectedDevice(getState().connect);
|
||||
if (!selected) {
|
||||
// TODO: throw error
|
||||
console.error("Start discovery: no selected device", device)
|
||||
return;
|
||||
} else if (selected.path !== device.path) {
|
||||
console.error("Start discovery: requested device is not selected", device, selected)
|
||||
return;
|
||||
} else if (!selected.checksum) {
|
||||
console.warn("Start discovery: Selected device wasn't authenticated yet...")
|
||||
return;
|
||||
}
|
||||
|
||||
// const history = await getTransactionHistory(ethAddress);
|
||||
// dispatch({
|
||||
// type: ACTIONS.ADDRESS_SET_HISTORY,
|
||||
// address,
|
||||
// history
|
||||
// })
|
||||
const discovery = getState().discovery;
|
||||
let discoveryProcess: ?Discovery = discovery.find(d => d.checksum === device.checksum && d.coin === coin);
|
||||
|
||||
// TODO redirect to 1st account
|
||||
if (index === 0) {
|
||||
dispatch( push('/address/0') );
|
||||
if (!selected.connected && (!discoveryProcess || !discoveryProcess.completed)) {
|
||||
dispatch({
|
||||
type: DISCOVERY.WAITING,
|
||||
device,
|
||||
coin
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!discoveryProcess) {
|
||||
dispatch( beginDiscoveryProcess(device, coin) );
|
||||
return;
|
||||
} else {
|
||||
if (discoveryProcess.completed && !ignoreCompleted) {
|
||||
dispatch({
|
||||
type: DISCOVERY.COMPLETE,
|
||||
device,
|
||||
coin
|
||||
});
|
||||
} else if (discoveryProcess.interrupted || discoveryProcess.waitingForDevice) {
|
||||
// discovery cycle was interrupted
|
||||
// start from beginning
|
||||
dispatch( beginDiscoveryProcess(device, coin) );
|
||||
} else {
|
||||
dispatch( discoverAddress(device, discoveryProcess) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const restoreDiscovery = (): any => {
|
||||
return (dispatch, getState): void => {
|
||||
const selected = findSelectedDevice(getState().connect);
|
||||
|
||||
if (selected && selected.connected && !selected.unacquired) {
|
||||
const discoveryProcess: ?Discovery = getState().discovery.find(d => d.checksum === selected.checksum && d.waitingForDevice);
|
||||
if (discoveryProcess) {
|
||||
dispatch( startDiscoveryProcess(selected, discoveryProcess.coin) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// there is no discovery process but it should be
|
||||
// this is possible race condition when coin was changed in url but device wasn't authenticated yet
|
||||
// try to discovery after CONNECT.AUTH_DEVICE action
|
||||
export const checkDiscoveryStatus = (): any => {
|
||||
return (dispatch, getState): void => {
|
||||
const selected = findSelectedDevice(getState().connect);
|
||||
if (!selected) return;
|
||||
|
||||
if (index < 2) {
|
||||
loop( index + 1);
|
||||
const urlParams = getState().router.location.params;
|
||||
if (urlParams.coin) {
|
||||
const discoveryProcess: ?Discovery = getState().discovery.find(d => d.checksum === selected.checksum && d.coin === urlParams.coin);
|
||||
if (!discoveryProcess) {
|
||||
dispatch( startDiscoveryProcess(selected, urlParams.coin) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loop(0);
|
||||
|
||||
|
||||
export function stopDiscoveryProcess(device: any): any {
|
||||
|
||||
// TODO: release devices session
|
||||
// corner case swtich /eth to /etc (discovery start stop - should not be async)
|
||||
return {
|
||||
type: DISCOVERY.STOP,
|
||||
device
|
||||
}
|
||||
}
|
||||
|
||||
export function onSelectDevice(): any {
|
||||
return async (dispatch, getState) => {
|
||||
// dispatch(Web3Actions.composeTransaction());
|
||||
export function addAddress(): any {
|
||||
return (dispatch, getState) => {
|
||||
const selected = findSelectedDevice(getState().connect);
|
||||
dispatch( startDiscoveryProcess(selected, getState().router.location.params.coin, true) ); // TODO: coin nicer
|
||||
}
|
||||
}
|
||||
|
@ -1,154 +0,0 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import HDKey from 'hdkey';
|
||||
import EthereumjsUtil from 'ethereumjs-util';
|
||||
import EthereumjsTx from 'ethereumjs-tx';
|
||||
import TrezorConnect from 'trezor-connect';
|
||||
import { strip } from '../utils/ethUtils';
|
||||
|
||||
export function getTransaction(web3, txid) {
|
||||
return new Promise((resolve, reject) => {
|
||||
web3.eth.getTransaction(txid, (error, result) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function getBalance(web3, address) {
|
||||
return new Promise((resolve, reject) => {
|
||||
web3.eth.getBalance(address, (error, result) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function getNonce(web3, address) {
|
||||
return new Promise((resolve, reject) => {
|
||||
web3.eth.getTransactionCount(address, (error, result) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function estimateGas(web3, gasOptions) {
|
||||
return new Promise((resolve, reject) => {
|
||||
web3.eth.estimateGas(gasOptions, (error, result) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
export function getGasPrice(web3) {
|
||||
return new Promise((resolve, reject) => {
|
||||
web3.eth.getGasPrice((error, result) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
export function push(web3, tx) {
|
||||
return new Promise((resolve, reject) => {
|
||||
web3.eth.sendRawTransaction(tx, (error, result) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
export function composeTransaction() {
|
||||
return async function (dispatch, getState) {
|
||||
const { web3 } = getState().web3;
|
||||
const { address, amount } = getState().sendForm;
|
||||
|
||||
const resp = await TrezorConnect.getPublicKey({ path: "m/44'/60'/0'/0", confirmation: false });
|
||||
|
||||
const hdk = new HDKey();
|
||||
hdk.publicKey = new Buffer(resp.data.publicKey, 'hex');
|
||||
hdk.chainCode = new Buffer(resp.data.chainCode, 'hex');
|
||||
|
||||
const derivedKey = hdk.derive("m/0");
|
||||
const myAddress = EthereumjsUtil.publicToAddress(derivedKey.publicKey, true);
|
||||
|
||||
const txData = {
|
||||
address_n: [
|
||||
(44 | 0x80000000) >>> 0,
|
||||
(60 | 0x80000000) >>> 0,
|
||||
(0 | 0x80000000) >>> 0,
|
||||
0, 0
|
||||
],
|
||||
to: address,
|
||||
value: web3.toHex(web3.toWei(amount, 'ether')),
|
||||
data,
|
||||
chainId: 3
|
||||
}
|
||||
|
||||
console.log("NONCE", myAddress)
|
||||
const nonce = await getNonce(web3, '0x' + myAddress.toString('hex') );
|
||||
console.log("NONCE", nonce)
|
||||
|
||||
const gasOptions = {
|
||||
to: txData.to,
|
||||
data: txData.data
|
||||
}
|
||||
const gasLimit = await estimateGas(web3, gasOptions);
|
||||
const gasPrice = await getGasPrice(web3);
|
||||
|
||||
txData.nonce = web3.toHex(nonce);
|
||||
txData.gasLimit = web3.toHex(gasLimit);
|
||||
txData.gasPrice = web3.toHex(gasPrice);
|
||||
|
||||
console.log("NONCE", nonce, gasLimit, gasPrice)
|
||||
|
||||
let signedTransaction = await TrezorConnect.ethereumSignTransaction({
|
||||
//path: "m/44'/60'/0'/0/0",
|
||||
address_n: txData.address_n,
|
||||
nonce: strip(txData.nonce),
|
||||
gas_price: strip(txData.gasPrice),
|
||||
gas_limit: strip(txData.gasLimit),
|
||||
to: strip(txData.to),
|
||||
value: strip(txData.value),
|
||||
data: txData.data,
|
||||
chain_id: txData.chainId
|
||||
});
|
||||
|
||||
txData.r = '0x' + signedTransaction.data.r;
|
||||
txData.s = '0x' + signedTransaction.data.s;
|
||||
txData.v = web3.toHex(signedTransaction.data.v);
|
||||
|
||||
const tx = new EthereumjsTx(txData);
|
||||
const serializedTx = '0x' + tx.serialize().toString('hex');
|
||||
|
||||
const txid = await push(web3, serializedTx);
|
||||
|
||||
dispatch({
|
||||
type: 'tx_complete',
|
||||
txid
|
||||
})
|
||||
|
||||
console.log("TXID", txid);
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
export const START: string = 'discovery__start';
|
||||
export const STOP: string = 'discovery__stop';
|
||||
export const COMPLETE: string = 'discovery__complete';
|
||||
export const WAITING: string = 'discovery__waiting';
|
||||
export const FROM_STORAGE: string = 'discovery__from_storage';
|
@ -0,0 +1,6 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
export const SAVE: string = 'storage__save';
|
||||
export const READY: string = 'storage__ready';
|
||||
export const ERROR: string = 'storage__error';
|
@ -0,0 +1,13 @@
|
||||
export const ON_PASSPHRASE_CHANGE: string = 'action__on_passphrase_change';
|
||||
export const ON_PASSPHRASE_SHOW: string = 'action__on_passphrase_show';
|
||||
export const ON_PASSPHRASE_HIDE: string = 'action__on_passphrase_hide';
|
||||
export const ON_PASSPHRASE_SAVE: string = 'action__on_passphrase_save';
|
||||
export const ON_PASSPHRASE_FORGET: string = 'action__on_passphrase_forget';
|
||||
export const ON_PASSPHRASE_FOCUS: string = 'action__on_passphrase_focus';
|
||||
export const ON_PASSPHRASE_BLUR: string = 'action__on_passphrase_blur';
|
||||
export const ON_PASSPHRASE_SUBMIT: string = 'action__on_passphrase_submit';
|
||||
|
||||
export const FORGET: string = 'modal__forget';
|
||||
export const REMEMBER: string = 'modal__remember';
|
||||
export const ON_FORGET: string = 'modal__on_forget';
|
||||
export const ON_REMEMBER: string = 'modal__on_remember';
|
@ -0,0 +1,19 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
export const INIT: string = 'send__init';
|
||||
export const DISPOSE: string = 'send__dispose';
|
||||
export const VALIDATION: string = 'send__validation';
|
||||
export const ADDRESS_CHANGE: string = 'send__address_change';
|
||||
export const AMOUNT_CHANGE: string = 'send__amount_change';
|
||||
export const SET_MAX: string = 'send__set_max';
|
||||
export const CURRENCY_CHANGE: string = 'send__currency_change';
|
||||
export const FEE_LEVEL_CHANGE: string = 'send__fee_level_change';
|
||||
export const GAS_PRICE_CHANGE: string = 'send__gas_price_change';
|
||||
export const GAS_LIMIT_CHANGE: string = 'send__gas_limit_change';
|
||||
export const UPDATE_FEE_LEVELS: string = 'send__update_fee_levels';
|
||||
export const DATA_CHANGE: string = 'send__data_change';
|
||||
export const SEND: string = 'send__submit';
|
||||
export const TX_COMPLETE: string = 'send__tx_complete';
|
||||
export const TX_ERROR: string = 'send__tx_error';
|
||||
export const TOGGLE_ADVANCED: string = 'send__toggle_advanced';
|
@ -0,0 +1,7 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
export const ADD: string = 'token__add';
|
||||
export const REMOVE: string = 'token__remove';
|
||||
export const SET_BALANCE: string = 'token__set_balance';
|
||||
export const FROM_STORAGE: string = 'token__from_storage';
|
@ -0,0 +1,26 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
export const READY: string = 'trezorconnect__ready';
|
||||
export const INITIALIZATION_ERROR: string = 'trezorconnect__init_error';
|
||||
export const SELECT_DEVICE: string = 'trezorconnect__select_device';
|
||||
|
||||
|
||||
export const DEVICE_FROM_STORAGE: string = 'trezorconnect__device_from_storage';
|
||||
export const AUTH_DEVICE: string = 'trezorconnect__auth_device';
|
||||
export const COIN_CHANGED: string = 'trezorconnect__coin_changed';
|
||||
|
||||
export const REMEMBER_REQUEST: string = 'trezorconnect__remember_request';
|
||||
export const FORGET_REQUEST: string = 'trezorconnect__forget_request';
|
||||
export const FORGET: string = 'trezorconnect__forget';
|
||||
export const FORGET_SINGLE: string = 'trezorconnect__forget_single';
|
||||
export const DISCONNECT_REQUEST: string = 'trezorconnect__disconnect_request';
|
||||
export const REMEMBER: string = 'trezorconnect__remember';
|
||||
|
||||
export const START_ACQUIRING: string = 'trezorconnect__start_acquiring';
|
||||
export const STOP_ACQUIRING: string = 'trezorconnect__stop_acquiring';
|
||||
|
||||
export const TRY_TO_DUPLICATE: string = 'trezorconnect__try_to_duplicate';
|
||||
export const DUPLICATE: string = 'trezorconnect__duplicate';
|
||||
|
||||
export const DEVICE_STATE_EXCEPTION: string = 'trezorconnect__device_state_exception';
|
@ -0,0 +1,9 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
export const START: string = 'web3__start';
|
||||
export const STOP: string = 'web3__stop';
|
||||
export const CREATE: string = 'web3__create';
|
||||
export const READY: string = 'web3__ready';
|
||||
export const BLOCK_UPDATED: string = 'web3__block_updated';
|
||||
export const GAS_PRICE_UPDATED: string = 'web3__gas_price_updated';
|
@ -0,0 +1,11 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
export const INIT: string = 'account__init';
|
||||
export const DISPOSE: string = 'account__dispose';
|
||||
|
||||
export const CREATE: string = 'address__create';
|
||||
export const REMOVE: string = 'address__remove';
|
||||
export const SET_BALANCE: string = 'address__set_balance';
|
||||
export const SET_NONCE: string = 'address__set_nonce';
|
||||
export const FROM_STORAGE: string = 'address__from_storage';
|
@ -0,0 +1,9 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
export const CREATE: string = 'address__create';
|
||||
export const REMOVE: string = 'address__remove';
|
||||
export const SET_BALANCE: string = 'address__set_balance';
|
||||
export const SET_NONCE: string = 'address__set_nonce';
|
||||
export const FROM_STORAGE: string = 'address__from_storage';
|
||||
|
@ -0,0 +1,6 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
export const ADD: string = 'notification__add';
|
||||
export const CLOSE: string = 'notification__close';
|
||||
export const REMOVE: string = 'account__remove';
|
@ -0,0 +1,8 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
export const INIT: string = 'receive__init';
|
||||
export const DISPOSE: string = 'receive__dispose';
|
||||
export const REQUEST_UNVERIFIED: string = 'receive__request_unverified';
|
||||
export const SHOW_ADDRESS: string = 'receive__show_address';
|
||||
export const SHOW_UNVERIFIED_ADDRESS: string = 'receive__show_unverified';
|
@ -0,0 +1,7 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
export const INIT: string = 'summary__init';
|
||||
export const DISPOSE: string = 'summary__dispose';
|
||||
export const ADD_TOKEN: string = 'summary__add_token';
|
||||
export const DETAILS_TOGGLE: string = 'summary__details_toggle';
|
@ -1,27 +0,0 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
const AddressMenu = (props): any => {
|
||||
|
||||
const { addresses } = props.addresses;
|
||||
|
||||
let accounts = addresses.map((address, i) => {
|
||||
return (
|
||||
<NavLink key={i} activeClassName="selected" to={ `/address/${i}` }>
|
||||
{ `Address #${(address.index + 1 )}` }
|
||||
<span>{ address.balance } ETH</span>
|
||||
</NavLink>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<section className="accounts">
|
||||
{ accounts }
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default AddressMenu;
|
@ -1,27 +0,0 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const AddressTab = (props): any => {
|
||||
|
||||
const urlParams = props.match.params;
|
||||
const basePath = `/address/${urlParams.address}`;
|
||||
|
||||
return (
|
||||
<div className="address-menu">
|
||||
<Link to={ `${basePath}` }>
|
||||
History
|
||||
</Link>
|
||||
<Link to={ `${basePath}/send` }>
|
||||
Send
|
||||
</Link>
|
||||
<Link to={ `${basePath}/receive` }>
|
||||
Receive
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AddressTab;
|
@ -1,42 +0,0 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export default class Devices extends Component {
|
||||
render() {
|
||||
const { devices, selectedDevice } = this.props.connect;
|
||||
const deviceList: Array<any> = devices.map((dev, index) => {
|
||||
let css: string = "";
|
||||
if (dev.unacquired) {
|
||||
css += "unacquired";
|
||||
}
|
||||
if (dev.isUsedElsewhere) {
|
||||
css += " used-elsewhere";
|
||||
}
|
||||
if (dev.featuresNeedsReload) {
|
||||
css += " reload-features";
|
||||
}
|
||||
if (dev.path === selectedDevice) {
|
||||
css += " active";
|
||||
}
|
||||
return (<li key={index} className={css} onClick={ event => this.props.onSelectDevice(dev.path) } >{ dev.label }</li>);
|
||||
});
|
||||
|
||||
if (deviceList.length === 0) {
|
||||
deviceList.push(
|
||||
(<li key={0}>No connected devices</li>)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<nav>
|
||||
<div className="layout-wrapper">
|
||||
<ul>
|
||||
{ deviceList }
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export default class Footer extends Component {
|
||||
render() {
|
||||
return (
|
||||
<footer>
|
||||
<div className="layout-wrapper">
|
||||
<span>© 2017</span>
|
||||
<a href="http://satoshilabs.com">SatoshiLabs</a>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
export default class Main extends Component {
|
||||
render() {
|
||||
return (
|
||||
<main>
|
||||
<section className="methods">
|
||||
<ul className="accounts">
|
||||
</ul>
|
||||
</section>
|
||||
<section className="method-content">
|
||||
{ this.props.children }
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import AddressTab from './AddressTab';
|
||||
import { QRCode } from 'react-qr-svg';
|
||||
|
||||
const History = (props): any => {
|
||||
|
||||
const { addresses } = props.addresses;
|
||||
const currentAddress = addresses[ parseInt(props.match.params.address) ];
|
||||
|
||||
if (!currentAddress) return null;
|
||||
|
||||
return (
|
||||
<section className="receive">
|
||||
<AddressTab match={ props.match } />
|
||||
<h3>{ currentAddress.address }</h3>
|
||||
<QRCode
|
||||
bgColor="#FFFFFF"
|
||||
fgColor="#000000"
|
||||
level="Q"
|
||||
style={{ width: 256 }}
|
||||
value={ currentAddress.address }
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default History;
|
@ -1,72 +0,0 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import AddressTab from './AddressTab';
|
||||
|
||||
const SendForm = (props): any => {
|
||||
|
||||
console.log("ENDFORM", props)
|
||||
|
||||
const addressId = parseInt( props.match.params.address );
|
||||
|
||||
const {
|
||||
address,
|
||||
amount,
|
||||
gasPrice,
|
||||
gasLimit,
|
||||
data
|
||||
} = props.sendForm;
|
||||
|
||||
const {
|
||||
onAddressChange,
|
||||
onAmountChange,
|
||||
onGasPriceChange,
|
||||
onGasLimitChange,
|
||||
onDataChange,
|
||||
onSend
|
||||
} = props.sendFormActions;
|
||||
|
||||
const disabled = false;
|
||||
|
||||
return (
|
||||
<section className="send-form">
|
||||
|
||||
<AddressTab match={ props.match } />
|
||||
|
||||
<div className="row">
|
||||
<label>Amount</label>
|
||||
<input type="text" value={ address } onChange={ event => onAddressChange(event.target.value) } />
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<label>Address</label>
|
||||
<input type="text" value={ amount } onChange={ event => onAmountChange(event.target.value) } />
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<label>Gas limit</label>
|
||||
<input type="text" value={ gasLimit } onChange={ event => onGasLimitChange(event.target.value) } />
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<label>Gas price</label>
|
||||
<input type="text" value={ gasPrice } onChange={ event => onGasPriceChange(event.target.value) } />
|
||||
GWEI
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<label>Data</label>
|
||||
<input type="text" value={ data } onChange={ event => onDataChange(event.target.value) } />
|
||||
</div>
|
||||
|
||||
<div className="row">
|
||||
<label></label>
|
||||
<button disabled={ disabled } onClick={ event => onSend(addressId) }>SEND</button>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default SendForm;
|
@ -0,0 +1,17 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
const Footer = (props: any): any => {
|
||||
return (
|
||||
<footer>
|
||||
<span>© 2018</span>
|
||||
<a href="http://satoshilabs.com" target="_blank" className="satoshi green">SatoshiLabs</a>
|
||||
<a href="tos.pdf" target="_blank" className="green">Terms</a>
|
||||
<a onClick={ props.showLog } className="green">Show Log</a>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
export default Footer;
|
@ -0,0 +1,22 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export default (props: any): any => {
|
||||
|
||||
const style = {
|
||||
width: `${props.size}px`,
|
||||
height: `${props.size}px`,
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="loader-circle" style={ style }>
|
||||
<p>{ props.label }</p>
|
||||
<svg className="circular" viewBox="25 25 50 50">
|
||||
<circle className="route" cx="50" cy="50" r="20" fill="none" stroke="" strokeWidth="1" strokeMiterlimit="10" />
|
||||
<circle className="path" cx="50" cy="50" r="20" fill="none" strokeWidth="1" strokeMiterlimit="10" />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import * as SendFormActions from '../../actions/SendFormActions';
|
||||
import { getAddress } from '../../actions/TrezorConnectActions';
|
||||
|
||||
|
||||
const Log = (props: any) => {
|
||||
return (
|
||||
<details className="log">
|
||||
Log
|
||||
</details>
|
||||
)
|
||||
}
|
||||
|
||||
function mapStateToProps(state, own) {
|
||||
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state) => {
|
||||
return {
|
||||
accounts: state.accounts,
|
||||
receive: state.receive
|
||||
};
|
||||
},
|
||||
(dispatch) => {
|
||||
return {
|
||||
getAddress: bindActionCreators(getAddress, dispatch),
|
||||
};
|
||||
}
|
||||
)(Log);
|
@ -0,0 +1,74 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import * as NOTIFICATION from '../../actions/constants/notification';
|
||||
|
||||
|
||||
export const Notification = (props: any) => {
|
||||
const className = `notification ${ props.className }`;
|
||||
|
||||
|
||||
const actionButtons = !props.actions ? null : props.actions.map((a, i) => {
|
||||
return (
|
||||
<button key={ i } onClick={ event => { props.close(); a.callback(); } } className="transparent">{ a.label }</button>
|
||||
)
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={ className }>
|
||||
{ props.cancelable ? (
|
||||
<button className="notification-close transparent"
|
||||
onClick={ event => props.close() }></button>
|
||||
) : null }
|
||||
<div className="notification-body">
|
||||
<h2>{ props.title }</h2>
|
||||
<p dangerouslySetInnerHTML={{__html: props.message }}></p>
|
||||
</div>
|
||||
{ props.actions && props.actions.length > 0 ? (
|
||||
<div className="notification-action">
|
||||
{ actionButtons }
|
||||
</div>
|
||||
) : null }
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const NotificationGroup = (props: any) => {
|
||||
const { notifications, close } = props;
|
||||
return notifications.map((n, i) => {
|
||||
return (
|
||||
<Notification
|
||||
key={i}
|
||||
className={ n.type }
|
||||
title={ n.title }
|
||||
message={ n.message }
|
||||
cancelable={ n.cancelable }
|
||||
actions={ n.actions }
|
||||
close={ close }
|
||||
/>
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state) => {
|
||||
return {
|
||||
notifications: state.notifications
|
||||
};
|
||||
},
|
||||
(dispatch) => {
|
||||
return {
|
||||
close: bindActionCreators((notif) => {
|
||||
return {
|
||||
type: NOTIFICATION.CLOSE,
|
||||
payload: notif
|
||||
}
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
)(NotificationGroup);
|
@ -0,0 +1,36 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Header from '../common/Header';
|
||||
import Footer from '../common/Footer';
|
||||
|
||||
export default (props: any): any => {
|
||||
return (
|
||||
<div className="app connect-device">
|
||||
<Header />
|
||||
<main>
|
||||
<h2>The private bank in your hands.</h2>
|
||||
<p>TREZOR Wallet is an easy-to-use interface for your TREZOR.</p>
|
||||
<p>TREZOR Wallet allows you to easily control your funds, manage your balance and initiate transfers.</p>
|
||||
<div className="row">
|
||||
<p className="connect">
|
||||
<svg width="20px" height="57px" viewBox="0 0 20 57">
|
||||
<g stroke="none" strokeWidth="1" fill="none" transform="translate(1, 1)">
|
||||
<rect className="connect-usb-pin" fill="#01B757" x="6" y="39" width="6" height="5"></rect>
|
||||
<rect className="connect-usb-cable" stroke="#01B757" strokeWidth="1" x="8.5" y="44.5" width="1" height="11"></rect>
|
||||
<path stroke="#01B757" d="M8.90856859,33.9811778 L6.43814432,33.9811778 C5.45301486,34.0503113 4.69477081,33.6889084 4.1634122,32.8969691 C3.36637428,31.7090602 -0.000402169348,26.3761977 0.0748097911,23.2982514 C0.124878873,21.2492429 0.0999525141,14.5598149 3.07156595e-05,3.22996744 C-0.000274213164,3.1963928 0.00243636275,3.162859 0.00812115776,3.12976773 C0.28477346,1.51937083 1.22672004,0.617538852 2.8339609,0.424271782 C4.45813658,0.228968338 6.54411954,0.0875444105 9.09190977,0 L9.09190977,0.0169167084 C11.5566027,0.104886477 13.5814718,0.244169993 15.1665175,0.434768145 C16.7530267,0.625542287 17.6912941,1.50671985 17.9813196,3.07830083 C17.9943481,3.14889902 18.0005888,3.22058224 17.9999563,3.29236974 L17.9999901,3.29237004 C17.9004498,14.5907444 17.875676,21.2628703 17.9256686,23.3087478 C18.0008805,26.3866941 14.6341041,31.7195566 13.8370662,32.9074655 C13.3057075,33.6994047 12.5474635,34.0608076 11.562334,33.9916742 L8.90856859,33.9916742 L8.90856859,33.9811778 Z"></path>
|
||||
<rect fill="#01B757" x="2" y="7" width="14" height="7" rx="0.5625"></rect>
|
||||
</g>
|
||||
</svg>
|
||||
<span>Connect TREZOR to continue</span>
|
||||
</p>
|
||||
{/* <p>Don't have TREZOR? <a href="https://trezor.io/" target="_blank">Get one</a></p> */}
|
||||
</div>
|
||||
<div className="image"></div>
|
||||
<p>Don't have TREZOR? <a href="https://trezor.io/" className="green" target="_blank">Get one</a></p>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Preloader from './Preloader';
|
||||
import ConnectDevice from './ConnectDevice';
|
||||
import LocalStorageError from './LocalStorageError';
|
||||
import TrezorConnectError from './TrezorConnectError';
|
||||
import Header from '../common/Header';
|
||||
import Footer from '../common/Footer';
|
||||
import { Notification } from '../common/Notification';
|
||||
|
||||
export default (props: any): any => {
|
||||
|
||||
const web3 = props.web3;
|
||||
const { devices } = props.connect;
|
||||
const localStorageError = props.localStorage.error;
|
||||
const connectError = props.connect.error;
|
||||
|
||||
let notification = null;
|
||||
|
||||
if (localStorageError) {
|
||||
notification = (<Notification
|
||||
title="Error"
|
||||
message="Some files are missing"
|
||||
className="error"
|
||||
/>);
|
||||
}
|
||||
|
||||
if (connectError) {
|
||||
notification = (<Notification
|
||||
title="Error"
|
||||
message={ connectError }
|
||||
className="error"
|
||||
/>);
|
||||
}
|
||||
|
||||
if (notification || (web3.length > 0 && devices.length < 1)) {
|
||||
return (
|
||||
<div className="app connect-device">
|
||||
<Header />
|
||||
{ notification }
|
||||
<main>
|
||||
<h2 className="claim">The private bank in your hands.</h2>
|
||||
<p>TREZOR Wallet is an easy-to-use interface for your TREZOR.</p>
|
||||
<p>TREZOR Wallet allows you to easily control your funds, manage your balance and initiate transfers.</p>
|
||||
<div className="row">
|
||||
<p className="connect">
|
||||
<span>
|
||||
<svg width="12px" height="35px" viewBox="0 0 20 57">
|
||||
<g stroke="none" strokeWidth="1" fill="none" transform="translate(1, 1)">
|
||||
<rect className="connect-usb-pin" fill="#01B757" x="6" y="39" width="6" height="5"></rect>
|
||||
<rect className="connect-usb-cable" stroke="#01B757" strokeWidth="1" x="8.5" y="44.5" width="1" height="11"></rect>
|
||||
<path stroke="#01B757" d="M8.90856859,33.9811778 L6.43814432,33.9811778 C5.45301486,34.0503113 4.69477081,33.6889084 4.1634122,32.8969691 C3.36637428,31.7090602 -0.000402169348,26.3761977 0.0748097911,23.2982514 C0.124878873,21.2492429 0.0999525141,14.5598149 3.07156595e-05,3.22996744 C-0.000274213164,3.1963928 0.00243636275,3.162859 0.00812115776,3.12976773 C0.28477346,1.51937083 1.22672004,0.617538852 2.8339609,0.424271782 C4.45813658,0.228968338 6.54411954,0.0875444105 9.09190977,0 L9.09190977,0.0169167084 C11.5566027,0.104886477 13.5814718,0.244169993 15.1665175,0.434768145 C16.7530267,0.625542287 17.6912941,1.50671985 17.9813196,3.07830083 C17.9943481,3.14889902 18.0005888,3.22058224 17.9999563,3.29236974 L17.9999901,3.29237004 C17.9004498,14.5907444 17.875676,21.2628703 17.9256686,23.3087478 C18.0008805,26.3866941 14.6341041,31.7195566 13.8370662,32.9074655 C13.3057075,33.6994047 12.5474635,34.0608076 11.562334,33.9916742 L8.90856859,33.9916742 L8.90856859,33.9811778 Z"></path>
|
||||
<rect fill="#01B757" x="2" y="7" width="14" height="7" rx="0.5625"></rect>
|
||||
</g>
|
||||
</svg>
|
||||
Connect TREZOR to continue
|
||||
</span>
|
||||
</p>
|
||||
{/* <button>Add new device</button> */}
|
||||
{/* <p>Don't have TREZOR? <a href="https://trezor.io/" target="_blank">Get one</a></p> */}
|
||||
</div>
|
||||
<div className="image"></div>
|
||||
<p>Don't have TREZOR? <a href="https://trezor.io/" className="green" target="_blank">Get one</a></p>
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (<Preloader />);
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export default (props: any): any => {
|
||||
return (
|
||||
<section className="landing">
|
||||
localstorage ERROR
|
||||
</section>
|
||||
);
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import Loader from '../common/LoaderCircle';
|
||||
|
||||
export default (props: any): any => {
|
||||
return (
|
||||
<section className="landing">
|
||||
<Loader label="Loading" size="100" />
|
||||
</section>
|
||||
);
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export default (props: any): any => {
|
||||
return (
|
||||
<section className="landing">
|
||||
connect ERROR
|
||||
</section>
|
||||
);
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import { formatAmount } from '../../utils/formatUtils';
|
||||
|
||||
const AccountSelection = (props): any => {
|
||||
|
||||
const { accounts, coinInfo, complete } = props.modal;
|
||||
const accountsCollection = accounts.map((a, index) => {
|
||||
|
||||
let accountStatus: string = a.fresh ? 'Fresh account' : formatAmount(a.balance, coinInfo);
|
||||
// Loading...
|
||||
|
||||
return (
|
||||
<div key={ index } className="account account_default">
|
||||
<button onClick={ event => props.modalActions.onAccountSelect(index) }>
|
||||
<span className="account_title">{ a.label }</span>
|
||||
<span className="account_status">{ accountStatus }</span>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
});
|
||||
|
||||
const header: string = complete ? `Select ${ coinInfo.label } account` : `Loading ${ coinInfo.label } accounts...`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3>{ header }</h3>
|
||||
<div className="account_type_tabs">
|
||||
<div data-tab="normal" className="account_type_tab account_type_normal active">Accounts</div>
|
||||
<div data-tab="legacy" className="account_type_tab account_type_legacy">Legacy Accounts</div>
|
||||
</div>
|
||||
<div className="accounts_list">
|
||||
{ accountsCollection }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AccountSelection;
|
@ -0,0 +1,55 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import { findSelectedDevice } from '../../reducers/TrezorConnectReducer';
|
||||
|
||||
const ConfirmAddress = (props: any): any => {
|
||||
|
||||
const account = props.accounts.find(a => a.checksum === props.receive.checksum && a.index === props.receive.accountIndex && a.coin === props.receive.coin);
|
||||
|
||||
return (
|
||||
<div className="confirm-address">
|
||||
<div className="header">
|
||||
<h3>Confirm address on TREZOR</h3>
|
||||
<p>Please compare your address on device with address shown bellow.</p>
|
||||
</div>
|
||||
<div className="content">
|
||||
<p>{ account.address }</p>
|
||||
<label>{ account.coin.toUpperCase() } account #{ (account.index + 1) }</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default ConfirmAddress;
|
||||
|
||||
export const ConfirmUnverifiedAddress = (props: any): any => {
|
||||
|
||||
const account = props.accounts.find(a => a.checksum === props.receive.checksum && a.index === props.receive.accountIndex && a.coin === props.receive.coin);
|
||||
|
||||
const {
|
||||
onCancel
|
||||
} = props.modalActions;
|
||||
|
||||
const {
|
||||
showUnverifiedAddress,
|
||||
showAddress
|
||||
} = props.receiveActions;
|
||||
|
||||
|
||||
return (
|
||||
<div className="confirm-address-unverified">
|
||||
<button className="close-modal transparent" onClick={ onCancel }></button>
|
||||
<h3>Your TREZOR is not connected</h3>
|
||||
<p>To prevent phishing attacks, you should verify the address on your TREZOR first. Please reconnect your device to continue with the verification process.</p>
|
||||
<button onClick={ event => {
|
||||
onCancel();
|
||||
showAddress(account.addressPath);
|
||||
} }>Try again</button>
|
||||
<button className="white" onClick={ event => {
|
||||
onCancel();
|
||||
showUnverifiedAddress();
|
||||
} }>Show unverified address</button>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
const Confirmation = (props): any => {
|
||||
const {
|
||||
amount,
|
||||
address,
|
||||
coin,
|
||||
token,
|
||||
total,
|
||||
selectedFeeLevel
|
||||
} = props.sendForm;
|
||||
|
||||
return (
|
||||
<div className="confirm-tx">
|
||||
<div className="header">
|
||||
<h3>Confirm transaction on your TREZOR</h3>
|
||||
<p>Details are shown on device</p>
|
||||
</div>
|
||||
<div className="content">
|
||||
<label>Send </label>
|
||||
<p>{ `${amount} ${token.toUpperCase() }` }</p>
|
||||
<label>To</label>
|
||||
<p>{ address }</p>
|
||||
<label>Fee</label>
|
||||
<p>{ selectedFeeLevel.label }</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Confirmation;
|
@ -1,17 +0,0 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
const Confirmation = (props): any => {
|
||||
const { onConfirmation, onConfirmationCancel } = props.modalActions;
|
||||
return (
|
||||
<div className="confirmation">
|
||||
<h3>Confirm</h3>
|
||||
<button onClick={ onConfirmation }>Export</button>
|
||||
<button onClick={ onConfirmationCancel }>Cancel</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Confirmation;
|
@ -0,0 +1,21 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
const RememberDevice = (props: any): any => {
|
||||
const { device } = props.modal;
|
||||
const { onCancel, onDuplicateDevice } = props.modalActions;
|
||||
return (
|
||||
<div className="pin">
|
||||
<h3>Duplicate { device.label } ?</h3>
|
||||
|
||||
<label>Device label</label>
|
||||
<input type="text" />
|
||||
<button onClick={ onCancel }>Cancel</button>
|
||||
<button onClick={ event => onDuplicateDevice( { ...device, instanceLabel: "kokot" } ) }>Duplicate</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default RememberDevice;
|
@ -1,95 +0,0 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React from 'react';
|
||||
import { formatAmount, formatTime } from '../../utils/formatUtils';
|
||||
|
||||
const FeeSelection = (props): any => {
|
||||
|
||||
const {
|
||||
onChangeAccount,
|
||||
onCustomFeeOpen,
|
||||
onCustomFeeChange,
|
||||
onFeeSelect
|
||||
} = props.modalActions;
|
||||
|
||||
const {
|
||||
feeList,
|
||||
coinInfo,
|
||||
customFeeOpened,
|
||||
customFee
|
||||
} = props.modal;
|
||||
|
||||
|
||||
const feesCollection = feeList.map((feeItem, index) => {
|
||||
// skip custom
|
||||
if (feeItem.name === 'custom') return null;
|
||||
let feeName;
|
||||
if (feeItem.name === 'normal' && feeItem.bytes > 0) {
|
||||
feeName = (
|
||||
<div>
|
||||
<span className="fee-name-normal">{ feeItem.name }</span>
|
||||
<span className="fee-name-subtitle">recommended</span>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
feeName = (<span className="fee-name">{ feeItem.name }</span>);
|
||||
}
|
||||
|
||||
let feeButton: string;
|
||||
|
||||
if (feeItem.fee > 0) {
|
||||
return (
|
||||
<div className="fee" key={ index }>
|
||||
<button onClick={ event => onFeeSelect(index) }>
|
||||
{ feeName }
|
||||
<span className="fee-size">{ formatAmount(feeItem.fee, coinInfo) }</span>
|
||||
<span className="fee-minutes">{ formatTime(feeItem.minutes) }</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="fee insufficient-funds" key={ index }>
|
||||
<button disabled>
|
||||
{ feeName }
|
||||
<span className="fee-insufficient-funds">Insufficient funds</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="select-fee">
|
||||
<h3>Select fee:</h3>
|
||||
<div className="change_account" onClick={ onChangeAccount }>
|
||||
<span>Change account</span>
|
||||
</div>
|
||||
<div className="select_fee_list">
|
||||
{ feesCollection }
|
||||
<div className="fee">
|
||||
<button className={ `fee-custom-opener ${ customFeeOpened ? 'opened' : 'untouched' }` } onClick={ onCustomFeeOpen }>
|
||||
<span className="fee-name">custom</span>
|
||||
<span className="fee-insufficient-funds"></span>
|
||||
<span className="fee-size"></span>
|
||||
<span className="fee-minutes"></span>
|
||||
</button>
|
||||
<div className={ `fee-custom ${ customFeeOpened ? '' : 'hidden' }` }>
|
||||
<div className="fee-custom-wrapper">
|
||||
<input className="text" value={ customFee } data-lpignore="true" onChange={ event => onCustomFeeChange(event.target.value) } />
|
||||
<div className="fee-custom-label">sat/B</div>
|
||||
<button className="fee-custom-button" disabled="disabled">SEND</button>
|
||||
</div>
|
||||
<div className="fee-custom-warning">
|
||||
<strong>Setting custom fee is not recommended.</strong>
|
||||
If you set too low fee, it might get stuck forever.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FeeSelection;
|
@ -1,100 +0,0 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { CSSTransition, Transition } from 'react-transition-group';
|
||||
|
||||
import { UI } from 'trezor-connect';
|
||||
|
||||
import Pin from './Pin';
|
||||
import InvalidPin from './InvalidPin';
|
||||
import Passphrase from './Passphrase';
|
||||
import Permission from './Permission';
|
||||
import Confirmation from './Confirmation';
|
||||
|
||||
import AccountSelection from './AccountSelection';
|
||||
import FeeSelection from './FeeSelection';
|
||||
|
||||
const duration = 300;
|
||||
|
||||
const defaultStyle = {
|
||||
transition: `opacity ${duration}ms ease-in-out`,
|
||||
opacity: 0,
|
||||
padding: 20,
|
||||
display: 'inline-block',
|
||||
backgroundColor: '#8787d8'
|
||||
}
|
||||
|
||||
const transitionStyles = {
|
||||
entering: { opacity: 0 },
|
||||
entered: { opacity: 1 },
|
||||
};
|
||||
|
||||
const Fade2 = ({ in: inProp }) => (
|
||||
<Transition in={inProp} timeout={duration}>
|
||||
{(state) => (
|
||||
<div style={{
|
||||
...defaultStyle,
|
||||
...transitionStyles[state]
|
||||
}}>
|
||||
I'm A fade Transition2
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
);
|
||||
|
||||
const Fade = ({ children, ...props }) => (
|
||||
<CSSTransition
|
||||
{ ...props }
|
||||
timeout={ 1000 }
|
||||
classNames="fade">
|
||||
{ children }
|
||||
</CSSTransition>
|
||||
);
|
||||
|
||||
export default class Modal extends Component {
|
||||
render() {
|
||||
const { opened, windowType } = this.props.modal;
|
||||
|
||||
let component = null;
|
||||
switch(windowType) {
|
||||
case UI.REQUEST_PIN :
|
||||
component = (<Pin { ...this.props } />);
|
||||
break;
|
||||
case UI.INVALID_PIN :
|
||||
component = (<InvalidPin />);
|
||||
break;
|
||||
case UI.REQUEST_PASSPHRASE :
|
||||
component = (<Passphrase { ...this.props } />);
|
||||
break;
|
||||
case UI.REQUEST_PERMISSION :
|
||||
component = (<Permission { ...this.props } />);
|
||||
break;
|
||||
case UI.REQUEST_CONFIRMATION :
|
||||
component = (<Confirmation { ...this.props } />);
|
||||
break;
|
||||
|
||||
case UI.SELECT_ACCOUNT :
|
||||
component = (<AccountSelection { ...this.props } />);
|
||||
break;
|
||||
case UI.SELECT_FEE :
|
||||
component = (<FeeSelection { ...this.props } />);
|
||||
break;
|
||||
}
|
||||
|
||||
let ch = null;
|
||||
if (opened) {
|
||||
ch = (
|
||||
<Fade key="1">
|
||||
<div className="modal-container">
|
||||
<div className="modal-window">
|
||||
{ component }
|
||||
</div>
|
||||
</div>
|
||||
</Fade>
|
||||
);
|
||||
}
|
||||
|
||||
return ch;
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import { CSSTransition, Transition } from 'react-transition-group';
|
||||
|
||||
import { UI } from 'trezor-connect';
|
||||
|
||||
import * as ModalActions from '../../actions/ModalActions';
|
||||
import * as ReceiveActions from '../../actions/ReceiveActions';
|
||||
|
||||
import Pin from './Pin';
|
||||
import InvalidPin from './InvalidPin';
|
||||
import Passphrase from './Passphrase';
|
||||
import ConfirmSignTx from './ConfirmSignTx';
|
||||
import ConfirmAddress, { ConfirmUnverifiedAddress } from './ConfirmAddress';
|
||||
import RememberDevice, { ForgetDevice, DisconnectDevice } from './RememberDevice';
|
||||
import DuplicateDevice from './DuplicateDevice';
|
||||
|
||||
import * as RECEIVE from '../../actions/constants/receive';
|
||||
import * as MODAL from '../../actions/constants/Modal';
|
||||
import * as CONNECT from '../../actions/constants/TrezorConnect';
|
||||
|
||||
const duration = 300;
|
||||
|
||||
|
||||
const Fade = ({ children, ...props }) => (
|
||||
<CSSTransition
|
||||
{ ...props }
|
||||
timeout={ 1000 }
|
||||
classNames="fade">
|
||||
{ children }
|
||||
</CSSTransition>
|
||||
);
|
||||
|
||||
class Modal extends Component {
|
||||
render() {
|
||||
const { opened, windowType } = this.props.modal;
|
||||
|
||||
let component = null;
|
||||
switch (windowType) {
|
||||
case UI.REQUEST_PIN :
|
||||
component = (<Pin { ...this.props } />);
|
||||
break;
|
||||
case UI.INVALID_PIN :
|
||||
component = (<InvalidPin { ...this.props } />);
|
||||
break;
|
||||
case UI.REQUEST_PASSPHRASE :
|
||||
component = (<Passphrase { ...this.props } />);
|
||||
break;
|
||||
case "ButtonRequest_SignTx" :
|
||||
component = (<ConfirmSignTx { ...this.props } />)
|
||||
break;
|
||||
case "ButtonRequest_Address" :
|
||||
component = (<ConfirmAddress { ...this.props } />)
|
||||
break;
|
||||
case RECEIVE.REQUEST_UNVERIFIED :
|
||||
component = (<ConfirmUnverifiedAddress { ...this.props } />)
|
||||
break;
|
||||
|
||||
case CONNECT.REMEMBER_REQUEST :
|
||||
component = (<RememberDevice { ...this.props } />)
|
||||
break;
|
||||
|
||||
case CONNECT.FORGET_REQUEST :
|
||||
component = (<ForgetDevice { ...this.props } />)
|
||||
break;
|
||||
|
||||
case CONNECT.DISCONNECT_REQUEST :
|
||||
component = (<DisconnectDevice { ...this.props } />)
|
||||
break;
|
||||
|
||||
case CONNECT.TRY_TO_DUPLICATE :
|
||||
component = (<DuplicateDevice { ...this.props } />)
|
||||
break;
|
||||
}
|
||||
|
||||
let ch = null;
|
||||
if (opened) {
|
||||
ch = (
|
||||
<Fade key="1">
|
||||
<div className="modal-container">
|
||||
<div className="modal-window">
|
||||
{ component }
|
||||
</div>
|
||||
</div>
|
||||
</Fade>
|
||||
);
|
||||
}
|
||||
|
||||
return ch;
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: any, own: any): any => {
|
||||
return {
|
||||
modal: state.modal,
|
||||
accounts: state.accounts,
|
||||
devices: state.connect.devices,
|
||||
sendForm: state.sendForm,
|
||||
receive: state.receive,
|
||||
};
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch: any): any => {
|
||||
return {
|
||||
modalActions: bindActionCreators(ModalActions, dispatch),
|
||||
receiveActions: bindActionCreators(ReceiveActions, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
// export default connect(mapStateToProps, mapDispatchToProps)(Modal);
|
||||
export default withRouter(
|
||||
connect(mapStateToProps, mapDispatchToProps)(Modal)
|
||||
);
|
@ -1,94 +1,308 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
|
||||
import React, { Component, KeyboardEvent, FocusEvent } from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import raf from 'raf';
|
||||
|
||||
type State = {
|
||||
singleInput: boolean;
|
||||
passphrase: string;
|
||||
passphraseRevision: string;
|
||||
passphraseFocused: boolean;
|
||||
passphraseRevisionFocused: boolean;
|
||||
passphraseRevisionTouched: boolean;
|
||||
match: boolean;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
export default class PinModal extends Component {
|
||||
|
||||
input: HTMLInputElement;
|
||||
state: State;
|
||||
passphraseInput: HTMLInputElement;
|
||||
passphraseRevisionInput: HTMLInputElement;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
console.warn("PROPZ", props)
|
||||
const isSavedDevice = props.devices.find(d => d.path === props.modal.device.path && d.remember);
|
||||
|
||||
this.state = {
|
||||
singleInput: isSavedDevice ? true : false,
|
||||
passphrase: '',
|
||||
passphraseRevision: '',
|
||||
passphraseFocused: false,
|
||||
passphraseRevisionFocused: false,
|
||||
passphraseRevisionTouched: false,
|
||||
match: true,
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
|
||||
keyboardHandler(event: KeyboardEvent): void {
|
||||
|
||||
|
||||
if (event.keyCode === 13) {
|
||||
event.preventDefault();
|
||||
//this.passphraseInput.blur();
|
||||
//this.passphraseRevisionInput.blur();
|
||||
|
||||
//this.passphraseInput.type = 'text';
|
||||
//this.passphraseRevisionInput.type = 'text';
|
||||
|
||||
this.submit();
|
||||
|
||||
// TODO: set timeout, or wait for blur event
|
||||
//onPassphraseSubmit(passphrase, passphraseCached);
|
||||
//raf(() => onPassphraseSubmit(passphrase));
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
// one time autofocus
|
||||
this.input.focus();
|
||||
this.passphraseInput.focus();
|
||||
this.keyboardHandler = this.keyboardHandler.bind(this);
|
||||
window.addEventListener('keydown', this.keyboardHandler, false);
|
||||
|
||||
|
||||
|
||||
// document.oncontextmenu = (event) => {
|
||||
// const el = window.event.srcElement || event.target;
|
||||
// const type = el.tagName.toLowerCase() || '';
|
||||
// if (type === 'input') {
|
||||
// return false;
|
||||
// }
|
||||
// };
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
window.removeEventListener('keydown', this.keyboardHandler, false);
|
||||
// this.passphraseInput.type = 'text';
|
||||
// this.passphraseInput.style.display = 'none';
|
||||
// this.passphraseRevisionInput.type = 'text';
|
||||
// this.passphraseRevisionInput.style.display = 'none';
|
||||
}
|
||||
|
||||
keyboardHandler(event: KeyboardEvent): void {
|
||||
const { onPassphraseSubmit } = this.props;
|
||||
const { passphrase, passphraseCached } = this.props.modal;
|
||||
|
||||
if (event.keyCode === 13) {
|
||||
event.preventDefault();
|
||||
this.input.blur();
|
||||
onPassphraseSubmit(passphrase, passphraseCached);
|
||||
}
|
||||
}
|
||||
|
||||
// we don't want to keep password inside "value" attribute,
|
||||
// so we need to replace it thru javascript
|
||||
componentDidUpdate() {
|
||||
const { passphrase, passphraseFocused, passphraseVisible } = this.props.modal;
|
||||
let inputValue: string = passphrase;
|
||||
if (!passphraseVisible && !passphraseFocused) {
|
||||
inputValue = passphrase.replace(/./g, '•');
|
||||
const {
|
||||
passphrase,
|
||||
passphraseRevision,
|
||||
passphraseFocused,
|
||||
passphraseRevisionFocused,
|
||||
visible
|
||||
} = this.state;
|
||||
// } = this.props.modal;
|
||||
|
||||
let passphraseInputValue: string = passphrase;
|
||||
let passphraseRevisionInputValue: string = passphraseRevision;
|
||||
if (!visible && !passphraseFocused) {
|
||||
passphraseInputValue = passphrase.replace(/./g, '•');
|
||||
}
|
||||
if (!visible && !passphraseRevisionFocused) {
|
||||
passphraseRevisionInputValue = passphraseRevision.replace(/./g, '•');
|
||||
}
|
||||
|
||||
this.passphraseInput.value = passphraseInputValue;
|
||||
this.passphraseInput.setAttribute("type", visible ? "text" : "password");
|
||||
|
||||
if (this.passphraseRevisionInput) {
|
||||
this.passphraseRevisionInput.value = passphraseRevisionInputValue;
|
||||
this.passphraseRevisionInput.setAttribute("type", visible ? "text" : "password");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onPassphraseChange = (input: string, value: string): void => {
|
||||
// https://codepen.io/MiDri/pen/PGqvrO
|
||||
// or
|
||||
// https://github.com/zakangelle/react-password-mask/blob/master/src/index.js
|
||||
if (input === 'passphrase') {
|
||||
this.setState({
|
||||
match: this.state.singleInput || this.state.passphraseRevision === value,
|
||||
passphrase: value
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
match: this.state.passphrase === value,
|
||||
passphraseRevision: value,
|
||||
passphraseRevisionTouched: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onPassphraseFocus = (input: string): void => {
|
||||
if (input === 'passphrase') {
|
||||
this.setState({
|
||||
passphraseFocused: true
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
passphraseRevisionFocused: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onPassphraseBlur = (input: string): void => {
|
||||
if (input === 'passphrase') {
|
||||
this.setState({
|
||||
passphraseFocused: false
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
passphraseRevisionFocused: false
|
||||
});
|
||||
}
|
||||
this.input.value = inputValue;
|
||||
}
|
||||
|
||||
render(): void {
|
||||
onPassphraseShow = (): void => {
|
||||
this.setState({
|
||||
visible: true
|
||||
});
|
||||
}
|
||||
|
||||
onPassphraseHide = (): void => {
|
||||
this.setState({
|
||||
visible: false
|
||||
});
|
||||
}
|
||||
|
||||
submit = (empty: boolean = false): void => {
|
||||
const { onPassphraseSubmit } = this.props.modalActions;
|
||||
const { passphrase } = this.state;
|
||||
|
||||
//this.passphraseInput.type = 'text';
|
||||
// this.passphraseInput.style.display = 'none';
|
||||
//this.passphraseInput.setAttribute('readonly', 'readonly');
|
||||
// this.passphraseRevisionInput.type = 'text';
|
||||
//this.passphraseRevisionInput.style.display = 'none';
|
||||
//this.passphraseRevisionInput.setAttribute('readonly', 'readonly');
|
||||
|
||||
const p = passphrase;
|
||||
|
||||
this.setState({
|
||||
passphrase: '',
|
||||
passphraseRevision: '',
|
||||
passphraseFocused: false,
|
||||
passphraseRevisionFocused: false,
|
||||
visible: false
|
||||
})
|
||||
|
||||
raf(() => onPassphraseSubmit(empty ? '' : passphrase));
|
||||
}
|
||||
|
||||
render(): any {
|
||||
|
||||
const {
|
||||
onPassphraseChange,
|
||||
onPassphraseSubmit,
|
||||
onPassphraseForget,
|
||||
onPassphraseFocus,
|
||||
onPassphraseBlur,
|
||||
onPassphraseSave,
|
||||
onPassphraseShow,
|
||||
onPassphraseHide
|
||||
//onPassphraseChange,
|
||||
//onPassphraseSubmit,
|
||||
//onPassphraseSubmitEmpty,
|
||||
//onPassphraseForget,
|
||||
//onPassphraseFocus,
|
||||
//onPassphraseBlur,
|
||||
//onPassphraseSave,
|
||||
//onPassphraseShow,
|
||||
//onPassphraseHide
|
||||
} = this.props.modalActions;
|
||||
const { passphrase, passphraseFocused, passphraseVisible, passphraseCached } = this.props.modal;
|
||||
|
||||
let inputType: string = passphraseVisible || (!passphraseVisible && !passphraseFocused) ? "text" : "password";
|
||||
const showPassphraseCheckboxFn: Function = passphraseVisible ? onPassphraseHide : onPassphraseShow;
|
||||
const savePassphraseCheckboxFn: Function = passphraseCached ? onPassphraseForget : onPassphraseSave;
|
||||
const {
|
||||
device,
|
||||
//passphrase,
|
||||
//passphraseRevision,
|
||||
//passphraseFocused,
|
||||
//passphraseRevisionFocused,
|
||||
//passphraseVisible,
|
||||
//passphraseMatch,
|
||||
//passphraseRevisionTouched,
|
||||
passphraseCached
|
||||
} = this.props.modal;
|
||||
|
||||
const {
|
||||
singleInput,
|
||||
passphrase,
|
||||
passphraseRevision,
|
||||
passphraseFocused,
|
||||
passphraseRevisionFocused,
|
||||
visible,
|
||||
match,
|
||||
passphraseRevisionTouched,
|
||||
} = this.state;
|
||||
|
||||
let passphraseInputType: string = visible || (!visible && !passphraseFocused) ? "text" : "password";
|
||||
let passphraseRevisionInputType: string = visible || (!visible && !passphraseRevisionFocused) ? "text" : "password";
|
||||
passphraseInputType = passphraseRevisionInputType = "text";
|
||||
//let passphraseInputType: string = visible || passphraseFocused ? "text" : "password";
|
||||
//let passphraseRevisionInputType: string = visible || passphraseRevisionFocused ? "text" : "password";
|
||||
|
||||
|
||||
const showPassphraseCheckboxFn: Function = visible ? this.onPassphraseHide : this.onPassphraseShow;
|
||||
|
||||
return (
|
||||
<div className="passphrase">
|
||||
<h3>Please enter your passphrase.</h3>
|
||||
<h4>Note that passphrase is case-sensitive.</h4>
|
||||
<div>
|
||||
{/* <button className="close-modal transparent" onClick={ event => this.submit(true) }></button> */}
|
||||
<h3>Enter { device.label } passphrase</h3>
|
||||
<p>Note that passphrase is case-sensitive.</p>
|
||||
<div className="row">
|
||||
<label>Passphrase</label>
|
||||
<input
|
||||
ref={ (element) => { this.input = element; } }
|
||||
onChange={ event => onPassphraseChange(event.currentTarget.value) }
|
||||
type={ inputType }
|
||||
ref={ (element) => { this.passphraseInput = element; } }
|
||||
onChange={ event => this.onPassphraseChange('passphrase', event.currentTarget.value) }
|
||||
type={ passphraseInputType }
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
data-lpignore="true"
|
||||
onFocus={ onPassphraseFocus }
|
||||
onBlur={ onPassphraseBlur }
|
||||
onFocus={ event => this.onPassphraseFocus('passphrase') }
|
||||
onBlur={ event => this.onPassphraseBlur('passphrase') }
|
||||
|
||||
tabIndex="1" />
|
||||
</div>
|
||||
<div className="passphrase_options">
|
||||
<label>
|
||||
<input type="checkbox" className="show_passphrase" tabIndex="2" onChange={ showPassphraseCheckboxFn } checked={ passphraseVisible } />
|
||||
<span>Show passphrase</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="checkbox" className="save_passphrase" tabIndex="3" onChange={ savePassphraseCheckboxFn } checked={ passphraseCached } />
|
||||
<span>Save passphrase for current session *</span>
|
||||
{ singleInput ? null : (
|
||||
<div className="row">
|
||||
<label>Re-enter passphrase</label>
|
||||
<input
|
||||
ref={ (element) => { this.passphraseRevisionInput = element; } }
|
||||
onChange={ event => this.onPassphraseChange('revision', event.currentTarget.value) }
|
||||
type={ passphraseRevisionInputType }
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
data-lpignore="true"
|
||||
onFocus={ event => this.onPassphraseFocus('revision') }
|
||||
onBlur={ event => this.onPassphraseBlur('revision') }
|
||||
|
||||
tabIndex="2" />
|
||||
{ !match && passphraseRevisionTouched ? <span className="error">Passphrases do not match</span> : null }
|
||||
</div>
|
||||
) }
|
||||
|
||||
|
||||
<div className="row">
|
||||
<label className="custom-checkbox">
|
||||
<input type="checkbox" tabIndex="3" onChange={ showPassphraseCheckboxFn } checked={ visible } />
|
||||
<span className="indicator"></span>
|
||||
Show passphrase
|
||||
</label>
|
||||
{/* <label className="custom-checkbox">
|
||||
<input type="checkbox" className="save_passphrase" tabIndex="4" onChange={ savePassphraseCheckboxFn } checked={ passphraseCached } />
|
||||
<span className="indicator"></span>
|
||||
<span>Save passphrase for current session (i)</span>
|
||||
</label> */}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="button" className="submit" tabIndex="4" disabled={ !match } onClick={ event => this.submit() }>Enter</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="button" className="submit" tabIndex="4" onClick={ event => onPassphraseSubmit(passphrase, passphraseCached) }>Enter</button>
|
||||
<p>If you want to access your default account</p>
|
||||
<p><a className="green" onClick={ event => this.submit(true) }>Leave passphrase blank</a></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|