Update v1 Wallet Dapp (#6935)
* Start removing duplicated functionality (v1 inside v2) * Update compilation targets * Update locks * Fix js-old build * Update with removed extra references * Adapt dev.{parity,web3}.html for extra debug info * Update dependencies * Remove Tooltips * Update dependencies * Only inject window.ethereum once * Fix versions to 2.0.x for @parity libraries * Update to @parity/api 2.1.x * Update for @parity/api 2.1.x * Freeze signer plugin dependency hashes * Fix lint * Move local account handling from API * Update for 2.2.x @parity/{shared,ui} * Update API references for middleware * Install updated dependencies * Update for build * Always do local builds for development * Remove unused hasAccounts property * Fix Windows build for js-old * Adjust inclusing rules to be Windows friendly * Explicitly add --config option to webpack * Add process.env.EMBED flag for Windows compatability * Revert embed flag * Fix build * Merge changes from beta * Update packages after merge * Update Accounts from beta * Update with beta * Remove upgrade check * Fix CI build script execution * Make rm -rf commands cross-platform * Remove ability to deploy wallets (only watch) * Update path references for js-old (Windows) * Render local dapps first * Cleanup dependencies
This commit is contained in:
parent
bcdfc50a0b
commit
ce1609726f
5325
js-old/package-lock.json
generated
5325
js-old/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "parity.js",
|
"name": "@parity/dapp-v1",
|
||||||
"version": "1.8.18",
|
"version": "1.9.99",
|
||||||
"main": "release/index.js",
|
"main": "release/index.js",
|
||||||
"jsnext:main": "src/index.js",
|
"jsnext:main": "src/index.js",
|
||||||
"author": "Parity Team <admin@parity.io>",
|
"author": "Parity Team <admin@parity.io>",
|
||||||
@ -27,42 +27,17 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install": "napa",
|
"install": "napa",
|
||||||
"analize": "npm run analize:lib && npm run analize:dll && npm run analize:app",
|
"build": "npm run build:lib && npm run build:dll && npm run build:app",
|
||||||
"analize:app": "WPANALIZE=1 webpack --config webpack/app --json > .build/analize.app.json && cat .build/analize.app.json | webpack-bundle-size-analyzer",
|
|
||||||
"analize:lib": "WPANALIZE=1 webpack --config webpack/libraries --json > .build/analize.lib.json && cat .build/analize.lib.json | webpack-bundle-size-analyzer",
|
|
||||||
"analize:dll": "WPANALIZE=1 webpack --config webpack/vendor --json > .build/analize.dll.json && cat .build/analize.dll.json | webpack-bundle-size-analyzer",
|
|
||||||
"build": "npm run build:lib && npm run build:dll && npm run build:app && npm run build:embed",
|
|
||||||
"build:app": "webpack --config webpack/app",
|
"build:app": "webpack --config webpack/app",
|
||||||
"build:lib": "webpack --config webpack/libraries",
|
"build:lib": "webpack --config webpack/libraries",
|
||||||
"build:dll": "webpack --config webpack/vendor",
|
"build:dll": "webpack --config webpack/vendor",
|
||||||
"build:markdown": "babel-node ./scripts/build-rpc-markdown.js",
|
"ci:build": "cross-env NODE_ENV=production npm run build",
|
||||||
"build:json": "babel-node ./scripts/build-rpc-json.js",
|
"clean": "rimraf ./.build ./.coverage ./.happypack ./.npmjs ./build ./node_modules/.cache",
|
||||||
"build:embed": "EMBED=1 node webpack/embed",
|
|
||||||
"build:i18n": "npm run clean && npm run build && babel-node ./scripts/build-i18n.js",
|
|
||||||
"ci:build": "npm run ci:build:lib && npm run ci:build:dll && npm run ci:build:app && npm run ci:build:embed",
|
|
||||||
"ci:build:app": "NODE_ENV=production webpack --config webpack/app",
|
|
||||||
"ci:build:lib": "NODE_ENV=production webpack --config webpack/libraries",
|
|
||||||
"ci:build:dll": "NODE_ENV=production webpack --config webpack/vendor",
|
|
||||||
"ci:build:npm": "NODE_ENV=production webpack --config webpack/npm",
|
|
||||||
"ci:build:jsonrpc": "babel-node ./scripts/build-rpc-json.js --output .npmjs/jsonrpc",
|
|
||||||
"ci:build:embed": "NODE_ENV=production EMBED=1 node webpack/embed",
|
|
||||||
"start": "npm run clean && npm install && npm run build:lib && npm run build:dll && npm run start:app",
|
|
||||||
"start:app": "node webpack/dev.server",
|
|
||||||
"clean": "rm -rf ./.build ./.coverage ./.happypack ./.npmjs ./build ./node_modules/.cache ./node_modules/@parity",
|
|
||||||
"coveralls": "npm run testCoverage && coveralls < coverage/lcov.info",
|
|
||||||
"lint": "npm run lint:css && npm run lint:js",
|
"lint": "npm run lint:css && npm run lint:js",
|
||||||
"lint:cached": "npm run lint:css && npm run lint:js:cached",
|
|
||||||
"lint:css": "stylelint ./src/**/*.css",
|
"lint:css": "stylelint ./src/**/*.css",
|
||||||
"lint:fix": "npm run lint:js:fix",
|
|
||||||
"lint:i18n": "babel-node ./scripts/lint-i18n.js",
|
|
||||||
"lint:js": "eslint --ignore-path .gitignore ./src/",
|
"lint:js": "eslint --ignore-path .gitignore ./src/",
|
||||||
"lint:js:cached": "eslint --cache --ignore-path .gitignore ./src/",
|
|
||||||
"lint:js:fix": "eslint --fix --ignore-path .gitignore ./src/",
|
|
||||||
"test": "NODE_ENV=test mocha --compilers ejs:ejsify 'src/**/*.spec.js'",
|
"test": "NODE_ENV=test mocha --compilers ejs:ejsify 'src/**/*.spec.js'",
|
||||||
"test:coverage": "NODE_ENV=test istanbul cover _mocha -- --compilers ejs:ejsify 'src/**/*.spec.js'",
|
"watch": "webpack --watch --config webpack/app"
|
||||||
"test:e2e": "NODE_ENV=test mocha 'src/**/*.e2e.js'",
|
|
||||||
"test:npm": "(cd .npmjs && npm i) && node test/npmParity && node test/npmJsonRpc && (rm -rf .npmjs/node_modules)",
|
|
||||||
"prepush": "npm run lint:cached"
|
|
||||||
},
|
},
|
||||||
"napa": {
|
"napa": {
|
||||||
"qrcode-generator": "kazuhikoarase/qrcode-generator"
|
"qrcode-generator": "kazuhikoarase/qrcode-generator"
|
||||||
@ -98,6 +73,7 @@
|
|||||||
"copy-webpack-plugin": "4.0.1",
|
"copy-webpack-plugin": "4.0.1",
|
||||||
"core-js": "2.4.1",
|
"core-js": "2.4.1",
|
||||||
"coveralls": "2.11.16",
|
"coveralls": "2.11.16",
|
||||||
|
"cross-env": "5.1.1",
|
||||||
"css-loader": "0.26.1",
|
"css-loader": "0.26.1",
|
||||||
"ejs-loader": "0.3.0",
|
"ejs-loader": "0.3.0",
|
||||||
"ejsify": "1.0.0",
|
"ejsify": "1.0.0",
|
||||||
@ -118,9 +94,7 @@
|
|||||||
"html-loader": "0.4.4",
|
"html-loader": "0.4.4",
|
||||||
"html-webpack-plugin": "2.28.0",
|
"html-webpack-plugin": "2.28.0",
|
||||||
"http-proxy-middleware": "0.17.3",
|
"http-proxy-middleware": "0.17.3",
|
||||||
"husky": "0.13.1",
|
|
||||||
"ignore-styles": "5.0.1",
|
"ignore-styles": "5.0.1",
|
||||||
"image-webpack-loader": "3.2.0",
|
|
||||||
"istanbul": "1.0.0-alpha.2",
|
"istanbul": "1.0.0-alpha.2",
|
||||||
"jsdom": "9.11.0",
|
"jsdom": "9.11.0",
|
||||||
"json-loader": "0.5.4",
|
"json-loader": "0.5.4",
|
||||||
@ -140,6 +114,7 @@
|
|||||||
"react-addons-test-utils": "15.4.2",
|
"react-addons-test-utils": "15.4.2",
|
||||||
"react-hot-loader": "3.0.0-beta.6",
|
"react-hot-loader": "3.0.0-beta.6",
|
||||||
"react-intl-aggregate-webpack-plugin": "0.0.1",
|
"react-intl-aggregate-webpack-plugin": "0.0.1",
|
||||||
|
"rimraf": "2.6.2",
|
||||||
"rucksack-css": "0.9.1",
|
"rucksack-css": "0.9.1",
|
||||||
"script-ext-html-webpack-plugin": "1.7.1",
|
"script-ext-html-webpack-plugin": "1.7.1",
|
||||||
"serviceworker-webpack-plugin": "0.2.0",
|
"serviceworker-webpack-plugin": "0.2.0",
|
||||||
@ -160,9 +135,8 @@
|
|||||||
"yargs": "6.6.0"
|
"yargs": "6.6.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@parity/wordlist": "1.0.1",
|
"@parity/api": "2.1.x",
|
||||||
"arraybuffer-loader": "0.2.2",
|
"@parity/wordlist": "1.1.x",
|
||||||
"babel-runtime": "6.23.0",
|
|
||||||
"base32.js": "0.1.0",
|
"base32.js": "0.1.0",
|
||||||
"bignumber.js": "3.0.1",
|
"bignumber.js": "3.0.1",
|
||||||
"blockies": "0.0.2",
|
"blockies": "0.0.2",
|
||||||
@ -234,7 +208,6 @@
|
|||||||
"web3": "0.17.0-beta",
|
"web3": "0.17.0-beta",
|
||||||
"whatwg-fetch": "2.0.1",
|
"whatwg-fetch": "2.0.1",
|
||||||
"worker-loader": "^0.8.0",
|
"worker-loader": "^0.8.0",
|
||||||
"yarn": "^0.21.3",
|
|
||||||
"zxcvbn": "4.4.1"
|
"zxcvbn": "4.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
<div id="container">
|
<div id="container">
|
||||||
<div class="loading">Loading</div>
|
<div class="loading">Loading</div>
|
||||||
</div>
|
</div>
|
||||||
|
<script src="/parity-utils/inject.js"></script>
|
||||||
<script src="vendor.js"></script>
|
<script src="vendor.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -25,9 +25,9 @@ import { AppContainer } from 'react-hot-loader';
|
|||||||
|
|
||||||
import injectTapEventPlugin from 'react-tap-event-plugin';
|
import injectTapEventPlugin from 'react-tap-event-plugin';
|
||||||
import { hashHistory } from 'react-router';
|
import { hashHistory } from 'react-router';
|
||||||
import qs from 'querystring';
|
|
||||||
|
|
||||||
import SecureApi from './secureApi';
|
import Api from '@parity/api';
|
||||||
|
|
||||||
import ContractInstances from '~/contracts';
|
import ContractInstances from '~/contracts';
|
||||||
|
|
||||||
import { initStore } from './redux';
|
import { initStore } from './redux';
|
||||||
@ -45,23 +45,7 @@ import '../assets/fonts/RobotoMono/font.css';
|
|||||||
|
|
||||||
injectTapEventPlugin();
|
injectTapEventPlugin();
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
const api = new Api(window.ethereum);
|
||||||
// Expose the React Performance Tools on the`window` object
|
|
||||||
const Perf = require('react-addons-perf');
|
|
||||||
|
|
||||||
window.Perf = Perf;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AUTH_HASH = '#/auth?';
|
|
||||||
|
|
||||||
let token = null;
|
|
||||||
|
|
||||||
if (window.location.hash && window.location.hash.indexOf(AUTH_HASH) === 0) {
|
|
||||||
token = qs.parse(window.location.hash.substr(AUTH_HASH.length)).token;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uiUrl = window.location.host;
|
|
||||||
const api = new SecureApi(uiUrl, token);
|
|
||||||
|
|
||||||
patchApi(api);
|
patchApi(api);
|
||||||
loadSender(api);
|
loadSender(api);
|
||||||
@ -72,8 +56,6 @@ const store = initStore(api, hashHistory);
|
|||||||
store.dispatch({ type: 'initAll', api });
|
store.dispatch({ type: 'initAll', api });
|
||||||
store.dispatch(setApi(api));
|
store.dispatch(setApi(api));
|
||||||
|
|
||||||
window.secureApi = api;
|
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<AppContainer>
|
<AppContainer>
|
||||||
<ContextProvider api={ api } muiTheme={ muiTheme } store={ store }>
|
<ContextProvider api={ api } muiTheme={ muiTheme } store={ store }>
|
||||||
|
@ -40,10 +40,10 @@ impl WebApp for App {
|
|||||||
|
|
||||||
fn info(&self) -> Info {
|
fn info(&self) -> Info {
|
||||||
Info {
|
Info {
|
||||||
name: "Parity Wallet v1",
|
name: "Parity Wallet",
|
||||||
version: env!("CARGO_PKG_VERSION"),
|
version: env!("CARGO_PKG_VERSION"),
|
||||||
author: "Parity <admin@parity.io>",
|
author: "Parity <admin@parity.io>",
|
||||||
description: "Deprecated version of Parity Wallet.",
|
description: "Parity Wallet and Account management tools",
|
||||||
icon_url: "icon.png",
|
icon_url: "icon.png",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
js-old/src/manifest.json
Normal file
7
js-old/src/manifest.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "Parity Wallet",
|
||||||
|
"version": "development",
|
||||||
|
"author": "Parity <admin@parity.io>",
|
||||||
|
"description": "Parity Wallet and Account management tools",
|
||||||
|
"icon_url": "icon.png",
|
||||||
|
}
|
@ -17,35 +17,35 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { walletSourceURL } from '~/contracts/code/wallet';
|
// import { walletSourceURL } from '~/contracts/code/wallet';
|
||||||
import { RadioButtons } from '~/ui';
|
import { RadioButtons } from '~/ui';
|
||||||
|
|
||||||
const TYPES = [
|
const TYPES = [
|
||||||
{
|
// {
|
||||||
label: (
|
// label: (
|
||||||
<FormattedMessage
|
// <FormattedMessage
|
||||||
id='createWallet.type.multisig.label'
|
// id='createWallet.type.multisig.label'
|
||||||
defaultMessage='Multi-Sig wallet'
|
// defaultMessage='Multi-Sig wallet'
|
||||||
/>
|
// />
|
||||||
),
|
// ),
|
||||||
key: 'MULTISIG',
|
// key: 'MULTISIG',
|
||||||
description: (
|
// description: (
|
||||||
<FormattedMessage
|
// <FormattedMessage
|
||||||
id='createWallet.type.multisig.description'
|
// id='createWallet.type.multisig.description'
|
||||||
defaultMessage='Create/Deploy a {link} Wallet'
|
// defaultMessage='Create/Deploy a {link} Wallet'
|
||||||
values={ {
|
// values={ {
|
||||||
link: (
|
// link: (
|
||||||
<a href={ walletSourceURL } target='_blank'>
|
// <a href={ walletSourceURL } target='_blank'>
|
||||||
<FormattedMessage
|
// <FormattedMessage
|
||||||
id='createWallet.type.multisig.link'
|
// id='createWallet.type.multisig.link'
|
||||||
defaultMessage='standard multi-signature'
|
// defaultMessage='standard multi-signature'
|
||||||
/>
|
// />
|
||||||
</a>
|
// </a>
|
||||||
)
|
// )
|
||||||
} }
|
// } }
|
||||||
/>
|
// />
|
||||||
)
|
// )
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
label: (
|
label: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
@ -57,7 +57,7 @@ const TYPES = [
|
|||||||
description: (
|
description: (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='createWallet.type.watch.description'
|
id='createWallet.type.watch.description'
|
||||||
defaultMessage='Add an existing wallet to your accounts'
|
defaultMessage='Add an existing multisig wallet to your accounts'
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ const STEPS = {
|
|||||||
export default class CreateWalletStore {
|
export default class CreateWalletStore {
|
||||||
@observable step = null;
|
@observable step = null;
|
||||||
@observable txhash = null;
|
@observable txhash = null;
|
||||||
@observable walletType = 'MULTISIG';
|
@observable walletType = 'WATCH'; // 'MULTISIG';
|
||||||
|
|
||||||
@observable wallet = {
|
@observable wallet = {
|
||||||
account: '',
|
account: '',
|
||||||
|
@ -133,8 +133,8 @@ export default class TransferStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action handleClose = () => {
|
@action handleClose = () => {
|
||||||
this.stage = 0;
|
|
||||||
this.onClose();
|
this.onClose();
|
||||||
|
this.stage = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action onUpdateDetails = (type, value) => {
|
@action onUpdateDetails = (type, value) => {
|
||||||
@ -169,7 +169,6 @@ export default class TransferStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action onSend = () => {
|
@action onSend = () => {
|
||||||
this.onNext();
|
|
||||||
this.sending = true;
|
this.sending = true;
|
||||||
|
|
||||||
this
|
this
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import { handleActions } from 'redux-actions';
|
import { handleActions } from 'redux-actions';
|
||||||
|
|
||||||
const initialState = {};
|
const initialState = null;
|
||||||
|
|
||||||
export default handleActions({
|
export default handleActions({
|
||||||
setApi (state, action) {
|
setApi (state, action) {
|
||||||
|
@ -16,12 +16,11 @@
|
|||||||
|
|
||||||
import { throttle } from 'lodash';
|
import { throttle } from 'lodash';
|
||||||
|
|
||||||
import { fetchBalances, fetchTokensBalances, queryTokensFilter } from './balancesActions';
|
import { LOG_KEYS, getLogger } from '~/config';
|
||||||
import { loadTokens, fetchTokens } from './tokensActions';
|
|
||||||
import { padRight } from '~/api/util/format';
|
|
||||||
|
|
||||||
import Contracts from '~/contracts';
|
import { fetchBalances, queryTokensFilter, updateTokensFilter } from './balancesActions';
|
||||||
|
|
||||||
|
const log = getLogger(LOG_KEYS.Balances);
|
||||||
let instance = null;
|
let instance = null;
|
||||||
|
|
||||||
export default class Balances {
|
export default class Balances {
|
||||||
@ -29,40 +28,20 @@ export default class Balances {
|
|||||||
this._api = api;
|
this._api = api;
|
||||||
this._store = store;
|
this._store = store;
|
||||||
|
|
||||||
this._tokenreg = null;
|
this._apiSubs = [];
|
||||||
this._tokenregSID = null;
|
|
||||||
this._tokenMetaSID = null;
|
|
||||||
|
|
||||||
this._blockNumberSID = null;
|
// Throttled `_fetchEthBalances` function
|
||||||
this._accountsInfoSID = null;
|
|
||||||
|
|
||||||
// Throtthled load tokens (no more than once
|
|
||||||
// every minute)
|
|
||||||
this.loadTokens = throttle(
|
|
||||||
this._loadTokens,
|
|
||||||
60 * 1000,
|
|
||||||
{ leading: true, trailing: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
// Throttled `_fetchBalances` function
|
|
||||||
// that gets called max once every 40s
|
// that gets called max once every 40s
|
||||||
this.longThrottledFetch = throttle(
|
this.longThrottledFetch = throttle(
|
||||||
this._fetchBalances,
|
this._fetchEthBalances,
|
||||||
40 * 1000,
|
40 * 1000,
|
||||||
{ leading: false, trailing: true }
|
{ leading: true, trailing: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
this.shortThrottledFetch = throttle(
|
this.shortThrottledFetch = throttle(
|
||||||
this._fetchBalances,
|
this._fetchEthBalances,
|
||||||
2 * 1000,
|
2 * 1000,
|
||||||
{ leading: false, trailing: true }
|
{ leading: true, trailing: false }
|
||||||
);
|
|
||||||
|
|
||||||
// Fetch all tokens every 2 minutes
|
|
||||||
this.throttledTokensFetch = throttle(
|
|
||||||
this._fetchTokens,
|
|
||||||
2 * 60 * 1000,
|
|
||||||
{ leading: false, trailing: true }
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Unsubscribe previous instance if it exists
|
// Unsubscribe previous instance if it exists
|
||||||
@ -71,17 +50,19 @@ export default class Balances {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static get (store = {}) {
|
static get (store) {
|
||||||
if (!instance && store) {
|
if (!instance && store) {
|
||||||
const { api } = store.getState();
|
return Balances.init(store);
|
||||||
|
} else if (!instance) {
|
||||||
return Balances.instantiate(store, api);
|
throw new Error('The Balances Provider has not been initialized yet');
|
||||||
}
|
}
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
static instantiate (store, api) {
|
static init (store) {
|
||||||
|
const { api } = store.getState();
|
||||||
|
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
instance = new Balances(store, api);
|
instance = new Balances(store, api);
|
||||||
}
|
}
|
||||||
@ -91,15 +72,13 @@ export default class Balances {
|
|||||||
|
|
||||||
static start () {
|
static start () {
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
return Promise.reject('BalancesProvider has not been intiated yet');
|
return Promise.reject('BalancesProvider has not been initiated yet');
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = instance;
|
const self = instance;
|
||||||
|
|
||||||
// Unsubscribe from previous subscriptions
|
// Unsubscribe from previous subscriptions
|
||||||
return Balances
|
return Balances.stop()
|
||||||
.stop()
|
|
||||||
.then(() => self.loadTokens())
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const promises = [
|
const promises = [
|
||||||
self.subscribeBlockNumber(),
|
self.subscribeBlockNumber(),
|
||||||
@ -107,7 +86,8 @@ export default class Balances {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
});
|
})
|
||||||
|
.then(() => self.fetchEthBalances());
|
||||||
}
|
}
|
||||||
|
|
||||||
static stop () {
|
static stop () {
|
||||||
@ -116,71 +96,35 @@ export default class Balances {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const self = instance;
|
const self = instance;
|
||||||
const promises = [];
|
const promises = self._apiSubs.map((subId) => self._api.unsubscribe(subId));
|
||||||
|
|
||||||
if (self._blockNumberSID) {
|
return Promise.all(promises)
|
||||||
const p = self._api
|
|
||||||
.unsubscribe(self._blockNumberSID)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
self._blockNumberSID = null;
|
self._apiSubs = [];
|
||||||
});
|
});
|
||||||
|
|
||||||
promises.push(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self._accountsInfoSID) {
|
|
||||||
const p = self._api
|
|
||||||
.unsubscribe(self._accountsInfoSID)
|
|
||||||
.then(() => {
|
|
||||||
self._accountsInfoSID = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
promises.push(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unsubscribe without adding the promises
|
|
||||||
// to the result, since it would have to wait for a
|
|
||||||
// reconnection to resolve if the Node is disconnected
|
|
||||||
if (self._tokenreg) {
|
|
||||||
if (self._tokenregSID) {
|
|
||||||
const tokenregSID = self._tokenregSID;
|
|
||||||
|
|
||||||
self._tokenreg
|
|
||||||
.unsubscribe(tokenregSID)
|
|
||||||
.then(() => {
|
|
||||||
if (self._tokenregSID === tokenregSID) {
|
|
||||||
self._tokenregSID = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self._tokenMetaSID) {
|
|
||||||
const tokenMetaSID = self._tokenMetaSID;
|
|
||||||
|
|
||||||
self._tokenreg
|
|
||||||
.unsubscribe(tokenMetaSID)
|
|
||||||
.then(() => {
|
|
||||||
if (self._tokenMetaSID === tokenMetaSID) {
|
|
||||||
self._tokenMetaSID = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(promises);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribeAccountsInfo () {
|
subscribeAccountsInfo () {
|
||||||
|
// Don't trigger the balances updates on first call (when the
|
||||||
|
// subscriptions are setup)
|
||||||
|
let firstcall = true;
|
||||||
|
|
||||||
return this._api
|
return this._api
|
||||||
.subscribe('parity_allAccountsInfo', (error, accountsInfo) => {
|
.subscribe('parity_allAccountsInfo', (error, accountsInfo) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
|
return console.warn('balances::subscribeAccountsInfo', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstcall) {
|
||||||
|
firstcall = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fetchAllBalances();
|
this._store.dispatch(updateTokensFilter());
|
||||||
|
this.fetchEthBalances();
|
||||||
})
|
})
|
||||||
.then((accountsInfoSID) => {
|
.then((subId) => {
|
||||||
this._accountsInfoSID = accountsInfoSID;
|
this._apiSubs.push(subId);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.warn('_subscribeAccountsInfo', error);
|
console.warn('_subscribeAccountsInfo', error);
|
||||||
@ -188,161 +132,57 @@ export default class Balances {
|
|||||||
}
|
}
|
||||||
|
|
||||||
subscribeBlockNumber () {
|
subscribeBlockNumber () {
|
||||||
|
// Don't trigger the balances updates on first call (when the
|
||||||
|
// subscriptions are setup)
|
||||||
|
let firstcall = true;
|
||||||
|
|
||||||
return this._api
|
return this._api
|
||||||
.subscribe('eth_blockNumber', (error) => {
|
.subscribe('eth_blockNumber', (error, block) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
return console.warn('_subscribeBlockNumber', error);
|
return console.warn('balances::subscribeBlockNumber', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstcall) {
|
||||||
|
firstcall = false;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._store.dispatch(queryTokensFilter());
|
this._store.dispatch(queryTokensFilter());
|
||||||
return this.fetchAllBalances();
|
return this.fetchEthBalances();
|
||||||
})
|
})
|
||||||
.then((blockNumberSID) => {
|
.then((subId) => {
|
||||||
this._blockNumberSID = blockNumberSID;
|
this._apiSubs.push(subId);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.warn('_subscribeBlockNumber', error);
|
console.warn('_subscribeBlockNumber', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchAllBalances (options = {}) {
|
fetchEthBalances (options = {}) {
|
||||||
// If it's a network change, reload the tokens
|
log.debug('fetching eth balances (throttled)...');
|
||||||
// ( and then fetch the tokens balances ) and fetch
|
|
||||||
// the accounts balances
|
|
||||||
if (options.changedNetwork) {
|
|
||||||
this.loadTokens({ skipNotifications: true });
|
|
||||||
this.loadTokens.flush();
|
|
||||||
|
|
||||||
this.fetchBalances({
|
|
||||||
force: true,
|
|
||||||
skipNotifications: true
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.fetchTokensBalances(options);
|
|
||||||
this.fetchBalances(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchTokensBalances (options) {
|
|
||||||
const { skipNotifications = false, force = false } = options;
|
|
||||||
|
|
||||||
this.throttledTokensFetch(skipNotifications);
|
|
||||||
|
|
||||||
if (force) {
|
|
||||||
this.throttledTokensFetch.flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchBalances (options) {
|
|
||||||
const { skipNotifications = false, force = false } = options;
|
|
||||||
const { syncing } = this._store.getState().nodeStatus;
|
const { syncing } = this._store.getState().nodeStatus;
|
||||||
|
|
||||||
|
if (options.force) {
|
||||||
|
return this._fetchEthBalances();
|
||||||
|
}
|
||||||
|
|
||||||
// If syncing, only retrieve balances once every
|
// If syncing, only retrieve balances once every
|
||||||
// few seconds
|
// few seconds
|
||||||
if (syncing || syncing === null) {
|
if (syncing || syncing === null) {
|
||||||
this.shortThrottledFetch.cancel();
|
this.shortThrottledFetch.cancel();
|
||||||
this.longThrottledFetch(skipNotifications);
|
return this.longThrottledFetch();
|
||||||
|
|
||||||
if (force) {
|
|
||||||
this.longThrottledFetch.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.longThrottledFetch.cancel();
|
this.longThrottledFetch.cancel();
|
||||||
this.shortThrottledFetch(skipNotifications);
|
return this.shortThrottledFetch();
|
||||||
|
|
||||||
if (force) {
|
|
||||||
this.shortThrottledFetch.flush();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_fetchBalances (skipNotifications = false) {
|
_fetchEthBalances (skipNotifications = false) {
|
||||||
this._store.dispatch(fetchBalances(null, skipNotifications));
|
log.debug('fetching eth balances (real)...');
|
||||||
}
|
|
||||||
|
|
||||||
_fetchTokens (skipNotifications = false) {
|
const { dispatch, getState } = this._store;
|
||||||
this._store.dispatch(fetchTokensBalances(null, null, skipNotifications));
|
|
||||||
}
|
|
||||||
|
|
||||||
getTokenRegistry () {
|
return fetchBalances(null, skipNotifications)(dispatch, getState);
|
||||||
return Contracts.get().tokenReg.getContract();
|
|
||||||
}
|
|
||||||
|
|
||||||
_loadTokens (options = {}) {
|
|
||||||
return this
|
|
||||||
.getTokenRegistry()
|
|
||||||
.then((tokenreg) => {
|
|
||||||
this._tokenreg = tokenreg;
|
|
||||||
|
|
||||||
this._store.dispatch(loadTokens(options));
|
|
||||||
|
|
||||||
return this.attachToTokens(tokenreg);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.warn('balances::loadTokens', error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
attachToTokens (tokenreg) {
|
|
||||||
return Promise
|
|
||||||
.all([
|
|
||||||
this.attachToTokenMetaChange(tokenreg),
|
|
||||||
this.attachToNewToken(tokenreg)
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
attachToNewToken (tokenreg) {
|
|
||||||
if (this._tokenregSID) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
return tokenreg.instance.Registered
|
|
||||||
.subscribe({
|
|
||||||
fromBlock: 0,
|
|
||||||
toBlock: 'latest',
|
|
||||||
skipInitFetch: true
|
|
||||||
}, (error, logs) => {
|
|
||||||
if (error) {
|
|
||||||
return console.error('balances::attachToNewToken', 'failed to attach to tokenreg Registered', error.toString(), error.stack);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.handleTokensLogs(logs);
|
|
||||||
})
|
|
||||||
.then((tokenregSID) => {
|
|
||||||
this._tokenregSID = tokenregSID;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
attachToTokenMetaChange (tokenreg) {
|
|
||||||
if (this._tokenMetaSID) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
return tokenreg.instance.MetaChanged
|
|
||||||
.subscribe({
|
|
||||||
fromBlock: 0,
|
|
||||||
toBlock: 'latest',
|
|
||||||
topics: [ null, padRight(this._api.util.asciiToHex('IMG'), 32) ],
|
|
||||||
skipInitFetch: true
|
|
||||||
}, (error, logs) => {
|
|
||||||
if (error) {
|
|
||||||
return console.error('balances::attachToTokenMetaChange', 'failed to attach to tokenreg MetaChanged', error.toString(), error.stack);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.handleTokensLogs(logs);
|
|
||||||
})
|
|
||||||
.then((tokenMetaSID) => {
|
|
||||||
this._tokenMetaSID = tokenMetaSID;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTokensLogs (logs) {
|
|
||||||
const tokenIds = logs.map((log) => log.params.id.value.toNumber());
|
|
||||||
|
|
||||||
this._store.dispatch(fetchTokens(tokenIds));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { uniq, isEqual } from 'lodash';
|
import { difference, uniq } from 'lodash';
|
||||||
import { push } from 'react-router-redux';
|
import { push } from 'react-router-redux';
|
||||||
|
|
||||||
import { notifyTransaction } from '~/util/notifications';
|
import { notifyTransaction } from '~/util/notifications';
|
||||||
@ -22,11 +22,16 @@ import { ETH_TOKEN, fetchAccountsBalances } from '~/util/tokens';
|
|||||||
import { LOG_KEYS, getLogger } from '~/config';
|
import { LOG_KEYS, getLogger } from '~/config';
|
||||||
import { sha3 } from '~/api/util/sha3';
|
import { sha3 } from '~/api/util/sha3';
|
||||||
|
|
||||||
|
import { fetchTokens } from './tokensActions';
|
||||||
|
|
||||||
const TRANSFER_SIGNATURE = sha3('Transfer(address,address,uint256)');
|
const TRANSFER_SIGNATURE = sha3('Transfer(address,address,uint256)');
|
||||||
|
|
||||||
const log = getLogger(LOG_KEYS.Balances);
|
const log = getLogger(LOG_KEYS.Balances);
|
||||||
|
|
||||||
let tokensFilter = {};
|
let tokensFilter = {
|
||||||
|
tokenAddresses: [],
|
||||||
|
addresses: []
|
||||||
|
};
|
||||||
|
|
||||||
function _setBalances (balances) {
|
function _setBalances (balances) {
|
||||||
return {
|
return {
|
||||||
@ -63,13 +68,10 @@ function setBalances (updates, skipNotifications = false) {
|
|||||||
dispatch(notifyBalanceChange(who, prevTokenValue, nextTokenValue, token));
|
dispatch(notifyBalanceChange(who, prevTokenValue, nextTokenValue, token));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the token if it's native ETH or if it has a value
|
|
||||||
if (token.native || nextTokenValue.gt(0)) {
|
|
||||||
nextBalances[who] = {
|
nextBalances[who] = {
|
||||||
...(nextBalances[who] || {}),
|
...(nextBalances[who] || {}),
|
||||||
[tokenId]: nextTokenValue
|
[tokenId]: nextTokenValue
|
||||||
};
|
};
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -100,61 +102,75 @@ function notifyBalanceChange (who, fromValue, toValue, token) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: fetch txCount when needed
|
// TODO: fetch txCount when needed
|
||||||
export function fetchBalances (_addresses, skipNotifications = false) {
|
export function fetchBalances (addresses, skipNotifications = false) {
|
||||||
return fetchTokensBalances(_addresses, [ ETH_TOKEN ], skipNotifications);
|
return (dispatch, getState) => {
|
||||||
|
const { personal } = getState();
|
||||||
|
const { visibleAccounts, accounts } = personal;
|
||||||
|
|
||||||
|
const addressesToFetch = addresses || uniq(visibleAccounts.concat(Object.keys(accounts)));
|
||||||
|
const updates = addressesToFetch.reduce((updates, who) => {
|
||||||
|
updates[who] = [ ETH_TOKEN.id ];
|
||||||
|
|
||||||
|
return updates;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return fetchTokensBalances(updates, skipNotifications)(dispatch, getState);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateTokensFilter (_addresses, _tokens, options = {}) {
|
export function updateTokensFilter (options = {}) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const { api, personal, tokens } = getState();
|
const { api, personal, tokens } = getState();
|
||||||
const { visibleAccounts, accounts } = personal;
|
const { visibleAccounts, accounts } = personal;
|
||||||
|
|
||||||
const addressesToFetch = uniq(visibleAccounts.concat(Object.keys(accounts)));
|
const addresses = uniq(visibleAccounts.concat(Object.keys(accounts)));
|
||||||
const addresses = uniq(_addresses || addressesToFetch || []).sort();
|
const tokensToUpdate = Object.values(tokens);
|
||||||
|
const tokensAddressMap = Object.values(tokens).reduce((map, token) => {
|
||||||
|
map[token.address] = token;
|
||||||
|
return map;
|
||||||
|
}, {});
|
||||||
|
|
||||||
const tokensToUpdate = _tokens || Object.values(tokens);
|
|
||||||
const tokenAddresses = tokensToUpdate
|
const tokenAddresses = tokensToUpdate
|
||||||
.map((t) => t.address)
|
.map((t) => t.address)
|
||||||
.filter((address) => address)
|
.filter((address) => address && !/^(0x)?0*$/.test(address));
|
||||||
.sort();
|
|
||||||
|
|
||||||
if (tokensFilter.filterFromId || tokensFilter.filterToId) {
|
// Token Addresses that are not in the current filter
|
||||||
// Has the tokens addresses changed (eg. a network change)
|
const newTokenAddresses = difference(tokenAddresses, tokensFilter.tokenAddresses);
|
||||||
const sameTokens = isEqual(tokenAddresses, tokensFilter.tokenAddresses);
|
|
||||||
|
|
||||||
// Addresses that are not in the current filter (omit those
|
// Addresses that are not in the current filter (omit those
|
||||||
// that the filter includes)
|
// that the filter includes)
|
||||||
const newAddresses = addresses.filter((address) => !tokensFilter.addresses.includes(address));
|
const newAddresses = difference(addresses, tokensFilter.addresses);
|
||||||
|
|
||||||
|
if (tokensFilter.filterFromId || tokensFilter.filterToId) {
|
||||||
// If no new addresses and the same tokens, don't change the filter
|
// If no new addresses and the same tokens, don't change the filter
|
||||||
if (sameTokens && newAddresses.length === 0) {
|
if (newTokenAddresses.length === 0 && newAddresses.length === 0) {
|
||||||
log.debug('no need to update token filter', addresses, tokenAddresses, tokensFilter);
|
log.debug('no need to update token filter', addresses, tokenAddresses, tokensFilter);
|
||||||
return queryTokensFilter(tokensFilter)(dispatch, getState);
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
const updates = {};
|
||||||
|
|
||||||
|
const allTokenIds = tokensToUpdate.map((token) => token.id);
|
||||||
|
const newTokenIds = newTokenAddresses.map((address) => tokensAddressMap[address].id);
|
||||||
|
|
||||||
|
newAddresses.forEach((newAddress) => {
|
||||||
|
updates[newAddress] = allTokenIds;
|
||||||
|
});
|
||||||
|
|
||||||
|
difference(addresses, newAddresses).forEach((oldAddress) => {
|
||||||
|
updates[oldAddress] = newTokenIds;
|
||||||
|
});
|
||||||
|
|
||||||
log.debug('updating the token filter', addresses, tokenAddresses);
|
log.debug('updating the token filter', addresses, tokenAddresses);
|
||||||
const promises = [];
|
|
||||||
|
|
||||||
if (tokensFilter.filterFromId) {
|
|
||||||
promises.push(api.eth.uninstallFilter(tokensFilter.filterFromId));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tokensFilter.filterToId) {
|
|
||||||
promises.push(api.eth.uninstallFilter(tokensFilter.filterToId));
|
|
||||||
}
|
|
||||||
|
|
||||||
Promise
|
|
||||||
.all([
|
|
||||||
api.eth.blockNumber()
|
|
||||||
].concat(promises))
|
|
||||||
.then(([ block ]) => {
|
|
||||||
const topicsFrom = [ TRANSFER_SIGNATURE, addresses, null ];
|
const topicsFrom = [ TRANSFER_SIGNATURE, addresses, null ];
|
||||||
const topicsTo = [ TRANSFER_SIGNATURE, null, addresses ];
|
const topicsTo = [ TRANSFER_SIGNATURE, null, addresses ];
|
||||||
|
|
||||||
const filterOptions = {
|
const filterOptions = {
|
||||||
fromBlock: block,
|
fromBlock: 'latest',
|
||||||
toBlock: 'pending',
|
toBlock: 'latest',
|
||||||
address: tokenAddresses
|
address: tokenAddresses
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -168,24 +184,29 @@ export function updateTokensFilter (_addresses, _tokens, options = {}) {
|
|||||||
topics: topicsTo
|
topics: topicsTo
|
||||||
};
|
};
|
||||||
|
|
||||||
const newFilters = Promise.all([
|
promises.push(
|
||||||
api.eth.newFilter(optionsFrom),
|
api.eth.newFilter(optionsFrom),
|
||||||
api.eth.newFilter(optionsTo)
|
api.eth.newFilter(optionsTo)
|
||||||
]);
|
);
|
||||||
|
|
||||||
return newFilters;
|
if (tokensFilter.filterFromId) {
|
||||||
})
|
promises.push(api.eth.uninstallFilter(tokensFilter.filterFromId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokensFilter.filterToId) {
|
||||||
|
promises.push(api.eth.uninstallFilter(tokensFilter.filterToId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises)
|
||||||
.then(([ filterFromId, filterToId ]) => {
|
.then(([ filterFromId, filterToId ]) => {
|
||||||
const nextTokensFilter = {
|
const nextTokensFilter = {
|
||||||
filterFromId, filterToId,
|
filterFromId, filterToId,
|
||||||
addresses, tokenAddresses
|
addresses, tokenAddresses
|
||||||
};
|
};
|
||||||
|
|
||||||
const { skipNotifications } = options;
|
|
||||||
|
|
||||||
tokensFilter = nextTokensFilter;
|
tokensFilter = nextTokensFilter;
|
||||||
fetchTokensBalances(addresses, tokensToUpdate, skipNotifications)(dispatch, getState);
|
|
||||||
})
|
})
|
||||||
|
.then(() => fetchTokensBalances(updates)(dispatch, getState))
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.warn('balances::updateTokensFilter', error);
|
console.warn('balances::updateTokensFilter', error);
|
||||||
});
|
});
|
||||||
@ -194,12 +215,7 @@ export function updateTokensFilter (_addresses, _tokens, options = {}) {
|
|||||||
|
|
||||||
export function queryTokensFilter () {
|
export function queryTokensFilter () {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const { api, personal, tokens } = getState();
|
const { api } = getState();
|
||||||
const { visibleAccounts, accounts } = personal;
|
|
||||||
|
|
||||||
const allAddresses = visibleAccounts.concat(Object.keys(accounts));
|
|
||||||
const addressesToFetch = uniq(allAddresses);
|
|
||||||
const lcAddresses = addressesToFetch.map((a) => a.toLowerCase());
|
|
||||||
|
|
||||||
Promise
|
Promise
|
||||||
.all([
|
.all([
|
||||||
@ -207,67 +223,107 @@ export function queryTokensFilter () {
|
|||||||
api.eth.getFilterChanges(tokensFilter.filterToId)
|
api.eth.getFilterChanges(tokensFilter.filterToId)
|
||||||
])
|
])
|
||||||
.then(([ logsFrom, logsTo ]) => {
|
.then(([ logsFrom, logsTo ]) => {
|
||||||
const addresses = [];
|
const logs = [].concat(logsFrom, logsTo);
|
||||||
const tokenAddresses = [];
|
|
||||||
const logs = logsFrom.concat(logsTo);
|
|
||||||
|
|
||||||
if (logs.length > 0) {
|
if (logs.length === 0) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
log.debug('got tokens filter logs', logs);
|
log.debug('got tokens filter logs', logs);
|
||||||
}
|
}
|
||||||
|
|
||||||
logs
|
const { personal, tokens } = getState();
|
||||||
.forEach((log) => {
|
const { visibleAccounts, accounts } = personal;
|
||||||
const tokenAddress = log.address;
|
|
||||||
|
|
||||||
const fromAddress = '0x' + log.topics[1].slice(-40);
|
const addressesToFetch = uniq(visibleAccounts.concat(Object.keys(accounts)));
|
||||||
const toAddress = '0x' + log.topics[2].slice(-40);
|
const lcAddresses = addressesToFetch.map((a) => a.toLowerCase());
|
||||||
|
|
||||||
const fromAddressIndex = lcAddresses.indexOf(fromAddress);
|
const lcTokensMap = Object.values(tokens).reduce((map, token) => {
|
||||||
const toAddressIndex = lcAddresses.indexOf(toAddress);
|
map[token.address.toLowerCase()] = token;
|
||||||
|
return map;
|
||||||
if (fromAddressIndex > -1) {
|
|
||||||
addresses.push(addressesToFetch[fromAddressIndex]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toAddressIndex > -1) {
|
|
||||||
addresses.push(addressesToFetch[toAddressIndex]);
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenAddresses.push(tokenAddress);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (addresses.length === 0) {
|
// The keys are the account addresses,
|
||||||
|
// and the value is an Array of the tokens addresses
|
||||||
|
// to update
|
||||||
|
const updates = {};
|
||||||
|
|
||||||
|
logs
|
||||||
|
.forEach((log, index) => {
|
||||||
|
const tokenAddress = log.address.toLowerCase();
|
||||||
|
const token = lcTokensMap[tokenAddress];
|
||||||
|
|
||||||
|
// logs = [ ...logsFrom, ...logsTo ]
|
||||||
|
const topicIdx = index < logsFrom.length ? 1 : 2;
|
||||||
|
const address = ('0x' + log.topics[topicIdx].slice(-40)).toLowerCase();
|
||||||
|
const addressIndex = lcAddresses.indexOf(address);
|
||||||
|
|
||||||
|
if (addressIndex > -1) {
|
||||||
|
const who = addressesToFetch[addressIndex];
|
||||||
|
|
||||||
|
updates[who] = [].concat(updates[who] || [], token.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// No accounts to update
|
||||||
|
if (Object.keys(updates).length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokensToUpdate = Object.values(tokens)
|
Object.keys(updates).forEach((who) => {
|
||||||
.filter((t) => tokenAddresses.includes(t.address));
|
// Keep non-empty token addresses
|
||||||
|
updates[who] = uniq(updates[who]);
|
||||||
|
});
|
||||||
|
|
||||||
fetchTokensBalances(uniq(addresses), tokensToUpdate)(dispatch, getState);
|
fetchTokensBalances(updates)(dispatch, getState);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchTokensBalances (_addresses = null, _tokens = null, skipNotifications = false) {
|
export function fetchTokensBalances (updates, skipNotifications = false) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const { api, personal, tokens } = getState();
|
const { api, personal, tokens } = getState();
|
||||||
const { visibleAccounts, accounts } = personal;
|
|
||||||
const allTokens = Object.values(tokens);
|
const allTokens = Object.values(tokens);
|
||||||
|
|
||||||
|
if (!updates) {
|
||||||
|
const { visibleAccounts, accounts } = personal;
|
||||||
const addressesToFetch = uniq(visibleAccounts.concat(Object.keys(accounts)));
|
const addressesToFetch = uniq(visibleAccounts.concat(Object.keys(accounts)));
|
||||||
const addresses = _addresses || addressesToFetch;
|
|
||||||
const tokensToUpdate = _tokens || allTokens;
|
|
||||||
|
|
||||||
if (addresses.length === 0) {
|
updates = addressesToFetch.reduce((updates, who) => {
|
||||||
return Promise.resolve();
|
updates[who] = allTokens.map((token) => token.id);
|
||||||
}
|
|
||||||
|
|
||||||
const updates = addresses.reduce((updates, who) => {
|
|
||||||
updates[who] = tokensToUpdate.map((token) => token.id);
|
|
||||||
return updates;
|
return updates;
|
||||||
}, {});
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = Date.now();
|
||||||
|
|
||||||
return fetchAccountsBalances(api, allTokens, updates)
|
return fetchAccountsBalances(api, allTokens, updates)
|
||||||
|
.then((balances) => {
|
||||||
|
log.debug('got tokens balances', balances, updates, `(took ${Date.now() - start}ms)`);
|
||||||
|
|
||||||
|
// Tokens info might not be fetched yet (to not load
|
||||||
|
// tokens we don't care about)
|
||||||
|
const tokenIdsToFetch = Object.values(balances)
|
||||||
|
.reduce((tokenIds, balance) => {
|
||||||
|
const nextTokenIds = Object.keys(balance)
|
||||||
|
.filter((tokenId) => balance[tokenId].gt(0));
|
||||||
|
|
||||||
|
return tokenIds.concat(nextTokenIds);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const tokenIndexesToFetch = uniq(tokenIdsToFetch)
|
||||||
|
.filter((tokenId) => tokens[tokenId] && tokens[tokenId].index && !tokens[tokenId].fetched)
|
||||||
|
.map((tokenId) => tokens[tokenId].index);
|
||||||
|
|
||||||
|
if (tokenIndexesToFetch.length === 0) {
|
||||||
|
return balances;
|
||||||
|
}
|
||||||
|
|
||||||
|
start = Date.now();
|
||||||
|
return fetchTokens(tokenIndexesToFetch)(dispatch, getState)
|
||||||
|
.then(() => log.debug('token indexes fetched', tokenIndexesToFetch, `(took ${Date.now() - start}ms)`))
|
||||||
|
.then(() => balances);
|
||||||
|
})
|
||||||
.then((balances) => {
|
.then((balances) => {
|
||||||
dispatch(setBalances(balances, skipNotifications));
|
dispatch(setBalances(balances, skipNotifications));
|
||||||
})
|
})
|
||||||
|
@ -14,14 +14,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
export const fetchCertifiers = () => ({
|
|
||||||
type: 'fetchCertifiers'
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchCertifications = (address) => ({
|
|
||||||
type: 'fetchCertifications', address
|
|
||||||
});
|
|
||||||
|
|
||||||
export const addCertification = (address, id, name, title, icon) => ({
|
export const addCertification = (address, id, name, title, icon) => ({
|
||||||
type: 'addCertification', address, id, name, title, icon
|
type: 'addCertification', address, id, name, title, icon
|
||||||
});
|
});
|
||||||
|
343
js-old/src/redux/providers/certifications/certifiers.monitor.js
Normal file
343
js-old/src/redux/providers/certifications/certifiers.monitor.js
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { range } from 'lodash';
|
||||||
|
|
||||||
|
import { addCertification, removeCertification } from './actions';
|
||||||
|
|
||||||
|
import { getLogger, LOG_KEYS } from '~/config';
|
||||||
|
import Contract from '~/api/contract';
|
||||||
|
import { bytesToHex, hexToAscii } from '~/api/util/format';
|
||||||
|
import Contracts from '~/contracts';
|
||||||
|
import CertifierABI from '~/contracts/abi/certifier.json';
|
||||||
|
import { querier } from './enhanced-querier';
|
||||||
|
|
||||||
|
const log = getLogger(LOG_KEYS.CertificationsMiddleware);
|
||||||
|
|
||||||
|
let self = null;
|
||||||
|
|
||||||
|
export default class CertifiersMonitor {
|
||||||
|
constructor (api, store) {
|
||||||
|
this._api = api;
|
||||||
|
this._name = 'Certifiers';
|
||||||
|
this._store = store;
|
||||||
|
|
||||||
|
this._contract = new Contract(this.api, CertifierABI);
|
||||||
|
this._contractEvents = [ 'Confirmed', 'Revoked' ]
|
||||||
|
.map((name) => this.contract.events.find((e) => e.name === name));
|
||||||
|
|
||||||
|
this.certifiers = {};
|
||||||
|
this.fetchedAccounts = {};
|
||||||
|
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
static get () {
|
||||||
|
if (self) {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
self = new CertifiersMonitor();
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
static init (api, store) {
|
||||||
|
if (!self) {
|
||||||
|
self = new CertifiersMonitor(api, store);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get api () {
|
||||||
|
return this._api;
|
||||||
|
}
|
||||||
|
|
||||||
|
get contract () {
|
||||||
|
return this._contract;
|
||||||
|
}
|
||||||
|
|
||||||
|
get contractEvents () {
|
||||||
|
return this._contractEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
get name () {
|
||||||
|
return this._name;
|
||||||
|
}
|
||||||
|
|
||||||
|
get store () {
|
||||||
|
return this._store;
|
||||||
|
}
|
||||||
|
|
||||||
|
get registry () {
|
||||||
|
return this._registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
get registryEvents () {
|
||||||
|
return this._registryEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkFilters () {
|
||||||
|
this.checkCertifiersFilter();
|
||||||
|
this.checkRegistryFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
checkCertifiersFilter () {
|
||||||
|
if (!this.certifiersFilter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.api.eth.getFilterChanges(this.certifiersFilter)
|
||||||
|
.then((logs) => {
|
||||||
|
if (logs.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedLogs = this.contract.parseEventLogs(logs).filter((log) => log.params);
|
||||||
|
|
||||||
|
log.debug('received certifiers logs', parsedLogs);
|
||||||
|
|
||||||
|
const promises = parsedLogs.map((log) => {
|
||||||
|
const account = log.params.who.value;
|
||||||
|
const certifier = Object.values(this.certifiers).find((c) => c.address === log.address);
|
||||||
|
|
||||||
|
if (!certifier) {
|
||||||
|
log.warn('could not find the certifier', { certifiers: this.certifiers, log });
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.fetchAccount(account, { ids: [ certifier.id ] });
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
checkRegistryFilter () {
|
||||||
|
if (!this.registryFilter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.api.eth.getFilterChanges(this.registryFilter)
|
||||||
|
.then((logs) => {
|
||||||
|
if (logs.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedLogs = this.contract.parseEventLogs(logs).filter((log) => log.params);
|
||||||
|
const indexes = parsedLogs.map((log) => log.params && log.params.id.value.toNumber());
|
||||||
|
|
||||||
|
log.debug('received registry logs', parsedLogs);
|
||||||
|
return this.fetchElements(indexes);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial load of the Monitor.
|
||||||
|
* Fetch the contract from the Registry, and
|
||||||
|
* load the elements addresses
|
||||||
|
*/
|
||||||
|
load () {
|
||||||
|
const badgeReg = Contracts.get().badgeReg;
|
||||||
|
|
||||||
|
log.debug(`loading the ${this.name} monitor...`);
|
||||||
|
return badgeReg.getContract()
|
||||||
|
.then((registryContract) => {
|
||||||
|
this._registry = registryContract;
|
||||||
|
this._registryEvents = [ 'Registered', 'Unregistered', 'MetaChanged', 'AddressChanged' ]
|
||||||
|
.map((name) => this.registry.events.find((e) => e.name === name));
|
||||||
|
|
||||||
|
return this.registry.instance.badgeCount.call({});
|
||||||
|
})
|
||||||
|
.then((count) => {
|
||||||
|
log.debug(`found ${count.toFormat()} registered contracts for ${this.name}`);
|
||||||
|
return this.fetchElements(range(count.toNumber()));
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return this.setRegistryFilter();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// Listen for new blocks
|
||||||
|
return this.api.subscribe('eth_blockNumber', (err) => {
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.checkFilters();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
log.debug(`loaded the ${this.name} monitor!`, this.certifiers);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
log.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the given registered element
|
||||||
|
*/
|
||||||
|
fetchElements (indexes) {
|
||||||
|
const badgeReg = Contracts.get().badgeReg;
|
||||||
|
const { instance } = this.registry;
|
||||||
|
|
||||||
|
const sorted = indexes.sort();
|
||||||
|
const from = sorted[0];
|
||||||
|
const last = sorted[sorted.length - 1];
|
||||||
|
const limit = last - from + 1;
|
||||||
|
|
||||||
|
// Fetch the address, name and owner in one batch
|
||||||
|
return querier(this.api, { address: instance.address, from, limit }, instance.badge)
|
||||||
|
.then((results) => {
|
||||||
|
const certifiers = results
|
||||||
|
.map(([ address, name, owner ], index) => ({
|
||||||
|
address, owner,
|
||||||
|
id: index + from,
|
||||||
|
name: hexToAscii(bytesToHex(name).replace(/(00)+$/, ''))
|
||||||
|
}))
|
||||||
|
.reduce((certifiers, certifier) => {
|
||||||
|
const { id } = certifier;
|
||||||
|
|
||||||
|
if (!/^(0x)?0+$/.test(certifier.address)) {
|
||||||
|
certifiers[id] = certifier;
|
||||||
|
} else if (certifiers[id]) {
|
||||||
|
delete certifiers[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
return certifiers;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// Fetch the meta-data in serie
|
||||||
|
return Object.values(certifiers).reduce((promise, certifier) => {
|
||||||
|
return promise.then(() => badgeReg.fetchMeta(certifier.id))
|
||||||
|
.then((meta) => {
|
||||||
|
this.certifiers[certifier.id] = { ...certifier, ...meta };
|
||||||
|
});
|
||||||
|
}, Promise.resolve());
|
||||||
|
})
|
||||||
|
.then(() => log.debug('fetched certifiers', { certifiers: this.certifiers }))
|
||||||
|
// Fetch the know accounts in case it's an update of the certifiers
|
||||||
|
.then(() => this.fetchAccounts(Object.keys(this.fetchedAccounts), { ids: indexes, force: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchAccounts (addresses, { ids = null, force = false } = {}) {
|
||||||
|
const newAddresses = force
|
||||||
|
? addresses
|
||||||
|
: addresses.filter((address) => !this.fetchedAccounts[address]);
|
||||||
|
|
||||||
|
if (newAddresses.length === 0) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug(`fetching values for "${addresses.join(' ; ')}" in ${this.name}...`);
|
||||||
|
return newAddresses
|
||||||
|
.reduce((promise, address) => {
|
||||||
|
return promise.then(() => this.fetchAccount(address, { ids }));
|
||||||
|
}, Promise.resolve())
|
||||||
|
.then(() => {
|
||||||
|
log.debug(`fetched values for "${addresses.join(' ; ')}" in ${this.name}!`);
|
||||||
|
})
|
||||||
|
.then(() => this.setCertifiersFilter());
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchAccount (address, { ids = null } = {}) {
|
||||||
|
let certifiers = Object.values(this.certifiers);
|
||||||
|
|
||||||
|
// Only fetch values for the givens ids, if any
|
||||||
|
if (ids) {
|
||||||
|
certifiers = certifiers.filter((certifier) => ids.includes(certifier.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
certifiers
|
||||||
|
.reduce((promise, certifier) => {
|
||||||
|
return promise
|
||||||
|
.then(() => {
|
||||||
|
return this.contract.at(certifier.address).instance.certified.call({}, [ address ]);
|
||||||
|
})
|
||||||
|
.then((certified) => {
|
||||||
|
const { id, title, icon, name } = certifier;
|
||||||
|
|
||||||
|
if (!certified) {
|
||||||
|
return this.store.dispatch(removeCertification(address, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug('seen as certified', { address, id, name, icon });
|
||||||
|
this.store.dispatch(addCertification(address, id, name, title, icon));
|
||||||
|
});
|
||||||
|
}, Promise.resolve())
|
||||||
|
.then(() => {
|
||||||
|
this.fetchedAccounts[address] = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setCertifiersFilter () {
|
||||||
|
const accounts = Object.keys(this.fetchedAccounts);
|
||||||
|
const addresses = Object.values(this.certifiers).map((c) => c.address);
|
||||||
|
// The events have as first indexed data the account address
|
||||||
|
const topics = [
|
||||||
|
this.contractEvents.map((event) => '0x' + event.signature),
|
||||||
|
accounts
|
||||||
|
];
|
||||||
|
|
||||||
|
if (accounts.length === 0 || addresses.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const promise = this.certifiersFilter
|
||||||
|
? this.api.eth.uninstallFilter(this.certifiersFilter)
|
||||||
|
: Promise.resolve();
|
||||||
|
|
||||||
|
log.debug('setting up registry filter', { topics, accounts, addresses });
|
||||||
|
|
||||||
|
return promise
|
||||||
|
.then(() => this.api.eth.newFilter({
|
||||||
|
fromBlock: 'latest',
|
||||||
|
toBlock: 'latest',
|
||||||
|
address: addresses,
|
||||||
|
topics
|
||||||
|
}))
|
||||||
|
.then((filterId) => {
|
||||||
|
this.certifiersFilter = filterId;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setRegistryFilter () {
|
||||||
|
const { address } = this.registry.instance;
|
||||||
|
const topics = [ this.registryEvents.map((event) => '0x' + event.signature) ];
|
||||||
|
|
||||||
|
log.debug('setting up registry filter', { topics, address });
|
||||||
|
|
||||||
|
return this.api.eth
|
||||||
|
.newFilter({
|
||||||
|
fromBlock: 'latest',
|
||||||
|
toBlock: 'latest',
|
||||||
|
address, topics
|
||||||
|
})
|
||||||
|
.then((filterId) => {
|
||||||
|
this.registryFilter = filterId;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { padRight, padLeft } from '~/api/util/format';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bytecode of this contract:
|
||||||
|
*
|
||||||
|
*
|
||||||
|
pragma solidity ^0.4.10;
|
||||||
|
|
||||||
|
contract Querier {
|
||||||
|
function Querier
|
||||||
|
(address addr, bytes32 sign, uint out_size, uint from, uint limit)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
// The size is 32 bytes for each
|
||||||
|
// value, plus 32 bytes for the count
|
||||||
|
uint m_size = out_size * limit + 32;
|
||||||
|
|
||||||
|
bytes32 p_return;
|
||||||
|
uint p_in;
|
||||||
|
uint p_out;
|
||||||
|
|
||||||
|
assembly {
|
||||||
|
p_return := mload(0x40)
|
||||||
|
mstore(0x40, add(p_return, m_size))
|
||||||
|
|
||||||
|
mstore(p_return, limit)
|
||||||
|
|
||||||
|
p_in := mload(0x40)
|
||||||
|
mstore(0x40, add(p_in, 0x24))
|
||||||
|
|
||||||
|
mstore(p_in, sign)
|
||||||
|
|
||||||
|
p_out := add(p_return, 0x20)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint i = from; i < from + limit; i++) {
|
||||||
|
assembly {
|
||||||
|
mstore(add(p_in, 0x4), i)
|
||||||
|
call(gas, addr, 0x0, p_in, 0x24, p_out, out_size)
|
||||||
|
p_out := add(p_out, out_size)
|
||||||
|
pop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assembly {
|
||||||
|
return (p_return, m_size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const bytecode = '0x60606040523415600e57600080fd5b60405160a0806099833981016040528080519190602001805191906020018051919060200180519190602001805191505082810260200160008080806040519350848401604052858452604051602481016040528981529250505060208201855b858701811015609457806004840152878260248560008e5af15090870190600101606f565b8484f300';
|
||||||
|
|
||||||
|
export const querier = (api, { address, from, limit }, method) => {
|
||||||
|
const { outputs, signature } = method;
|
||||||
|
const outLength = 32 * outputs.length;
|
||||||
|
const callargs = [
|
||||||
|
padLeft(address, 32),
|
||||||
|
padRight(signature, 32),
|
||||||
|
padLeft(outLength, 32),
|
||||||
|
padLeft(from, 32),
|
||||||
|
padLeft(limit, 32)
|
||||||
|
].map((v) => v.slice(2)).join('');
|
||||||
|
const calldata = bytecode + callargs;
|
||||||
|
|
||||||
|
return api.eth.call({ data: calldata })
|
||||||
|
.then((result) => {
|
||||||
|
const data = result.slice(2);
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < limit; i++) {
|
||||||
|
const datum = data.substr(2 * (32 + i * outLength), 2 * outLength);
|
||||||
|
const decoded = method.decodeOutput('0x' + datum).map((t) => t.value);
|
||||||
|
|
||||||
|
results.push(decoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
});
|
||||||
|
};
|
@ -14,222 +14,22 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { uniq, range, debounce } from 'lodash';
|
|
||||||
|
|
||||||
import { addCertification, removeCertification } from './actions';
|
|
||||||
|
|
||||||
import { getLogger, LOG_KEYS } from '~/config';
|
|
||||||
import Contract from '~/api/contract';
|
|
||||||
import Contracts from '~/contracts';
|
import Contracts from '~/contracts';
|
||||||
import CertifierABI from '~/contracts/abi/certifier.json';
|
import Monitor from './certifiers.monitor';
|
||||||
|
|
||||||
const log = getLogger(LOG_KEYS.CertificationsMiddleware);
|
|
||||||
|
|
||||||
// TODO: move this to a more general place
|
|
||||||
const updatableFilter = (api, onFilter) => {
|
|
||||||
let filter = null;
|
|
||||||
|
|
||||||
const update = (address, topics) => {
|
|
||||||
if (filter) {
|
|
||||||
filter = filter.then((filterId) => {
|
|
||||||
api.eth.uninstallFilter(filterId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
filter = (filter || Promise.resolve())
|
|
||||||
.then(() => api.eth.newFilter({
|
|
||||||
fromBlock: 0,
|
|
||||||
toBlock: 'latest',
|
|
||||||
address,
|
|
||||||
topics
|
|
||||||
}))
|
|
||||||
.then((filterId) => {
|
|
||||||
onFilter(filterId);
|
|
||||||
return filterId;
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.error('Failed to create certifications filter:', err);
|
|
||||||
});
|
|
||||||
|
|
||||||
return filter;
|
|
||||||
};
|
|
||||||
|
|
||||||
return update;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class CertificationsMiddleware {
|
export default class CertificationsMiddleware {
|
||||||
toMiddleware () {
|
toMiddleware () {
|
||||||
const api = Contracts.get()._api;
|
const api = Contracts.get()._api;
|
||||||
const badgeReg = Contracts.get().badgeReg;
|
|
||||||
|
|
||||||
const contract = new Contract(api, CertifierABI);
|
|
||||||
const Confirmed = contract.events.find((e) => e.name === 'Confirmed');
|
|
||||||
const Revoked = contract.events.find((e) => e.name === 'Revoked');
|
|
||||||
|
|
||||||
return (store) => {
|
return (store) => {
|
||||||
let certifiers = [];
|
Monitor.init(api, store);
|
||||||
let addresses = [];
|
|
||||||
let filterChanged = false;
|
|
||||||
let filter = null;
|
|
||||||
let badgeRegFilter = null;
|
|
||||||
let fetchCertifiersPromise = null;
|
|
||||||
|
|
||||||
const updateFilter = updatableFilter(api, (filterId) => {
|
|
||||||
filterChanged = true;
|
|
||||||
filter = filterId;
|
|
||||||
});
|
|
||||||
|
|
||||||
const badgeRegUpdateFilter = updatableFilter(api, (filterId) => {
|
|
||||||
filterChanged = true;
|
|
||||||
badgeRegFilter = filterId;
|
|
||||||
});
|
|
||||||
|
|
||||||
badgeReg
|
|
||||||
.getContract()
|
|
||||||
.then((badgeRegContract) => {
|
|
||||||
return badgeRegUpdateFilter(badgeRegContract.address, [ [
|
|
||||||
badgeRegContract.instance.Registered.signature,
|
|
||||||
badgeRegContract.instance.Unregistered.signature,
|
|
||||||
badgeRegContract.instance.MetaChanged.signature,
|
|
||||||
badgeRegContract.instance.AddressChanged.signature
|
|
||||||
] ]);
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
shortFetchChanges();
|
|
||||||
|
|
||||||
api.subscribe('eth_blockNumber', (err) => {
|
|
||||||
if (err) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchChanges();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function onLogs (logs) {
|
|
||||||
logs = contract.parseEventLogs(logs);
|
|
||||||
logs.forEach((log) => {
|
|
||||||
const certifier = certifiers.find((c) => c.address === log.address);
|
|
||||||
|
|
||||||
if (!certifier) {
|
|
||||||
throw new Error(`Could not find certifier at ${log.address}.`);
|
|
||||||
}
|
|
||||||
const { id, name, title, icon } = certifier;
|
|
||||||
|
|
||||||
if (log.event === 'Revoked') {
|
|
||||||
store.dispatch(removeCertification(log.params.who.value, id));
|
|
||||||
} else {
|
|
||||||
store.dispatch(addCertification(log.params.who.value, id, name, title, icon));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onBadgeRegLogs (logs) {
|
|
||||||
return badgeReg.getContract()
|
|
||||||
.then((badgeRegContract) => {
|
|
||||||
logs = badgeRegContract.parseEventLogs(logs);
|
|
||||||
|
|
||||||
const ids = logs.map((log) => log.params && log.params.id.value.toNumber());
|
|
||||||
|
|
||||||
return fetchCertifiers(uniq(ids));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function _fetchChanges () {
|
|
||||||
const method = filterChanged
|
|
||||||
? 'getFilterLogs'
|
|
||||||
: 'getFilterChanges';
|
|
||||||
|
|
||||||
filterChanged = false;
|
|
||||||
|
|
||||||
api.eth[method](badgeRegFilter)
|
|
||||||
.then(onBadgeRegLogs)
|
|
||||||
.catch((err) => {
|
|
||||||
console.error('Failed to fetch badge reg events:', err);
|
|
||||||
})
|
|
||||||
.then(() => api.eth[method](filter))
|
|
||||||
.then(onLogs)
|
|
||||||
.catch((err) => {
|
|
||||||
console.error('Failed to fetch new certifier events:', err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const shortFetchChanges = debounce(_fetchChanges, 0.5 * 1000, { leading: true });
|
|
||||||
const fetchChanges = debounce(shortFetchChanges, 10 * 1000, { leading: true });
|
|
||||||
|
|
||||||
function fetchConfirmedEvents () {
|
|
||||||
return updateFilter(certifiers.map((c) => c.address), [
|
|
||||||
[ Confirmed.signature, Revoked.signature ],
|
|
||||||
addresses
|
|
||||||
]).then(() => shortFetchChanges());
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchCertifiers (ids = []) {
|
|
||||||
if (fetchCertifiersPromise) {
|
|
||||||
return fetchCertifiersPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
let fetchEvents = false;
|
|
||||||
|
|
||||||
const idsPromise = (certifiers.length === 0)
|
|
||||||
? badgeReg.certifierCount().then((count) => {
|
|
||||||
return range(count);
|
|
||||||
})
|
|
||||||
: Promise.resolve(ids);
|
|
||||||
|
|
||||||
fetchCertifiersPromise = idsPromise
|
|
||||||
.then((ids) => {
|
|
||||||
const promises = ids.map((id) => {
|
|
||||||
return badgeReg.fetchCertifier(id)
|
|
||||||
.then((cert) => {
|
|
||||||
if (!certifiers.some((c) => c.id === cert.id)) {
|
|
||||||
certifiers = certifiers.concat(cert);
|
|
||||||
fetchEvents = true;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
if (/does not exist/.test(err.toString())) {
|
|
||||||
return log.info(err.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
log.warn(`Could not fetch certifier ${id}:`, err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return Promise
|
|
||||||
.all(promises)
|
|
||||||
.then(() => {
|
|
||||||
fetchCertifiersPromise = null;
|
|
||||||
|
|
||||||
if (fetchEvents) {
|
|
||||||
return fetchConfirmedEvents();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return fetchCertifiersPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (next) => (action) => {
|
return (next) => (action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'fetchCertifiers':
|
|
||||||
fetchConfirmedEvents();
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'fetchCertifications':
|
|
||||||
const { address } = action;
|
|
||||||
|
|
||||||
if (!addresses.includes(address)) {
|
|
||||||
addresses = addresses.concat(address);
|
|
||||||
fetchConfirmedEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case 'setVisibleAccounts':
|
case 'setVisibleAccounts':
|
||||||
const _addresses = action.addresses || [];
|
const { addresses = [] } = action;
|
||||||
|
|
||||||
addresses = uniq(addresses.concat(_addresses));
|
Monitor.get().fetchAccounts(addresses);
|
||||||
fetchConfirmedEvents();
|
|
||||||
next(action);
|
next(action);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -20,24 +20,32 @@ export default (state = initialState, action) => {
|
|||||||
if (action.type === 'addCertification') {
|
if (action.type === 'addCertification') {
|
||||||
const { address, id, name, icon, title } = action;
|
const { address, id, name, icon, title } = action;
|
||||||
const certifications = state[address] || [];
|
const certifications = state[address] || [];
|
||||||
|
const certifierIndex = certifications.findIndex((c) => c.id === id);
|
||||||
|
const data = { id, name, icon, title };
|
||||||
|
const nextCertifications = certifications.slice();
|
||||||
|
|
||||||
if (certifications.some((c) => c.id === id)) {
|
if (certifierIndex >= 0) {
|
||||||
return state;
|
nextCertifications[certifierIndex] = data;
|
||||||
|
} else {
|
||||||
|
nextCertifications.push(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newCertifications = certifications.concat({
|
return { ...state, [address]: nextCertifications };
|
||||||
id, name, icon, title
|
|
||||||
});
|
|
||||||
|
|
||||||
return { ...state, [address]: newCertifications };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.type === 'removeCertification') {
|
if (action.type === 'removeCertification') {
|
||||||
const { address, id } = action;
|
const { address, id } = action;
|
||||||
const certifications = state[address] || [];
|
const certifications = state[address] || [];
|
||||||
|
const certifierIndex = certifications.findIndex((c) => c.id === id);
|
||||||
|
|
||||||
const newCertifications = certifications.filter((c) => c.id !== id);
|
// Don't remove if not there
|
||||||
|
if (certifierIndex < 0) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCertifications = certifications.slice();
|
||||||
|
|
||||||
|
newCertifications.splice(certifierIndex, 1);
|
||||||
return { ...state, [address]: newCertifications };
|
return { ...state, [address]: newCertifications };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ export Balances from './balances';
|
|||||||
export Personal from './personal';
|
export Personal from './personal';
|
||||||
export Signer from './signer';
|
export Signer from './signer';
|
||||||
export Status from './status';
|
export Status from './status';
|
||||||
|
export Tokens from './tokens';
|
||||||
|
|
||||||
export apiReducer from './apiReducer';
|
export apiReducer from './apiReducer';
|
||||||
export balancesReducer from './balancesReducer';
|
export balancesReducer from './balancesReducer';
|
||||||
|
@ -16,22 +16,70 @@
|
|||||||
|
|
||||||
import { personalAccountsInfo } from './personalActions';
|
import { personalAccountsInfo } from './personalActions';
|
||||||
|
|
||||||
|
let instance;
|
||||||
|
|
||||||
export default class Personal {
|
export default class Personal {
|
||||||
constructor (store, api) {
|
constructor (store, api) {
|
||||||
this._api = api;
|
this._api = api;
|
||||||
this._store = store;
|
this._store = store;
|
||||||
}
|
}
|
||||||
|
|
||||||
start () {
|
static get (store) {
|
||||||
this._removeDeleted();
|
if (!instance && store) {
|
||||||
this._subscribeAccountsInfo();
|
return Personal.init(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static init (store) {
|
||||||
|
const { api } = store.getState();
|
||||||
|
|
||||||
|
if (!instance) {
|
||||||
|
instance = new Personal(store, api);
|
||||||
|
} else if (!instance) {
|
||||||
|
throw new Error('The Personal Provider has not been initialized yet');
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static start () {
|
||||||
|
const self = instance;
|
||||||
|
|
||||||
|
return Personal.stop()
|
||||||
|
.then(() => Promise.all([
|
||||||
|
self._removeDeleted(),
|
||||||
|
self._subscribeAccountsInfo()
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static stop () {
|
||||||
|
if (!instance) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
const self = instance;
|
||||||
|
|
||||||
|
return self._unsubscribeAccountsInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
_subscribeAccountsInfo () {
|
_subscribeAccountsInfo () {
|
||||||
|
let resolved = false;
|
||||||
|
|
||||||
|
// The Promise will be resolved when the first
|
||||||
|
// accounts are loaded
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
this._api
|
this._api
|
||||||
.subscribe('parity_allAccountsInfo', (error, accountsInfo) => {
|
.subscribe('parity_allAccountsInfo', (error, accountsInfo) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('parity_allAccountsInfo', error);
|
console.error('parity_allAccountsInfo', error);
|
||||||
|
|
||||||
|
if (!resolved) {
|
||||||
|
resolved = true;
|
||||||
|
return reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,12 +89,44 @@ export default class Personal {
|
|||||||
accountsInfo[address].address = address;
|
accountsInfo[address].address = address;
|
||||||
});
|
});
|
||||||
|
|
||||||
this._store.dispatch(personalAccountsInfo(accountsInfo));
|
const { dispatch, getState } = this._store;
|
||||||
|
|
||||||
|
personalAccountsInfo(accountsInfo)(dispatch, getState)
|
||||||
|
.then(() => {
|
||||||
|
if (!resolved) {
|
||||||
|
resolved = true;
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (!resolved) {
|
||||||
|
resolved = true;
|
||||||
|
return reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then((subId) => {
|
||||||
|
this.subscriptionId = subId;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_unsubscribeAccountsInfo () {
|
||||||
|
// Unsubscribe to any previous
|
||||||
|
// subscriptions
|
||||||
|
if (this.subscriptionId) {
|
||||||
|
return this._api
|
||||||
|
.unsubscribe(this.subscriptionId)
|
||||||
|
.then(() => {
|
||||||
|
this.subscriptionId = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
_removeDeleted () {
|
_removeDeleted () {
|
||||||
this._api.parity
|
return this._api.parity
|
||||||
.allAccountsInfo()
|
.allAccountsInfo()
|
||||||
.then((accountsInfo) => {
|
.then((accountsInfo) => {
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
import { isEqual, intersection } from 'lodash';
|
import { isEqual, intersection } from 'lodash';
|
||||||
|
|
||||||
import BalancesProvider from './balances';
|
import BalancesProvider from './balances';
|
||||||
|
import TokensProvider from './tokens';
|
||||||
import { updateTokensFilter } from './balancesActions';
|
import { updateTokensFilter } from './balancesActions';
|
||||||
import { attachWallets } from './walletActions';
|
import { attachWallets } from './walletActions';
|
||||||
|
|
||||||
@ -70,7 +71,7 @@ export function personalAccountsInfo (accountsInfo) {
|
|||||||
return WalletsUtils.fetchOwners(walletContract.at(wallet.address));
|
return WalletsUtils.fetchOwners(walletContract.at(wallet.address));
|
||||||
});
|
});
|
||||||
|
|
||||||
Promise
|
return Promise
|
||||||
.all(_fetchOwners)
|
.all(_fetchOwners)
|
||||||
.then((walletsOwners) => {
|
.then((walletsOwners) => {
|
||||||
return Object
|
return Object
|
||||||
@ -135,10 +136,6 @@ export function personalAccountsInfo (accountsInfo) {
|
|||||||
hardware
|
hardware
|
||||||
}));
|
}));
|
||||||
dispatch(attachWallets(wallets));
|
dispatch(attachWallets(wallets));
|
||||||
|
|
||||||
BalancesProvider.get().fetchAllBalances({
|
|
||||||
force: true
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.warn('personalAccountsInfo', error);
|
console.warn('personalAccountsInfo', error);
|
||||||
@ -176,12 +173,17 @@ export function setVisibleAccounts (addresses) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the Tokens filter to take into account the new
|
const promises = [];
|
||||||
// addresses
|
|
||||||
dispatch(updateTokensFilter());
|
|
||||||
|
|
||||||
BalancesProvider.get().fetchBalances({
|
// Update the Tokens filter to take into account the new
|
||||||
force: true
|
// addresses if it is not loading (it fetches the
|
||||||
});
|
// balances automatically after loading)
|
||||||
|
if (!TokensProvider.get().loading) {
|
||||||
|
promises.push(updateTokensFilter()(dispatch, getState));
|
||||||
|
}
|
||||||
|
|
||||||
|
promises.push(BalancesProvider.get().fetchEthBalances({ force: true }));
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -23,12 +23,19 @@ import SavedRequests from '~/views/Application/Requests/savedRequests';
|
|||||||
const savedRequests = new SavedRequests();
|
const savedRequests = new SavedRequests();
|
||||||
|
|
||||||
export const init = (api) => (dispatch) => {
|
export const init = (api) => (dispatch) => {
|
||||||
api.subscribe('parity_postTransaction', (error, request) => {
|
api.subscribe('signer_requestsToConfirm', (error, pending) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
return console.error(error);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(watchRequest(request));
|
const requests = pending
|
||||||
|
.filter((p) => p.payload && p.payload.sendTransaction)
|
||||||
|
.map((p) => ({
|
||||||
|
requestId: '0x' + p.id.toString(16),
|
||||||
|
transaction: p.payload.sendTransaction
|
||||||
|
}));
|
||||||
|
|
||||||
|
requests.forEach((request) => dispatch(watchRequest(request)));
|
||||||
});
|
});
|
||||||
|
|
||||||
api.once('connected', () => {
|
api.once('connected', () => {
|
||||||
@ -47,13 +54,24 @@ export const watchRequest = (request) => (dispatch, getState) => {
|
|||||||
dispatch(trackRequest(requestId, request));
|
dispatch(trackRequest(requestId, request));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const trackRequest = (requestId, { transactionHash = null } = {}) => (dispatch, getState) => {
|
export const trackRequest = (requestId, { transactionHash = null, retries = 0 } = {}) => (dispatch, getState) => {
|
||||||
const { api } = getState();
|
const { api } = getState();
|
||||||
|
|
||||||
trackRequestUtil(api, { requestId, transactionHash }, (error, _data = {}) => {
|
trackRequestUtil(api, { requestId, transactionHash }, (error, _data = {}) => {
|
||||||
const data = { ..._data };
|
const data = { ..._data };
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
|
// Retry in 500ms if request not found, max 5 times
|
||||||
|
if (error.type === 'REQUEST_NOT_FOUND') {
|
||||||
|
if (retries > 5) {
|
||||||
|
return dispatch(deleteRequest(requestId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return setTimeout(() => {
|
||||||
|
trackRequest(requestId, { transactionHash, retries: retries + 1 })(dispatch, getState);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
console.error(error);
|
console.error(error);
|
||||||
return dispatch(setRequest(requestId, { error }));
|
return dispatch(setRequest(requestId, { error }));
|
||||||
}
|
}
|
||||||
@ -65,8 +83,9 @@ export const trackRequest = (requestId, { transactionHash = null } = {}) => (dis
|
|||||||
const requestData = requests[requestId];
|
const requestData = requests[requestId];
|
||||||
let blockSubscriptionId = -1;
|
let blockSubscriptionId = -1;
|
||||||
|
|
||||||
// Set the block height to 0 at the beggining
|
// Set the block height to 1 at the beginning (transaction mined,
|
||||||
data.blockHeight = new BigNumber(0);
|
// thus one confirmation)
|
||||||
|
data.blockHeight = new BigNumber(1);
|
||||||
|
|
||||||
// If the request was a contract deployment,
|
// If the request was a contract deployment,
|
||||||
// then add the contract with the saved metadata to the account
|
// then add the contract with the saved metadata to the account
|
||||||
|
@ -48,6 +48,12 @@ let store;
|
|||||||
|
|
||||||
function createApi () {
|
function createApi () {
|
||||||
api = {
|
api = {
|
||||||
|
transport: {
|
||||||
|
on: sinon.stub()
|
||||||
|
},
|
||||||
|
pubsub: {
|
||||||
|
subscribeAndGetResult: sinon.stub().returns(Promise.reject(new Error('not connected')))
|
||||||
|
},
|
||||||
net: {
|
net: {
|
||||||
version: sinon.stub().resolves('2')
|
version: sinon.stub().resolves('2')
|
||||||
},
|
},
|
||||||
|
@ -14,12 +14,11 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual, debounce } from 'lodash';
|
||||||
|
|
||||||
import { LOG_KEYS, getLogger } from '~/config';
|
import { LOG_KEYS, getLogger } from '~/config';
|
||||||
import UpgradeStore from '~/modals/UpgradeParity/store';
|
// import UpgradeStore from '~/modals/UpgradeParity/store';
|
||||||
|
|
||||||
import BalancesProvider from './balances';
|
|
||||||
import { statusBlockNumber, statusCollection } from './statusActions';
|
import { statusBlockNumber, statusCollection } from './statusActions';
|
||||||
|
|
||||||
const log = getLogger(LOG_KEYS.Signer);
|
const log = getLogger(LOG_KEYS.Signer);
|
||||||
@ -31,7 +30,6 @@ const STATUS_BAD = 'bad';
|
|||||||
|
|
||||||
export default class Status {
|
export default class Status {
|
||||||
_apiStatus = {};
|
_apiStatus = {};
|
||||||
_status = {};
|
|
||||||
_longStatus = {};
|
_longStatus = {};
|
||||||
_minerSettings = {};
|
_minerSettings = {};
|
||||||
_timeoutIds = {};
|
_timeoutIds = {};
|
||||||
@ -41,21 +39,14 @@ export default class Status {
|
|||||||
constructor (store, api) {
|
constructor (store, api) {
|
||||||
this._api = api;
|
this._api = api;
|
||||||
this._store = store;
|
this._store = store;
|
||||||
this._upgradeStore = UpgradeStore.get(api);
|
// this._upgradeStore = UpgradeStore.get(api);
|
||||||
|
|
||||||
// On connecting, stop all subscriptions
|
|
||||||
api.on('connecting', this.stop, this);
|
|
||||||
|
|
||||||
// On connected, start the subscriptions
|
|
||||||
api.on('connected', this.start, this);
|
|
||||||
|
|
||||||
// On disconnected, stop all subscriptions
|
|
||||||
api.on('disconnected', this.stop, this);
|
|
||||||
|
|
||||||
this.updateApiStatus();
|
this.updateApiStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
static instantiate (store, api) {
|
static init (store) {
|
||||||
|
const { api } = store.getState();
|
||||||
|
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
instance = new Status(store, api);
|
instance = new Status(store, api);
|
||||||
}
|
}
|
||||||
@ -63,59 +54,61 @@ export default class Status {
|
|||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get () {
|
static get (store) {
|
||||||
if (!instance) {
|
if (!instance && store) {
|
||||||
|
return Status.init(store);
|
||||||
|
} else if (!instance) {
|
||||||
throw new Error('The Status Provider has not been initialized yet');
|
throw new Error('The Status Provider has not been initialized yet');
|
||||||
}
|
}
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
start () {
|
static start () {
|
||||||
|
const self = instance;
|
||||||
|
|
||||||
log.debug('status::start');
|
log.debug('status::start');
|
||||||
|
|
||||||
Promise
|
const promises = [
|
||||||
.all([
|
self._subscribeBlockNumber(),
|
||||||
this._subscribeBlockNumber(),
|
self._subscribeNetPeers(),
|
||||||
|
self._subscribeEthSyncing(),
|
||||||
|
self._subscribeNodeHealth(),
|
||||||
|
self._pollLongStatus(),
|
||||||
|
self._pollApiStatus()
|
||||||
|
];
|
||||||
|
|
||||||
this._pollLongStatus(),
|
return Status.stop()
|
||||||
this._pollStatus()
|
.then(() => Promise.all(promises));
|
||||||
])
|
|
||||||
.then(() => {
|
|
||||||
return BalancesProvider.start();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stop () {
|
static stop () {
|
||||||
|
if (!instance) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
const self = instance;
|
||||||
|
|
||||||
log.debug('status::stop');
|
log.debug('status::stop');
|
||||||
|
|
||||||
const promises = [];
|
self._clearTimeouts();
|
||||||
|
|
||||||
if (this._blockNumberSubscriptionId) {
|
return self._unsubscribeBlockNumber()
|
||||||
const promise = this._api
|
|
||||||
.unsubscribe(this._blockNumberSubscriptionId)
|
|
||||||
.then(() => {
|
|
||||||
this._blockNumberSubscriptionId = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
promises.push(promise);
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.values(this._timeoutIds).forEach((timeoutId) => {
|
|
||||||
clearTimeout(timeoutId);
|
|
||||||
});
|
|
||||||
|
|
||||||
const promise = BalancesProvider.stop();
|
|
||||||
|
|
||||||
promises.push(promise);
|
|
||||||
|
|
||||||
return Promise.all(promises)
|
|
||||||
.then(() => true)
|
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('status::stop', error);
|
console.error('status::stop', error);
|
||||||
return true;
|
|
||||||
})
|
})
|
||||||
.then(() => this.updateApiStatus());
|
.then(() => self.updateApiStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
getApiStatus = () => {
|
||||||
|
const { isConnected, isConnecting, needsToken, secureToken } = this._api;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isConnected,
|
||||||
|
isConnecting,
|
||||||
|
needsToken,
|
||||||
|
secureToken
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
updateApiStatus () {
|
updateApiStatus () {
|
||||||
@ -129,6 +122,33 @@ export default class Status {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_clearTimeouts () {
|
||||||
|
Object.values(this._timeoutIds).forEach((timeoutId) => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_overallStatus (health) {
|
||||||
|
const allWithTime = [health.peers, health.sync, health.time].filter(x => x);
|
||||||
|
const all = [health.peers, health.sync].filter(x => x);
|
||||||
|
const statuses = all.map(x => x.status);
|
||||||
|
const bad = statuses.find(x => x === STATUS_BAD);
|
||||||
|
const needsAttention = statuses.find(x => x === STATUS_WARN);
|
||||||
|
const message = allWithTime.map(x => x.message).filter(x => x);
|
||||||
|
|
||||||
|
if (all.length) {
|
||||||
|
return {
|
||||||
|
status: bad || needsAttention || STATUS_OK,
|
||||||
|
message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: STATUS_BAD,
|
||||||
|
message: ['Unable to fetch node health.']
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
_subscribeBlockNumber = () => {
|
_subscribeBlockNumber = () => {
|
||||||
return this._api
|
return this._api
|
||||||
.subscribe('eth_blockNumber', (error, blockNumber) => {
|
.subscribe('eth_blockNumber', (error, blockNumber) => {
|
||||||
@ -159,92 +179,81 @@ export default class Status {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_pollTraceMode = () => {
|
_updateStatus = debounce(status => {
|
||||||
return this._api.trace
|
this._store.dispatch(statusCollection(status));
|
||||||
.block()
|
}, 2500, {
|
||||||
.then(blockTraces => {
|
maxWait: 5000
|
||||||
// Assumes not in Trace Mode if no transactions
|
});
|
||||||
// in latest block...
|
|
||||||
return blockTraces.length > 0;
|
_subscribeEthSyncing = () => {
|
||||||
})
|
return this._api.pubsub
|
||||||
.catch(() => false);
|
.eth
|
||||||
|
.syncing((error, syncing) => {
|
||||||
|
if (error) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
getApiStatus = () => {
|
this._updateStatus({ syncing });
|
||||||
const { isConnected, isConnecting, needsToken, secureToken } = this._api;
|
});
|
||||||
|
|
||||||
return {
|
|
||||||
isConnected,
|
|
||||||
isConnecting,
|
|
||||||
needsToken,
|
|
||||||
secureToken
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_pollStatus = () => {
|
_subscribeNetPeers = () => {
|
||||||
const nextTimeout = (timeout = 1000) => {
|
return this._api.pubsub
|
||||||
if (this._timeoutIds.status) {
|
.parity
|
||||||
clearTimeout(this._timeoutIds.status);
|
.netPeers((error, netPeers) => {
|
||||||
|
if (error || !netPeers) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._timeoutIds.status = setTimeout(() => this._pollStatus(), timeout);
|
this._store.dispatch(statusCollection({ netPeers }));
|
||||||
};
|
});
|
||||||
|
|
||||||
this.updateApiStatus();
|
|
||||||
|
|
||||||
if (!this._api.isConnected) {
|
|
||||||
nextTimeout(250);
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusPromises = [
|
_subscribeNodeHealth = () => {
|
||||||
this._api.eth.syncing(),
|
return this._api.pubsub
|
||||||
this._api.parity.netPeers(),
|
.parity
|
||||||
this._api.parity.nodeHealth()
|
.nodeHealth((error, health) => {
|
||||||
];
|
if (error || !health) {
|
||||||
|
return;
|
||||||
return Promise
|
}
|
||||||
.all(statusPromises)
|
|
||||||
.then(([ syncing, netPeers, health ]) => {
|
|
||||||
const status = { netPeers, syncing, health };
|
|
||||||
|
|
||||||
health.overall = this._overallStatus(health);
|
health.overall = this._overallStatus(health);
|
||||||
health.peers = health.peers || {};
|
health.peers = health.peers || {};
|
||||||
health.sync = health.sync || {};
|
health.sync = health.sync || {};
|
||||||
health.time = health.time || {};
|
health.time = health.time || {};
|
||||||
|
|
||||||
if (!isEqual(status, this._status)) {
|
this._store.dispatch(statusCollection({ health }));
|
||||||
this._store.dispatch(statusCollection(status));
|
|
||||||
this._status = status;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('_pollStatus', error);
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
nextTimeout();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_overallStatus = (health) => {
|
_unsubscribeBlockNumber () {
|
||||||
const allWithTime = [health.peers, health.sync, health.time].filter(x => x);
|
if (this._blockNumberSubscriptionId) {
|
||||||
const all = [health.peers, health.sync].filter(x => x);
|
return this._api
|
||||||
const statuses = all.map(x => x.status);
|
.unsubscribe(this._blockNumberSubscriptionId)
|
||||||
const bad = statuses.find(x => x === STATUS_BAD);
|
.then(() => {
|
||||||
const needsAttention = statuses.find(x => x === STATUS_WARN);
|
this._blockNumberSubscriptionId = null;
|
||||||
const message = allWithTime.map(x => x.message).filter(x => x);
|
});
|
||||||
|
|
||||||
if (all.length) {
|
|
||||||
return {
|
|
||||||
status: bad || needsAttention || STATUS_OK,
|
|
||||||
message
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return Promise.resolve();
|
||||||
status: STATUS_BAD,
|
}
|
||||||
message: ['Unable to fetch node health.']
|
|
||||||
|
_pollApiStatus = () => {
|
||||||
|
const nextTimeout = (timeout = 1000) => {
|
||||||
|
if (this._timeoutIds.status) {
|
||||||
|
clearTimeout(this._timeoutIds.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._timeoutIds.status = setTimeout(() => this._pollApiStatus(), timeout);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.updateApiStatus();
|
||||||
|
|
||||||
|
if (!this._api.isConnected) {
|
||||||
|
nextTimeout(250);
|
||||||
|
} else {
|
||||||
|
nextTimeout();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -259,7 +268,7 @@ export default class Status {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { nodeKindFull } = this._store.getState().nodeStatus;
|
const { nodeKindFull } = this._store.getState().nodeStatus;
|
||||||
const defaultTimeout = (nodeKindFull === false ? 240 : 30) * 1000;
|
const defaultTimeout = (nodeKindFull === false ? 240 : 60) * 1000;
|
||||||
|
|
||||||
const nextTimeout = (timeout = defaultTimeout) => {
|
const nextTimeout = (timeout = defaultTimeout) => {
|
||||||
if (this._timeoutIds.longStatus) {
|
if (this._timeoutIds.longStatus) {
|
||||||
@ -271,19 +280,18 @@ export default class Status {
|
|||||||
|
|
||||||
const statusPromises = [
|
const statusPromises = [
|
||||||
this._api.parity.nodeKind(),
|
this._api.parity.nodeKind(),
|
||||||
this._api.parity.netPeers(),
|
|
||||||
this._api.web3.clientVersion(),
|
this._api.web3.clientVersion(),
|
||||||
this._api.net.version(),
|
this._api.net.version(),
|
||||||
this._api.parity.netChain()
|
this._api.parity.netChain()
|
||||||
];
|
];
|
||||||
|
|
||||||
if (nodeKindFull) {
|
// if (nodeKindFull) {
|
||||||
statusPromises.push(this._upgradeStore.checkUpgrade());
|
// statusPromises.push(this._upgradeStore.checkUpgrade());
|
||||||
}
|
// }
|
||||||
|
|
||||||
return Promise
|
return Promise
|
||||||
.all(statusPromises)
|
.all(statusPromises)
|
||||||
.then(([nodeKind, netPeers, clientVersion, netVersion, netChain]) => {
|
.then(([nodeKind, clientVersion, netVersion, netChain]) => {
|
||||||
const isTest = [
|
const isTest = [
|
||||||
'2', // morden
|
'2', // morden
|
||||||
'3', // ropsten,
|
'3', // ropsten,
|
||||||
@ -298,7 +306,6 @@ export default class Status {
|
|||||||
const longStatus = {
|
const longStatus = {
|
||||||
nodeKind,
|
nodeKind,
|
||||||
nodeKindFull,
|
nodeKindFull,
|
||||||
netPeers,
|
|
||||||
clientVersion,
|
clientVersion,
|
||||||
netChain,
|
netChain,
|
||||||
netVersion,
|
netVersion,
|
||||||
@ -310,11 +317,12 @@ export default class Status {
|
|||||||
this._longStatus = longStatus;
|
this._longStatus = longStatus;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.then(() => {
|
||||||
|
nextTimeout();
|
||||||
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('_pollLongStatus', error);
|
console.error('_pollLongStatus', error);
|
||||||
})
|
nextTimeout(30000);
|
||||||
.then(() => {
|
|
||||||
nextTimeout(60000);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
166
js-old/src/redux/providers/tokens.js
Normal file
166
js-old/src/redux/providers/tokens.js
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { updateTokensFilter } from './balancesActions';
|
||||||
|
import { loadTokens, fetchTokens } from './tokensActions';
|
||||||
|
import { padRight } from '~/api/util/format';
|
||||||
|
|
||||||
|
import { LOG_KEYS, getLogger } from '~/config';
|
||||||
|
import Contracts from '~/contracts';
|
||||||
|
|
||||||
|
const log = getLogger(LOG_KEYS.Balances);
|
||||||
|
|
||||||
|
let instance = null;
|
||||||
|
|
||||||
|
export default class Tokens {
|
||||||
|
constructor (store, api) {
|
||||||
|
this._api = api;
|
||||||
|
this._store = store;
|
||||||
|
|
||||||
|
this._tokenreg = null;
|
||||||
|
this._tokenregSubs = [];
|
||||||
|
|
||||||
|
this._loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get loading () {
|
||||||
|
return this._loading;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get (store) {
|
||||||
|
if (!instance && store) {
|
||||||
|
return Tokens.init(store);
|
||||||
|
} else if (!instance) {
|
||||||
|
throw new Error('The Tokens Provider has not been initialized yet');
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static init (store) {
|
||||||
|
const { api } = store.getState();
|
||||||
|
|
||||||
|
if (!instance) {
|
||||||
|
instance = new Tokens(store, api);
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
static start () {
|
||||||
|
if (!instance) {
|
||||||
|
return Promise.reject('Tokens Provider has not been initiated yet');
|
||||||
|
}
|
||||||
|
|
||||||
|
const self = instance;
|
||||||
|
|
||||||
|
self._loading = true;
|
||||||
|
|
||||||
|
// Unsubscribe from previous subscriptions
|
||||||
|
return Tokens.stop()
|
||||||
|
.then(() => self.loadTokens())
|
||||||
|
.then(() => {
|
||||||
|
self._loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static stop () {
|
||||||
|
if (!instance) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
const self = instance;
|
||||||
|
|
||||||
|
// Unsubscribe without adding the promises
|
||||||
|
// to the result, since it would have to wait for a
|
||||||
|
// reconnection to resolve if the Node is disconnected
|
||||||
|
if (self._tokenreg) {
|
||||||
|
const tokenregPromises = self._tokenregSubs
|
||||||
|
.map((tokenregSID) => self._tokenreg.unsubscribe(tokenregSID));
|
||||||
|
|
||||||
|
Promise.all(tokenregPromises)
|
||||||
|
.then(() => {
|
||||||
|
self._tokenregSubs = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
attachToTokensEvents (tokenreg) {
|
||||||
|
const metaTopics = [ null, padRight(this._api.util.asciiToHex('IMG'), 32) ];
|
||||||
|
|
||||||
|
return Promise
|
||||||
|
.all([
|
||||||
|
this._attachToTokenregEvents(tokenreg, 'Registered'),
|
||||||
|
this._attachToTokenregEvents(tokenreg, 'MetaChanged', metaTopics)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTokenRegistry () {
|
||||||
|
return Contracts.get().tokenReg.getContract();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadTokens (options = {}) {
|
||||||
|
const { dispatch, getState } = this._store;
|
||||||
|
|
||||||
|
return this
|
||||||
|
.getTokenRegistry()
|
||||||
|
.then((tokenreg) => {
|
||||||
|
this._tokenreg = tokenreg;
|
||||||
|
|
||||||
|
return loadTokens(options)(dispatch, getState);
|
||||||
|
})
|
||||||
|
.then(() => updateTokensFilter()(dispatch, getState))
|
||||||
|
.then(() => this.attachToTokensEvents(this._tokenreg))
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn('balances::loadTokens', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_attachToTokenregEvents (tokenreg, event, topics = []) {
|
||||||
|
if (this._tokenregSID) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenreg.instance[event]
|
||||||
|
.subscribe({
|
||||||
|
fromBlock: 'latest',
|
||||||
|
toBlock: 'latest',
|
||||||
|
topics: topics,
|
||||||
|
skipInitFetch: true
|
||||||
|
}, (error, logs) => {
|
||||||
|
if (error) {
|
||||||
|
return console.error('balances::attachToNewToken', 'failed to attach to tokenreg Registered', error.toString(), error.stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._handleTokensLogs(logs);
|
||||||
|
})
|
||||||
|
.then((tokenregSID) => {
|
||||||
|
this._tokenregSubs.push(tokenregSID);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleTokensLogs (logs) {
|
||||||
|
const { dispatch, getState } = this._store;
|
||||||
|
const tokenIds = logs.map((log) => log.params.id.value.toNumber());
|
||||||
|
|
||||||
|
log.debug('got TokenRegistry logs', logs, tokenIds);
|
||||||
|
|
||||||
|
return fetchTokens(tokenIds)(dispatch, getState)
|
||||||
|
.then(() => updateTokensFilter()(dispatch, getState));
|
||||||
|
}
|
||||||
|
}
|
@ -14,56 +14,223 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { uniq } from 'lodash';
|
import { chunk, uniq } from 'lodash';
|
||||||
|
import store from 'store';
|
||||||
|
|
||||||
import Contracts from '~/contracts';
|
import Contracts from '~/contracts';
|
||||||
import { LOG_KEYS, getLogger } from '~/config';
|
import { LOG_KEYS, getLogger } from '~/config';
|
||||||
import { fetchTokenIds, fetchTokenInfo } from '~/util/tokens';
|
import { fetchTokenIds, fetchTokensBasics, fetchTokensInfo, fetchTokensImages } from '~/util/tokens';
|
||||||
|
|
||||||
import { updateTokensFilter } from './balancesActions';
|
|
||||||
import { setAddressImage } from './imagesActions';
|
import { setAddressImage } from './imagesActions';
|
||||||
|
|
||||||
|
const TOKENS_CACHE_LS_KEY_PREFIX = '_parity::tokens::';
|
||||||
const log = getLogger(LOG_KEYS.Balances);
|
const log = getLogger(LOG_KEYS.Balances);
|
||||||
|
|
||||||
export function setTokens (tokens) {
|
function _setTokens (tokens) {
|
||||||
return {
|
return {
|
||||||
type: 'setTokens',
|
type: 'setTokens',
|
||||||
tokens
|
tokens
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setTokens (nextTokens) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const { nodeStatus, tokens: prevTokens } = getState();
|
||||||
|
const { tokenReg } = Contracts.get();
|
||||||
|
const tokens = {
|
||||||
|
...prevTokens,
|
||||||
|
...nextTokens
|
||||||
|
};
|
||||||
|
|
||||||
|
return tokenReg.getContract()
|
||||||
|
.then((tokenRegContract) => {
|
||||||
|
const lsKey = TOKENS_CACHE_LS_KEY_PREFIX + nodeStatus.netChain;
|
||||||
|
|
||||||
|
store.set(lsKey, {
|
||||||
|
tokenreg: tokenRegContract.address,
|
||||||
|
tokens
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
dispatch(_setTokens(nextTokens));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadCachedTokens (tokenRegContract) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const { nodeStatus } = getState();
|
||||||
|
|
||||||
|
const lsKey = TOKENS_CACHE_LS_KEY_PREFIX + nodeStatus.netChain;
|
||||||
|
const cached = store.get(lsKey);
|
||||||
|
|
||||||
|
if (cached) {
|
||||||
|
// Check if we have data from the right contract
|
||||||
|
if (cached.tokenreg === tokenRegContract.address && cached.tokens) {
|
||||||
|
log.debug('found cached tokens', cached.tokens);
|
||||||
|
|
||||||
|
// Fetch all the tokens images on load
|
||||||
|
// (it's the only thing that might have changed)
|
||||||
|
const tokenIndexes = Object.values(cached.tokens)
|
||||||
|
.filter((t) => t && t.fetched)
|
||||||
|
.map((t) => t.index);
|
||||||
|
|
||||||
|
fetchTokensData(tokenRegContract, tokenIndexes)(dispatch, getState);
|
||||||
|
} else {
|
||||||
|
store.remove(lsKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function loadTokens (options = {}) {
|
export function loadTokens (options = {}) {
|
||||||
log.debug('loading tokens', Object.keys(options).length ? options : '');
|
log.debug('loading tokens', Object.keys(options).length ? options : '');
|
||||||
|
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const { tokenReg } = Contracts.get();
|
const { tokenReg } = Contracts.get();
|
||||||
|
|
||||||
tokenReg.getInstance()
|
return tokenReg.getContract()
|
||||||
.then((tokenRegInstance) => {
|
.then((tokenRegContract) => {
|
||||||
return fetchTokenIds(tokenRegInstance);
|
loadCachedTokens(tokenRegContract)(dispatch, getState);
|
||||||
|
return fetchTokenIds(tokenRegContract.instance);
|
||||||
})
|
})
|
||||||
.then((tokenIndexes) => dispatch(fetchTokens(tokenIndexes, options)))
|
.then((tokenIndexes) => loadTokensBasics(tokenIndexes, options)(dispatch, getState))
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.warn('tokens::loadTokens', error);
|
console.warn('tokens::loadTokens', error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchTokens (_tokenIndexes, options = {}) {
|
export function loadTokensBasics (tokenIndexes, options) {
|
||||||
const tokenIndexes = uniq(_tokenIndexes || []);
|
const limit = 64;
|
||||||
|
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const { api } = getState();
|
||||||
|
const { tokenReg } = Contracts.get();
|
||||||
|
const nextTokens = {};
|
||||||
|
const count = tokenIndexes.length;
|
||||||
|
|
||||||
|
log.debug('loading basic tokens', tokenIndexes);
|
||||||
|
|
||||||
|
if (count === 0) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokenReg.getContract()
|
||||||
|
.then((tokenRegContract) => {
|
||||||
|
let promise = Promise.resolve();
|
||||||
|
const first = tokenIndexes[0];
|
||||||
|
const last = tokenIndexes[tokenIndexes.length - 1];
|
||||||
|
|
||||||
|
for (let from = first; from <= last; from += limit) {
|
||||||
|
// No need to fetch `limit` elements
|
||||||
|
const lowerLimit = Math.min(limit, last - from + 1);
|
||||||
|
|
||||||
|
promise = promise
|
||||||
|
.then(() => fetchTokensBasics(api, tokenRegContract, from, lowerLimit))
|
||||||
|
.then((results) => {
|
||||||
|
results
|
||||||
|
.forEach((token) => {
|
||||||
|
nextTokens[token.id] = token;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
log.debug('fetched tokens basic info', nextTokens);
|
||||||
|
|
||||||
|
dispatch(setTokens(nextTokens));
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn('tokens::fetchTokens', error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchTokens (_tokenIndexes) {
|
||||||
|
const tokenIndexes = uniq(_tokenIndexes || []);
|
||||||
|
const tokenChunks = chunk(tokenIndexes, 64);
|
||||||
|
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const { api, images } = getState();
|
|
||||||
const { tokenReg } = Contracts.get();
|
const { tokenReg } = Contracts.get();
|
||||||
|
|
||||||
return tokenReg.getInstance()
|
return tokenReg.getContract()
|
||||||
.then((tokenRegInstance) => {
|
.then((tokenRegContract) => {
|
||||||
const promises = tokenIndexes.map((id) => fetchTokenInfo(api, tokenRegInstance, id));
|
let promise = Promise.resolve();
|
||||||
|
|
||||||
return Promise.all(promises);
|
tokenChunks.forEach((tokenChunk) => {
|
||||||
|
promise = promise
|
||||||
|
.then(() => fetchTokensData(tokenRegContract, tokenChunk)(dispatch, getState));
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise;
|
||||||
})
|
})
|
||||||
.then((results) => {
|
.then(() => {
|
||||||
const tokens = results
|
log.debug('fetched token', getState().tokens);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.warn('tokens::fetchTokens', error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Split the given token indexes between those for whom
|
||||||
|
* we already have some info, and thus just need to fetch
|
||||||
|
* the image, and those for whom we don't have anything and
|
||||||
|
* need to fetch all the info.
|
||||||
|
*/
|
||||||
|
function fetchTokensData (tokenRegContract, tokenIndexes) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const { api, tokens, images } = getState();
|
||||||
|
const allTokens = Object.values(tokens);
|
||||||
|
|
||||||
|
const tokensIndexesMap = allTokens
|
||||||
|
.reduce((map, token) => {
|
||||||
|
map[token.index] = token;
|
||||||
|
return map;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const fetchedTokenIndexes = allTokens
|
||||||
|
.filter((token) => token.fetched)
|
||||||
|
.map((token) => token.index);
|
||||||
|
|
||||||
|
const fullIndexes = [];
|
||||||
|
const partialIndexes = [];
|
||||||
|
|
||||||
|
tokenIndexes.forEach((tokenIndex) => {
|
||||||
|
if (fetchedTokenIndexes.includes(tokenIndex)) {
|
||||||
|
partialIndexes.push(tokenIndex);
|
||||||
|
} else {
|
||||||
|
fullIndexes.push(tokenIndex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
log.debug('need to fully fetch', fullIndexes);
|
||||||
|
log.debug('need to partially fetch', partialIndexes);
|
||||||
|
|
||||||
|
const fullPromise = fetchTokensInfo(api, tokenRegContract, fullIndexes);
|
||||||
|
const partialPromise = fetchTokensImages(api, tokenRegContract, partialIndexes)
|
||||||
|
.then((imagesResult) => {
|
||||||
|
return imagesResult.map((image, index) => {
|
||||||
|
const tokenIndex = partialIndexes[index];
|
||||||
|
const token = tokensIndexesMap[tokenIndex];
|
||||||
|
|
||||||
|
return { ...token, image };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all([ fullPromise, partialPromise ])
|
||||||
|
.then(([ fullResults, partialResults ]) => {
|
||||||
|
log.debug('fetched', { fullResults, partialResults });
|
||||||
|
|
||||||
|
return [].concat(fullResults, partialResults)
|
||||||
|
.filter(({ address }) => !/0x0*$/.test(address))
|
||||||
.reduce((tokens, token) => {
|
.reduce((tokens, token) => {
|
||||||
const { id, image, address } = token;
|
const { id, image, address } = token;
|
||||||
|
|
||||||
@ -75,14 +242,9 @@ export function fetchTokens (_tokenIndexes, options = {}) {
|
|||||||
tokens[id] = token;
|
tokens[id] = token;
|
||||||
return tokens;
|
return tokens;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
log.debug('fetched token', tokens);
|
|
||||||
|
|
||||||
dispatch(setTokens(tokens));
|
|
||||||
dispatch(updateTokensFilter(null, null, options));
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.then((tokens) => {
|
||||||
console.warn('tokens::fetchTokens', error);
|
dispatch(setTokens(tokens));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -25,10 +25,15 @@ const initialState = {
|
|||||||
export default handleActions({
|
export default handleActions({
|
||||||
setTokens (state, action) {
|
setTokens (state, action) {
|
||||||
const { tokens } = action;
|
const { tokens } = action;
|
||||||
|
const nextTokens = { ...state };
|
||||||
|
|
||||||
return {
|
Object.keys(tokens).forEach((tokenId) => {
|
||||||
...state,
|
nextTokens[tokenId] = {
|
||||||
...tokens
|
...(nextTokens[tokenId]),
|
||||||
|
...tokens[tokenId]
|
||||||
};
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return nextTokens;
|
||||||
}
|
}
|
||||||
}, initialState);
|
}, initialState);
|
||||||
|
@ -22,12 +22,14 @@ import initReducers from './reducers';
|
|||||||
import { load as loadWallet } from './providers/walletActions';
|
import { load as loadWallet } from './providers/walletActions';
|
||||||
import { init as initRequests } from './providers/requestsActions';
|
import { init as initRequests } from './providers/requestsActions';
|
||||||
import { setupWorker } from './providers/workerWrapper';
|
import { setupWorker } from './providers/workerWrapper';
|
||||||
|
import { setApi } from './providers/apiActions';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Balances as BalancesProvider,
|
Balances as BalancesProvider,
|
||||||
Personal as PersonalProvider,
|
Personal as PersonalProvider,
|
||||||
Signer as SignerProvider,
|
Signer as SignerProvider,
|
||||||
Status as StatusProvider
|
Status as StatusProvider,
|
||||||
|
Tokens as TokensProvider
|
||||||
} from './providers';
|
} from './providers';
|
||||||
|
|
||||||
const storeCreation = window.devToolsExtension
|
const storeCreation = window.devToolsExtension
|
||||||
@ -39,14 +41,59 @@ export default function (api, browserHistory, forEmbed = false) {
|
|||||||
const middleware = initMiddleware(api, browserHistory, forEmbed);
|
const middleware = initMiddleware(api, browserHistory, forEmbed);
|
||||||
const store = applyMiddleware(...middleware)(storeCreation)(reducers);
|
const store = applyMiddleware(...middleware)(storeCreation)(reducers);
|
||||||
|
|
||||||
BalancesProvider.instantiate(store, api);
|
// Add the `api` to the Redux Store
|
||||||
StatusProvider.instantiate(store, api);
|
store.dispatch({ type: 'initAll', api });
|
||||||
new PersonalProvider(store, api).start();
|
store.dispatch(setApi(api));
|
||||||
|
|
||||||
|
// Initialise the Store Providers
|
||||||
|
BalancesProvider.init(store);
|
||||||
|
PersonalProvider.init(store);
|
||||||
|
StatusProvider.init(store);
|
||||||
|
TokensProvider.init(store);
|
||||||
|
|
||||||
new SignerProvider(store, api).start();
|
new SignerProvider(store, api).start();
|
||||||
|
|
||||||
store.dispatch(loadWallet(api));
|
store.dispatch(loadWallet(api));
|
||||||
store.dispatch(initRequests(api));
|
store.dispatch(initRequests(api));
|
||||||
setupWorker(store);
|
setupWorker(store);
|
||||||
|
|
||||||
|
const start = () => {
|
||||||
|
return Promise
|
||||||
|
.resolve()
|
||||||
|
.then(() => console.log('v1: starting Status Provider...'))
|
||||||
|
.then(() => StatusProvider.start())
|
||||||
|
.then(() => console.log('v1: started Status Provider'))
|
||||||
|
|
||||||
|
.then(() => console.log('v1: starting Personal Provider...'))
|
||||||
|
.then(() => PersonalProvider.start())
|
||||||
|
.then(() => console.log('v1: started Personal Provider'))
|
||||||
|
|
||||||
|
.then(() => console.log('v1: starting Balances Provider...'))
|
||||||
|
.then(() => BalancesProvider.start())
|
||||||
|
.then(() => console.log('v1: started Balances Provider'))
|
||||||
|
|
||||||
|
.then(() => console.log('v1: starting Tokens Provider...'))
|
||||||
|
.then(() => TokensProvider.start())
|
||||||
|
.then(() => console.log('v1: started Tokens Provider'));
|
||||||
|
};
|
||||||
|
|
||||||
|
const stop = () => {
|
||||||
|
return StatusProvider
|
||||||
|
.stop()
|
||||||
|
.then(() => PersonalProvider.stop())
|
||||||
|
.then(() => TokensProvider.stop())
|
||||||
|
.then(() => BalancesProvider.stop());
|
||||||
|
};
|
||||||
|
|
||||||
|
// On connected, start the subscriptions
|
||||||
|
api.on('connected', start);
|
||||||
|
|
||||||
|
// On disconnected, stop all subscriptions
|
||||||
|
api.on('disconnected', stop);
|
||||||
|
|
||||||
|
if (api.isConnected) {
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,12 @@
|
|||||||
import Push from 'push.js';
|
import Push from 'push.js';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
|
|
||||||
import unkownIcon from '~/../assets/images/contracts/unknown-64x64.png';
|
import unknownIcon from '~/../assets/images/contracts/unknown-64x64.png';
|
||||||
|
|
||||||
export function notifyTransaction (account, token, _value, onClick) {
|
export function notifyTransaction (account, token, _value, onClick) {
|
||||||
const name = account.name || account.address;
|
const name = account.name || account.address;
|
||||||
const value = _value.div(new BigNumber(token.format || 1));
|
const value = _value.div(new BigNumber(token.format || 1));
|
||||||
const icon = token.image || unkownIcon;
|
const icon = token.image || unknownIcon;
|
||||||
|
|
||||||
let _notification = null;
|
let _notification = null;
|
||||||
|
|
||||||
|
@ -1,133 +0,0 @@
|
|||||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
|
||||||
// This file is part of Parity.
|
|
||||||
|
|
||||||
// Parity is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
|
|
||||||
// Parity is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import { range } from 'lodash';
|
|
||||||
import BigNumber from 'bignumber.js';
|
|
||||||
|
|
||||||
import { hashToImageUrl } from '~/redux/util';
|
|
||||||
import { sha3 } from '~/api/util/sha3';
|
|
||||||
import imagesEthereum from '~/../assets/images/contracts/ethereum-black-64x64.png';
|
|
||||||
|
|
||||||
const BALANCEOF_SIGNATURE = sha3('balanceOf(address)');
|
|
||||||
const ADDRESS_PADDING = range(24).map(() => '0').join('');
|
|
||||||
|
|
||||||
export const ETH_TOKEN = {
|
|
||||||
address: '',
|
|
||||||
format: new BigNumber(10).pow(18),
|
|
||||||
id: sha3('eth_native_token').slice(0, 10),
|
|
||||||
image: imagesEthereum,
|
|
||||||
name: 'Ethereum',
|
|
||||||
native: true,
|
|
||||||
tag: 'ETH'
|
|
||||||
};
|
|
||||||
|
|
||||||
export function fetchTokenIds (tokenregInstance) {
|
|
||||||
return tokenregInstance.tokenCount
|
|
||||||
.call()
|
|
||||||
.then((numTokens) => {
|
|
||||||
const tokenIndexes = range(numTokens.toNumber());
|
|
||||||
|
|
||||||
return tokenIndexes;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchTokenInfo (api, tokenregInstace, tokenIndex) {
|
|
||||||
return Promise
|
|
||||||
.all([
|
|
||||||
tokenregInstace.token.call({}, [tokenIndex]),
|
|
||||||
tokenregInstace.meta.call({}, [tokenIndex, 'IMG'])
|
|
||||||
])
|
|
||||||
.then(([ tokenData, image ]) => {
|
|
||||||
const [ address, tag, format, name ] = tokenData;
|
|
||||||
|
|
||||||
const token = {
|
|
||||||
format: format.toString(),
|
|
||||||
index: tokenIndex,
|
|
||||||
image: hashToImageUrl(image),
|
|
||||||
id: sha3(address + tokenIndex).slice(0, 10),
|
|
||||||
address,
|
|
||||||
name,
|
|
||||||
tag
|
|
||||||
};
|
|
||||||
|
|
||||||
return token;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `updates` should be in the shape:
|
|
||||||
* {
|
|
||||||
* [ who ]: [ tokenId ] // Array of tokens to updates
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* Returns a Promise resolved witht the balances in the shape:
|
|
||||||
* {
|
|
||||||
* [ who ]: { [ tokenId ]: BigNumber } // The balances of `who`
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
export function fetchAccountsBalances (api, tokens, updates) {
|
|
||||||
const addresses = Object.keys(updates);
|
|
||||||
const promises = addresses
|
|
||||||
.map((who) => {
|
|
||||||
const tokensIds = updates[who];
|
|
||||||
const tokensToUpdate = tokensIds.map((tokenId) => tokens.find((t) => t.id === tokenId));
|
|
||||||
|
|
||||||
return fetchAccountBalances(api, tokensToUpdate, who);
|
|
||||||
});
|
|
||||||
|
|
||||||
return Promise.all(promises)
|
|
||||||
.then((results) => {
|
|
||||||
return results.reduce((balances, accountBalances, index) => {
|
|
||||||
balances[addresses[index]] = accountBalances;
|
|
||||||
return balances;
|
|
||||||
}, {});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a Promise resolved with the balances in the shape:
|
|
||||||
* {
|
|
||||||
* [ tokenId ]: BigNumber // Token balance value
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
export function fetchAccountBalances (api, tokens, who) {
|
|
||||||
const calldata = '0x' + BALANCEOF_SIGNATURE.slice(2, 10) + ADDRESS_PADDING + who.slice(2);
|
|
||||||
const promises = tokens.map((token) => fetchTokenBalance(api, token, { who, calldata }));
|
|
||||||
|
|
||||||
return Promise.all(promises)
|
|
||||||
.then((results) => {
|
|
||||||
return results.reduce((balances, value, index) => {
|
|
||||||
const token = tokens[index];
|
|
||||||
|
|
||||||
balances[token.id] = value;
|
|
||||||
return balances;
|
|
||||||
}, {});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchTokenBalance (api, token, { who, calldata }) {
|
|
||||||
if (token.native) {
|
|
||||||
return api.eth.getBalance(who);
|
|
||||||
}
|
|
||||||
|
|
||||||
return api.eth
|
|
||||||
.call({ data: calldata, to: token.address })
|
|
||||||
.then((result) => {
|
|
||||||
const cleanResult = result.replace(/^0x/, '');
|
|
||||||
|
|
||||||
return new BigNumber(`0x${cleanResult || 0}`);
|
|
||||||
});
|
|
||||||
}
|
|
23
js-old/src/util/tokens/bytecodes.js
Normal file
23
js-old/src/util/tokens/bytecodes.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// build from : https://raw.githubusercontent.com/paritytech/contracts/4c8501e908166aab7ff4d2ebb05db61b5d017024/TokenCalls.sol
|
||||||
|
// metadata (include build version and options):
|
||||||
|
// {"compiler":{"version":"0.4.16+commit.d7661dd9"},"language":"Solidity","output":{"abi":[{"inputs":[{"name":"tokenRegAddress","type":"address"},{"name":"start","type":"uint256"},{"name":"limit","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}],"devdoc":{"methods":{}},"userdoc":{"methods":{}}},"settings":{"compilationTarget":{"":"Tokens"},"libraries":{},"optimizer":{"enabled":true,"runs":200},"remappings":[]},"sources":{"":{"keccak256":"0x4790e490f418d1a5884c27ffe9684914dab2d55bd1d23b99cff7aa2ca289e2d3","urls":["bzzr://bb200beae6849f1f5bb97b36c57cd493be52877ec0b55ee9969fa5f8159cf37b"]}},"version":1}
|
||||||
|
// {"compiler":{"version":"0.4.16+commit.d7661dd9"},"language":"Solidity","output":{"abi":[{"inputs":[{"name":"who","type":"address[]"},{"name":"tokens","type":"address[]"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}],"devdoc":{"methods":{}},"userdoc":{"methods":{}}},"settings":{"compilationTarget":{"":"TokensBalances"},"libraries":{},"optimizer":{"enabled":true,"runs":200},"remappings":[]},"sources":{"":{"keccak256":"0x4790e490f418d1a5884c27ffe9684914dab2d55bd1d23b99cff7aa2ca289e2d3","urls":["bzzr://bb200beae6849f1f5bb97b36c57cd493be52877ec0b55ee9969fa5f8159cf37b"]}},"version":1}
|
||||||
|
|
||||||
|
export const tokenAddresses = '0x6060604052341561000f57600080fd5b6040516060806102528339810160405280805191906020018051919060200180519150505b6000806000806100426101fc565b600088955085600160a060020a0316639f181b5e6000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15156100a657600080fd5b6102c65a03f115156100b757600080fd5b50505060405180519550508688018890116100ce57fe5b8785116100de57600093506100f6565b8487890111156100f25787850393506100f6565b8693505b5b83602002602001925060405191508282016040528382528790505b8388018110156101ea5785600160a060020a031663044215c682600060405160a001526040517c010000000000000000000000000000000000000000000000000000000063ffffffff8416028152600481019190915260240160a060405180830381600087803b151561018457600080fd5b6102c65a03f1151561019557600080fd5b50505060405180519060200180519060200180519060200180519060200180515086935050508a84039050815181106101ca57fe5b600160a060020a039092166020928302909101909101525b600101610112565b8282f35b50505050505050505061020e565b60206040519081016040526000815290565b60368061021c6000396000f30060606040525b600080fd00a165627a7a72305820a9a09f013393cf3c6398ce0f8175073fe363b6f594f9bd569261d0bb94aa84d40029';
|
||||||
|
export const tokensBalances = '0x6060604052341561000f57600080fd5b60405161018b38038061018b8339810160405280805182019190602001805190910190505b6000806000610041610135565b60008060008060008060008c518c51029a506020808c020199507f70a0823100000000000000000000000000000000000000000000000000000000985060405197508988016040528a8852604051965060248701604052888752879550866004019450600093505b8c5184101561011f57600092505b8b51831015610113578c84815181106100cc57fe5b9060200190602002015191508b83815181106100e457fe5b90602001906020020151905060208601955081855260208660248960008561fffff1505b6001909201916100b7565b5b6001909301926100a9565b8988f35b50505050505050505050505050610147565b60206040519081016040526000815290565b6036806101556000396000f30060606040525b600080fd00a165627a7a723058203cfc17c394936aa87b7db79e4f082a7cfdcefef54acd3124d17525b56c92e7950029';
|
299
js-old/src/util/tokens/index.js
Normal file
299
js-old/src/util/tokens/index.js
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { range } from 'lodash';
|
||||||
|
import BigNumber from 'bignumber.js';
|
||||||
|
|
||||||
|
import { hashToImageUrl } from '~/redux/util';
|
||||||
|
import { sha3 } from '~/api/util/sha3';
|
||||||
|
import imagesEthereum from '~/../assets/images/contracts/ethereum-black-64x64.png';
|
||||||
|
import {
|
||||||
|
tokenAddresses as tokenAddressesBytcode,
|
||||||
|
tokensBalances as tokensBalancesBytecode
|
||||||
|
} from './bytecodes';
|
||||||
|
|
||||||
|
export const ETH_TOKEN = {
|
||||||
|
address: '',
|
||||||
|
format: new BigNumber(10).pow(18),
|
||||||
|
id: getTokenId('eth_native_token'),
|
||||||
|
image: imagesEthereum,
|
||||||
|
name: 'Ethereum',
|
||||||
|
native: true,
|
||||||
|
tag: 'ETH'
|
||||||
|
};
|
||||||
|
|
||||||
|
export function fetchTokenIds (tokenregInstance) {
|
||||||
|
return tokenregInstance.tokenCount
|
||||||
|
.call()
|
||||||
|
.then((numTokens) => {
|
||||||
|
const tokenIndexes = range(numTokens.toNumber());
|
||||||
|
|
||||||
|
return tokenIndexes;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchTokensBasics (api, tokenReg, start = 0, limit = 100) {
|
||||||
|
const tokenAddressesCallData = encode(
|
||||||
|
api,
|
||||||
|
[ 'address', 'uint', 'uint' ],
|
||||||
|
[ tokenReg.address, start, limit ]
|
||||||
|
);
|
||||||
|
|
||||||
|
return api.eth
|
||||||
|
.call({ data: tokenAddressesBytcode + tokenAddressesCallData })
|
||||||
|
.then((result) => {
|
||||||
|
return decodeArray(api, 'address[]', result);
|
||||||
|
})
|
||||||
|
.then((tokenAddresses) => {
|
||||||
|
return tokenAddresses.map((tokenAddress, index) => {
|
||||||
|
if (/^0x0*$/.test(tokenAddress)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenIndex = start + index;
|
||||||
|
|
||||||
|
return {
|
||||||
|
address: tokenAddress,
|
||||||
|
id: getTokenId(tokenAddress, tokenIndex),
|
||||||
|
index: tokenIndex,
|
||||||
|
|
||||||
|
fetched: false
|
||||||
|
};
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then((tokens) => tokens.filter((token) => token))
|
||||||
|
.then((tokens) => {
|
||||||
|
const randomAddress = sha3(`${Date.now()}`).substr(0, 42);
|
||||||
|
|
||||||
|
return fetchTokensBalances(api, tokens, [randomAddress])
|
||||||
|
.then((_balances) => {
|
||||||
|
const balances = _balances[randomAddress];
|
||||||
|
|
||||||
|
return tokens.filter(({ id }) => balances[id].eq(0));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchTokensInfo (api, tokenReg, tokenIndexes) {
|
||||||
|
const requests = tokenIndexes.map((tokenIndex) => {
|
||||||
|
const tokenCalldata = tokenReg.getCallData(tokenReg.instance.token, {}, [tokenIndex]);
|
||||||
|
|
||||||
|
return { to: tokenReg.address, data: tokenCalldata };
|
||||||
|
});
|
||||||
|
|
||||||
|
const calls = requests.map((req) => api.eth.call(req));
|
||||||
|
const imagesPromise = fetchTokensImages(api, tokenReg, tokenIndexes);
|
||||||
|
|
||||||
|
return Promise.all(calls)
|
||||||
|
.then((results) => {
|
||||||
|
return imagesPromise.then((images) => [ results, images ]);
|
||||||
|
})
|
||||||
|
.then(([ results, images ]) => {
|
||||||
|
return results.map((rawTokenData, index) => {
|
||||||
|
const tokenIndex = tokenIndexes[index];
|
||||||
|
const tokenData = tokenReg.instance.token
|
||||||
|
.decodeOutput(rawTokenData)
|
||||||
|
.map((t) => t.value);
|
||||||
|
|
||||||
|
const [ address, tag, format, name ] = tokenData;
|
||||||
|
const image = images[index];
|
||||||
|
|
||||||
|
const token = {
|
||||||
|
address,
|
||||||
|
id: getTokenId(address, tokenIndex),
|
||||||
|
index: tokenIndex,
|
||||||
|
|
||||||
|
format: format.toString(),
|
||||||
|
image,
|
||||||
|
name,
|
||||||
|
tag,
|
||||||
|
|
||||||
|
fetched: true
|
||||||
|
};
|
||||||
|
|
||||||
|
return token;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fetchTokensImages (api, tokenReg, tokenIndexes) {
|
||||||
|
const requests = tokenIndexes.map((tokenIndex) => {
|
||||||
|
const metaCalldata = tokenReg.getCallData(tokenReg.instance.meta, {}, [tokenIndex, 'IMG']);
|
||||||
|
|
||||||
|
return { to: tokenReg.address, data: metaCalldata };
|
||||||
|
});
|
||||||
|
|
||||||
|
const calls = requests.map((req) => api.eth.call(req));
|
||||||
|
|
||||||
|
return Promise.all(calls)
|
||||||
|
.then((results) => {
|
||||||
|
return results.map((rawImage) => {
|
||||||
|
const image = tokenReg.instance.meta.decodeOutput(rawImage)[0].value;
|
||||||
|
|
||||||
|
return hashToImageUrl(image);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `updates` should be in the shape:
|
||||||
|
* {
|
||||||
|
* [ who ]: [ tokenId ] // Array of tokens to updates
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* Returns a Promise resolved with the balances in the shape:
|
||||||
|
* {
|
||||||
|
* [ who ]: { [ tokenId ]: BigNumber } // The balances of `who`
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
export function fetchAccountsBalances (api, tokens, updates) {
|
||||||
|
const accountAddresses = Object.keys(updates);
|
||||||
|
|
||||||
|
// Updates for the ETH balances
|
||||||
|
const ethUpdates = accountAddresses
|
||||||
|
.filter((accountAddress) => {
|
||||||
|
return updates[accountAddress].find((tokenId) => tokenId === ETH_TOKEN.id);
|
||||||
|
})
|
||||||
|
.reduce((nextUpdates, accountAddress) => {
|
||||||
|
nextUpdates[accountAddress] = [ETH_TOKEN.id];
|
||||||
|
return nextUpdates;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// Updates for Tokens balances
|
||||||
|
const tokenUpdates = Object.keys(updates)
|
||||||
|
.reduce((nextUpdates, accountAddress) => {
|
||||||
|
const tokenIds = updates[accountAddress].filter((tokenId) => tokenId !== ETH_TOKEN.id);
|
||||||
|
|
||||||
|
if (tokenIds.length > 0) {
|
||||||
|
nextUpdates[accountAddress] = tokenIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextUpdates;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
let ethBalances = {};
|
||||||
|
let tokensBalances = {};
|
||||||
|
|
||||||
|
const ethPromise = fetchEthBalances(api, Object.keys(ethUpdates))
|
||||||
|
.then((_ethBalances) => {
|
||||||
|
ethBalances = _ethBalances;
|
||||||
|
});
|
||||||
|
|
||||||
|
const tokenPromise = Object.keys(tokenUpdates)
|
||||||
|
.reduce((tokenPromise, accountAddress) => {
|
||||||
|
const tokenIds = tokenUpdates[accountAddress];
|
||||||
|
const updateTokens = tokens
|
||||||
|
.filter((t) => tokenIds.includes(t.id));
|
||||||
|
|
||||||
|
return tokenPromise
|
||||||
|
.then(() => fetchTokensBalances(api, updateTokens, [ accountAddress ]))
|
||||||
|
.then((balances) => {
|
||||||
|
tokensBalances[accountAddress] = balances[accountAddress];
|
||||||
|
});
|
||||||
|
}, Promise.resolve());
|
||||||
|
|
||||||
|
return Promise.all([ ethPromise, tokenPromise ])
|
||||||
|
.then(() => {
|
||||||
|
const balances = Object.assign({}, tokensBalances);
|
||||||
|
|
||||||
|
Object.keys(ethBalances).forEach((accountAddress) => {
|
||||||
|
if (!balances[accountAddress]) {
|
||||||
|
balances[accountAddress] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
balances[accountAddress] = Object.assign(
|
||||||
|
{},
|
||||||
|
balances[accountAddress],
|
||||||
|
ethBalances[accountAddress]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return balances;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchEthBalances (api, accountAddresses) {
|
||||||
|
const promises = accountAddresses
|
||||||
|
.map((accountAddress) => api.eth.getBalance(accountAddress));
|
||||||
|
|
||||||
|
return Promise.all(promises)
|
||||||
|
.then((balancesArray) => {
|
||||||
|
return balancesArray.reduce((balances, balance, index) => {
|
||||||
|
balances[accountAddresses[index]] = {
|
||||||
|
[ETH_TOKEN.id]: balance
|
||||||
|
};
|
||||||
|
|
||||||
|
return balances;
|
||||||
|
}, {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchTokensBalances (api, tokens, accountAddresses) {
|
||||||
|
const tokenAddresses = tokens.map((t) => t.address);
|
||||||
|
const tokensBalancesCallData = encode(
|
||||||
|
api,
|
||||||
|
[ 'address[]', 'address[]' ],
|
||||||
|
[ accountAddresses, tokenAddresses ]
|
||||||
|
);
|
||||||
|
|
||||||
|
return api.eth
|
||||||
|
.call({ data: tokensBalancesBytecode + tokensBalancesCallData })
|
||||||
|
.then((result) => {
|
||||||
|
const rawBalances = decodeArray(api, 'uint[]', result);
|
||||||
|
const balances = {};
|
||||||
|
|
||||||
|
accountAddresses.forEach((accountAddress, accountIndex) => {
|
||||||
|
const balance = {};
|
||||||
|
const preIndex = accountIndex * tokenAddresses.length;
|
||||||
|
|
||||||
|
tokenAddresses.forEach((tokenAddress, tokenIndex) => {
|
||||||
|
const index = preIndex + tokenIndex;
|
||||||
|
const token = tokens[tokenIndex];
|
||||||
|
|
||||||
|
balance[token.id] = rawBalances[index];
|
||||||
|
});
|
||||||
|
|
||||||
|
balances[accountAddress] = balance;
|
||||||
|
});
|
||||||
|
|
||||||
|
return balances;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTokenId (...args) {
|
||||||
|
return sha3(args.join('')).slice(0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
function encode (api, types, values) {
|
||||||
|
return api.util.abiEncode(
|
||||||
|
null,
|
||||||
|
types,
|
||||||
|
values
|
||||||
|
).replace('0x', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeArray (api, type, data) {
|
||||||
|
return api.util
|
||||||
|
.abiDecode(
|
||||||
|
[type],
|
||||||
|
[
|
||||||
|
'0x',
|
||||||
|
(32).toString(16).padStart(64, 0),
|
||||||
|
data.replace('0x', '')
|
||||||
|
].join('')
|
||||||
|
)[0]
|
||||||
|
.map((t) => t.value);
|
||||||
|
}
|
@ -81,12 +81,11 @@ export function getTxOptions (api, func, _options, values = []) {
|
|||||||
options.to = options.to || func.contract.address;
|
options.to = options.to || func.contract.address;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!address) {
|
const promise = (!address)
|
||||||
return Promise.resolve({ func, options, values });
|
? Promise.resolve(false)
|
||||||
}
|
: WalletsUtils.isWallet(api, address);
|
||||||
|
|
||||||
return WalletsUtils
|
return promise
|
||||||
.isWallet(api, address)
|
|
||||||
.then((isWallet) => {
|
.then((isWallet) => {
|
||||||
if (!isWallet) {
|
if (!isWallet) {
|
||||||
return { func, options, values };
|
return { func, options, values };
|
||||||
|
@ -78,13 +78,39 @@ export default class WalletsUtils {
|
|||||||
.delegateCall(api, walletContract.address, 'fetchTransactions', [ walletContract ])
|
.delegateCall(api, walletContract.address, 'fetchTransactions', [ walletContract ])
|
||||||
.then((transactions) => {
|
.then((transactions) => {
|
||||||
return transactions.sort((txA, txB) => {
|
return transactions.sort((txA, txB) => {
|
||||||
const comp = txB.blockNumber.comparedTo(txA.blockNumber);
|
const bnA = txA.blockNumber;
|
||||||
|
const bnB = txB.blockNumber;
|
||||||
|
|
||||||
|
if (!bnA) {
|
||||||
|
console.warn('could not find block number in transaction', txA);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bnB) {
|
||||||
|
console.warn('could not find block number in transaction', txB);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const comp = bnA.comparedTo(bnB);
|
||||||
|
|
||||||
if (comp !== 0) {
|
if (comp !== 0) {
|
||||||
return comp;
|
return comp;
|
||||||
}
|
}
|
||||||
|
|
||||||
return txB.transactionIndex.comparedTo(txA.transactionIndex);
|
const txIdxA = txA.transactionIndex;
|
||||||
|
const txIdxB = txB.transactionIndex;
|
||||||
|
|
||||||
|
if (!txIdxA) {
|
||||||
|
console.warn('could not find transaction index in transaction', txA);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!txIdxB) {
|
||||||
|
console.warn('could not find transaction index in transaction', txB);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return txIdxA.comparedTo(txIdxB);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -212,6 +212,7 @@ export default class ConsensysWalletUtils {
|
|||||||
|
|
||||||
const transaction = {
|
const transaction = {
|
||||||
transactionHash: log.transactionHash,
|
transactionHash: log.transactionHash,
|
||||||
|
transactionIndex: log.transactionIndex,
|
||||||
blockNumber: log.blockNumber
|
blockNumber: log.blockNumber
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -130,7 +130,21 @@ export default class FoundationWalletUtils {
|
|||||||
.ConfirmationNeeded
|
.ConfirmationNeeded
|
||||||
.getAllLogs()
|
.getAllLogs()
|
||||||
.then((logs) => {
|
.then((logs) => {
|
||||||
return logs.map((log) => ({
|
return logs
|
||||||
|
.filter((log) => {
|
||||||
|
if (!log.blockNumber) {
|
||||||
|
console.warn('got a log without blockNumber', log);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!log.transactionIndex) {
|
||||||
|
console.warn('got a log without transactionIndex', log);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.map((log) => ({
|
||||||
initiator: log.params.initiator.value,
|
initiator: log.params.initiator.value,
|
||||||
to: log.params.to.value,
|
to: log.params.to.value,
|
||||||
data: log.params.data.value,
|
data: log.params.data.value,
|
||||||
@ -144,13 +158,39 @@ export default class FoundationWalletUtils {
|
|||||||
})
|
})
|
||||||
.then((logs) => {
|
.then((logs) => {
|
||||||
return logs.sort((logA, logB) => {
|
return logs.sort((logA, logB) => {
|
||||||
const comp = logA.blockNumber.comparedTo(logB.blockNumber);
|
const bnA = logA.blockNumber;
|
||||||
|
const bnB = logA.blockNumber;
|
||||||
|
|
||||||
|
if (!bnA) {
|
||||||
|
console.warn('could not find block number in log', logA);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bnB) {
|
||||||
|
console.warn('could not find block number in log', logB);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const comp = bnA.comparedTo(bnB);
|
||||||
|
|
||||||
if (comp !== 0) {
|
if (comp !== 0) {
|
||||||
return comp;
|
return comp;
|
||||||
}
|
}
|
||||||
|
|
||||||
return logA.transactionIndex.comparedTo(logB.transactionIndex);
|
const txIdxA = logA.transactionIndex;
|
||||||
|
const txIdxB = logB.transactionIndex;
|
||||||
|
|
||||||
|
if (!txIdxA) {
|
||||||
|
console.warn('could not find transaction index in log', logA);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!txIdxB) {
|
||||||
|
console.warn('could not find transaction index in log', logB);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return txIdxA.comparedTo(txIdxB);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then((pendingTxs) => {
|
.then((pendingTxs) => {
|
||||||
@ -205,7 +245,8 @@ export default class FoundationWalletUtils {
|
|||||||
] ]
|
] ]
|
||||||
})
|
})
|
||||||
.then((logs) => {
|
.then((logs) => {
|
||||||
const transactions = logs.map((log) => {
|
const transactions = logs
|
||||||
|
.map((log) => {
|
||||||
const signature = toHex(log.topics[0]);
|
const signature = toHex(log.topics[0]);
|
||||||
|
|
||||||
const value = log.params.value.value;
|
const value = log.params.value.value;
|
||||||
@ -219,10 +260,16 @@ export default class FoundationWalletUtils {
|
|||||||
|
|
||||||
const transaction = {
|
const transaction = {
|
||||||
transactionHash: log.transactionHash,
|
transactionHash: log.transactionHash,
|
||||||
|
transactionIndex: log.transactionIndex,
|
||||||
blockNumber: log.blockNumber,
|
blockNumber: log.blockNumber,
|
||||||
from, to, value
|
from, to, value
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!transaction.blockNumber) {
|
||||||
|
console.warn('log without block number', log);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (log.params.created && log.params.created.value && !/^(0x)?0*$/.test(log.params.created.value)) {
|
if (log.params.created && log.params.created.value && !/^(0x)?0*$/.test(log.params.created.value)) {
|
||||||
transaction.creates = log.params.created.value;
|
transaction.creates = log.params.created.value;
|
||||||
delete transaction.to;
|
delete transaction.to;
|
||||||
@ -238,7 +285,8 @@ export default class FoundationWalletUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return transaction;
|
return transaction;
|
||||||
});
|
})
|
||||||
|
.filter((tx) => tx);
|
||||||
|
|
||||||
return transactions;
|
return transactions;
|
||||||
});
|
});
|
||||||
|
@ -26,7 +26,6 @@ import HardwareStore from '~/mobx/hardwareStore';
|
|||||||
import ExportStore from '~/modals/ExportAccount/exportStore';
|
import ExportStore from '~/modals/ExportAccount/exportStore';
|
||||||
import { DeleteAccount, EditMeta, Faucet, PasswordManager, Shapeshift, Transfer, Verification } from '~/modals';
|
import { DeleteAccount, EditMeta, Faucet, PasswordManager, Shapeshift, Transfer, Verification } from '~/modals';
|
||||||
import { setVisibleAccounts } from '~/redux/providers/personalActions';
|
import { setVisibleAccounts } from '~/redux/providers/personalActions';
|
||||||
import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions';
|
|
||||||
import { Actionbar, Button, ConfirmDialog, Input, Page, Portal } from '~/ui';
|
import { Actionbar, Button, ConfirmDialog, Input, Page, Portal } from '~/ui';
|
||||||
import { DeleteIcon, DialIcon, EditIcon, LockedIcon, SendIcon, VerifyIcon, FileDownloadIcon } from '~/ui/Icons';
|
import { DeleteIcon, DialIcon, EditIcon, LockedIcon, SendIcon, VerifyIcon, FileDownloadIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
@ -45,8 +44,6 @@ class Account extends Component {
|
|||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
accounts: PropTypes.object.isRequired,
|
accounts: PropTypes.object.isRequired,
|
||||||
fetchCertifiers: PropTypes.func.isRequired,
|
|
||||||
fetchCertifications: PropTypes.func.isRequired,
|
|
||||||
setVisibleAccounts: PropTypes.func.isRequired,
|
setVisibleAccounts: PropTypes.func.isRequired,
|
||||||
|
|
||||||
account: PropTypes.object,
|
account: PropTypes.object,
|
||||||
@ -67,7 +64,6 @@ class Account extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.props.fetchCertifiers();
|
|
||||||
this.setVisibleAccounts();
|
this.setVisibleAccounts();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,11 +86,10 @@ class Account extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setVisibleAccounts (props = this.props) {
|
setVisibleAccounts (props = this.props) {
|
||||||
const { params, setVisibleAccounts, fetchCertifications } = props;
|
const { params, setVisibleAccounts } = props;
|
||||||
const addresses = [params.address];
|
const addresses = [params.address];
|
||||||
|
|
||||||
setVisibleAccounts(addresses);
|
setVisibleAccounts(addresses);
|
||||||
fetchCertifications(params.address);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
@ -370,14 +365,14 @@ class Account extends Component {
|
|||||||
onDeny={ this.exportClose }
|
onDeny={ this.exportClose }
|
||||||
title={
|
title={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='export.account.title'
|
id='account.export.title'
|
||||||
defaultMessage='Export Account'
|
defaultMessage='Export Account'
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className={ styles.textbox }>
|
<div className={ styles.textbox }>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='export.account.info'
|
id='account.export.info'
|
||||||
defaultMessage='Export your account as a JSON file. Please enter the password linked with this account.'
|
defaultMessage='Export your account as a JSON file. Please enter the password linked with this account.'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -388,13 +383,13 @@ class Account extends Component {
|
|||||||
type='password'
|
type='password'
|
||||||
hint={
|
hint={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='export.account.password.hint'
|
id='account.export.password.hint'
|
||||||
defaultMessage='The password specified when creating this account'
|
defaultMessage='The password specified when creating this account'
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='export.account.password.label'
|
id='account.export.password.label'
|
||||||
defaultMessage='Account password'
|
defaultMessage='Account password'
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@ -524,8 +519,6 @@ function mapStateToProps (state, props) {
|
|||||||
|
|
||||||
function mapDispatchToProps (dispatch) {
|
function mapDispatchToProps (dispatch) {
|
||||||
return bindActionCreators({
|
return bindActionCreators({
|
||||||
fetchCertifiers,
|
|
||||||
fetchCertifications,
|
|
||||||
newError,
|
newError,
|
||||||
setVisibleAccounts
|
setVisibleAccounts
|
||||||
}, dispatch);
|
}, dispatch);
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import sinon from 'sinon';
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
@ -34,7 +35,15 @@ function render (props) {
|
|||||||
/>,
|
/>,
|
||||||
{
|
{
|
||||||
context: {
|
context: {
|
||||||
store: createRedux()
|
store: createRedux(),
|
||||||
|
api: {
|
||||||
|
transport: {
|
||||||
|
on: sinon.stub()
|
||||||
|
},
|
||||||
|
pubsub: {
|
||||||
|
subscribeAndGetResult: sinon.stub()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).find('Account').shallow();
|
).find('Account').shallow();
|
||||||
|
@ -17,10 +17,8 @@
|
|||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
|
||||||
|
|
||||||
import { Container, SectionList } from '~/ui';
|
import { Container, SectionList } from '~/ui';
|
||||||
import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions';
|
|
||||||
import { ETH_TOKEN } from '~/util/tokens';
|
import { ETH_TOKEN } from '~/util/tokens';
|
||||||
|
|
||||||
import Summary from '../Summary';
|
import Summary from '../Summary';
|
||||||
@ -38,20 +36,9 @@ class List extends Component {
|
|||||||
orderFallback: PropTypes.string,
|
orderFallback: PropTypes.string,
|
||||||
search: PropTypes.array,
|
search: PropTypes.array,
|
||||||
|
|
||||||
fetchCertifiers: PropTypes.func.isRequired,
|
|
||||||
fetchCertifications: PropTypes.func.isRequired,
|
|
||||||
handleAddSearchToken: PropTypes.func
|
handleAddSearchToken: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
|
||||||
const { accounts, fetchCertifiers, fetchCertifications } = this.props;
|
|
||||||
|
|
||||||
fetchCertifiers();
|
|
||||||
for (let address in accounts) {
|
|
||||||
fetchCertifications(address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { accounts, disabled, empty } = this.props;
|
const { accounts, disabled, empty } = this.props;
|
||||||
|
|
||||||
@ -264,14 +251,7 @@ function mapStateToProps (state, props) {
|
|||||||
return { balances, certifications };
|
return { balances, certifications };
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps (dispatch) {
|
|
||||||
return bindActionCreators({
|
|
||||||
fetchCertifiers,
|
|
||||||
fetchCertifications
|
|
||||||
}, dispatch);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
mapDispatchToProps
|
null
|
||||||
)(List);
|
)(List);
|
||||||
|
@ -27,7 +27,11 @@ let instance;
|
|||||||
let redux;
|
let redux;
|
||||||
|
|
||||||
function createApi () {
|
function createApi () {
|
||||||
api = {};
|
api = {
|
||||||
|
pubsub: {
|
||||||
|
subscribeAndGetResult: sinon.stub().returns(Promise.reject(new Error('uninitialized')))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return api;
|
return api;
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,7 @@
|
|||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
import { FirstRun, UpgradeParity } from '~/modals';
|
import { Errors, ParityBackground } from '~/ui';
|
||||||
import { Errors, ParityBackground, Tooltips } from '~/ui';
|
|
||||||
|
|
||||||
import styles from '../application.css';
|
import styles from '../application.css';
|
||||||
|
|
||||||
@ -25,24 +24,17 @@ export default class Container extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
onCloseFirstRun: PropTypes.func,
|
onCloseFirstRun: PropTypes.func,
|
||||||
showFirstRun: PropTypes.bool,
|
showFirstRun: PropTypes.bool
|
||||||
upgradeStore: PropTypes.object.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { children, onCloseFirstRun, showFirstRun, upgradeStore } = this.props;
|
const { children } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ParityBackground
|
<ParityBackground
|
||||||
attachDocument
|
attachDocument
|
||||||
className={ styles.container }
|
className={ styles.container }
|
||||||
>
|
>
|
||||||
<FirstRun
|
|
||||||
onClose={ onCloseFirstRun }
|
|
||||||
visible={ showFirstRun }
|
|
||||||
/>
|
|
||||||
<Tooltips />
|
|
||||||
<UpgradeParity store={ upgradeStore } />
|
|
||||||
<Errors />
|
<Errors />
|
||||||
{ children }
|
{ children }
|
||||||
</ParityBackground>
|
</ParityBackground>
|
||||||
|
@ -15,13 +15,12 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { Link } from 'react-router';
|
import { Link } from 'react-router';
|
||||||
import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar';
|
import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
import { Tooltip, StatusIndicator } from '~/ui';
|
import { StatusIndicator } from '~/ui';
|
||||||
|
|
||||||
import Tab from './Tab';
|
import Tab from './Tab';
|
||||||
import styles from './tabBar.css';
|
import styles from './tabBar.css';
|
||||||
@ -66,15 +65,6 @@ class TabBar extends Component {
|
|||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
{ this.renderTabItems() }
|
{ this.renderTabItems() }
|
||||||
<Tooltip
|
|
||||||
className={ styles.tabbarTooltip }
|
|
||||||
text={
|
|
||||||
<FormattedMessage
|
|
||||||
id='tabBar.tooltip.overview'
|
|
||||||
defaultMessage='navigate between the different parts and views of the application, switching between an account view, token view and decentralized application view'
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<ToolbarGroup className={ styles.last }>
|
<ToolbarGroup className={ styles.last }>
|
||||||
<div />
|
<div />
|
||||||
|
@ -18,27 +18,14 @@ import { observer } from 'mobx-react';
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import UpgradeStore from '~/modals/UpgradeParity/store';
|
|
||||||
|
|
||||||
import Connection from '../Connection';
|
|
||||||
import ParityBar from '../ParityBar';
|
|
||||||
import SyncWarning, { showSyncWarning } from '../SyncWarning';
|
|
||||||
|
|
||||||
import Snackbar from './Snackbar';
|
import Snackbar from './Snackbar';
|
||||||
import Container from './Container';
|
import Container from './Container';
|
||||||
import DappContainer from './DappContainer';
|
import DappContainer from './DappContainer';
|
||||||
import Extension from './Extension';
|
|
||||||
import FrameError from './FrameError';
|
|
||||||
import Status from './Status';
|
|
||||||
import Store from './store';
|
import Store from './store';
|
||||||
import TabBar from './TabBar';
|
import TabBar from './TabBar';
|
||||||
import Requests from './Requests';
|
|
||||||
|
|
||||||
import styles from './application.css';
|
import styles from './application.css';
|
||||||
|
|
||||||
const inFrame = window.parent !== window && window.parent.frames.length !== 0;
|
|
||||||
const doShowSyncWarning = showSyncWarning();
|
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class Application extends Component {
|
class Application extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -53,11 +40,9 @@ class Application extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
store = new Store(this.context.api);
|
store = new Store(this.context.api);
|
||||||
upgradeStore = UpgradeStore.get(this.context.api);
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const [root] = (window.location.hash || '').replace('#/', '').split('/');
|
const [root] = (window.location.hash || '').replace('#/', '').split('/');
|
||||||
const isMinimized = root === 'app' || root === 'web';
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production' && root === 'playground') {
|
if (process.env.NODE_ENV !== 'production' && root === 'playground') {
|
||||||
return (
|
return (
|
||||||
@ -69,47 +54,20 @@ class Application extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{
|
{ this.renderApp() }
|
||||||
inFrame
|
|
||||||
? <FrameError />
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
{
|
|
||||||
isMinimized
|
|
||||||
? this.renderMinimized()
|
|
||||||
: this.renderApp()
|
|
||||||
}
|
|
||||||
{
|
|
||||||
doShowSyncWarning
|
|
||||||
? (<SyncWarning />)
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
<Connection />
|
|
||||||
<Requests />
|
|
||||||
<ParityBar dapp={ isMinimized } />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderApp () {
|
renderApp () {
|
||||||
const { blockNumber, children, pending } = this.props;
|
const { children, pending } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container>
|
||||||
upgradeStore={ this.upgradeStore }
|
|
||||||
onCloseFirstRun={ this.store.closeFirstrun }
|
|
||||||
showFirstRun={ this.store.firstrunVisible }
|
|
||||||
>
|
|
||||||
<TabBar pending={ pending } />
|
<TabBar pending={ pending } />
|
||||||
<div className={ styles.content }>
|
<div className={ styles.content }>
|
||||||
{ children }
|
{ children }
|
||||||
</div>
|
</div>
|
||||||
{
|
|
||||||
blockNumber
|
|
||||||
? <Status upgradeStore={ this.upgradeStore } />
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
<Extension />
|
|
||||||
<Snackbar />
|
<Snackbar />
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import imagesEthcoreBlock from '~/../assets/images/parity-logo-white-no-text.svg';
|
import imagesEthcoreBlock from '~/../assets/images/parity-logo-white-no-text.svg';
|
||||||
import { AccountsIcon, AddressesIcon, AppsIcon, ContactsIcon, FingerprintIcon, SettingsIcon } from '~/ui/Icons';
|
import { AccountsIcon, AddressesIcon, ContactsIcon, FingerprintIcon, SettingsIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
import styles from './views.css';
|
import styles from './views.css';
|
||||||
|
|
||||||
@ -50,13 +50,6 @@ const defaultViews = {
|
|||||||
value: 'address'
|
value: 'address'
|
||||||
},
|
},
|
||||||
|
|
||||||
apps: {
|
|
||||||
active: true,
|
|
||||||
icon: <AppsIcon />,
|
|
||||||
route: '/apps',
|
|
||||||
value: 'app'
|
|
||||||
},
|
|
||||||
|
|
||||||
contracts: {
|
contracts: {
|
||||||
active: false,
|
active: false,
|
||||||
onlyPersonal: true,
|
onlyPersonal: true,
|
||||||
|
@ -91,17 +91,6 @@ class Views extends Component {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
{
|
|
||||||
this.renderView('apps',
|
|
||||||
<FormattedMessage
|
|
||||||
id='settings.views.apps.label'
|
|
||||||
/>,
|
|
||||||
<FormattedMessage
|
|
||||||
id='settings.views.apps.description'
|
|
||||||
defaultMessage='Decentralized applications that interact with the underlying network. Add applications, manage you application portfolio and interact with application from around the network.'
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
this.renderView('contracts',
|
this.renderView('contracts',
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
@ -48,8 +48,9 @@ const entry = isEmbed
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
cache: !isProd,
|
cache: !isProd,
|
||||||
devtool: isProd ? '#hidden-source-map' : '#source-map',
|
devtool: isProd
|
||||||
|
? false
|
||||||
|
: '#source-map',
|
||||||
context: path.join(__dirname, '../src'),
|
context: path.join(__dirname, '../src'),
|
||||||
entry: entry,
|
entry: entry,
|
||||||
output: {
|
output: {
|
||||||
@ -67,7 +68,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
include: /node_modules\/(material-chip-input|ethereumjs-tx|@parity\/wordlist)/,
|
include: /(material-chip-input|ethereumjs-tx)/,
|
||||||
use: 'babel-loader'
|
use: 'babel-loader'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -214,6 +215,7 @@ module.exports = {
|
|||||||
|
|
||||||
new CopyWebpackPlugin([
|
new CopyWebpackPlugin([
|
||||||
{ from: './error_pages.css', to: 'styles.css' },
|
{ from: './error_pages.css', to: 'styles.css' },
|
||||||
|
{ from: './manifest.json', to: 'manifest.json' },
|
||||||
{ from: 'dapps/static' }
|
{ from: 'dapps/static' }
|
||||||
], {})
|
], {})
|
||||||
);
|
);
|
||||||
|
@ -62,7 +62,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
include: /node_modules\/(ethereumjs-tx|@parity\/wordlist)/,
|
include: /(ethereumjs-tx|wordlist)/,
|
||||||
use: 'babel-loader'
|
use: 'babel-loader'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -65,7 +65,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
include: /node_modules\/(ethereumjs-tx|@parity\/wordlist)/,
|
include: /(ethereumjs-tx|wordlist)/,
|
||||||
use: 'babel-loader'
|
use: 'babel-loader'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
8651
js-old/yarn.lock
8651
js-old/yarn.lock
File diff suppressed because it is too large
Load Diff
3277
js/package-lock.json
generated
3277
js/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -21,9 +21,9 @@
|
|||||||
"Parity"
|
"Parity"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run build:lib && npm run build:app && npm run build:embed",
|
"build": "npm run build:inject && npm run build:app && npm run build:embed",
|
||||||
"build:app": "webpack --progress --config webpack/app",
|
"build:app": "webpack --config webpack/app",
|
||||||
"build:lib": "webpack --progress --config webpack/libraries",
|
"build:inject": "webpack --config webpack/inject",
|
||||||
"build:embed": "cross-env EMBED=1 node webpack/embed",
|
"build:embed": "cross-env EMBED=1 node webpack/embed",
|
||||||
"build:i18n": "npm run clean && npm run build && babel-node ./scripts/build-i18n.js",
|
"build:i18n": "npm run clean && npm run build && babel-node ./scripts/build-i18n.js",
|
||||||
"ci:build": "cross-env NODE_ENV=production npm run build",
|
"ci:build": "cross-env NODE_ENV=production npm run build",
|
||||||
@ -41,10 +41,7 @@
|
|||||||
"start:app": "node webpack/dev.server",
|
"start:app": "node webpack/dev.server",
|
||||||
"start:electron": "npm run build:app && electron .build/",
|
"start:electron": "npm run build:app && electron .build/",
|
||||||
"test": "cross-env NODE_ENV=test mocha --compilers ejs:ejsify 'src/**/*.spec.js'",
|
"test": "cross-env NODE_ENV=test mocha --compilers ejs:ejsify 'src/**/*.spec.js'",
|
||||||
"test:coverage": "cross-env NODE_ENV=test istanbul cover _mocha -- --compilers ejs:ejsify 'src/**/*.spec.js'",
|
"test:coverage": "cross-env NODE_ENV=test istanbul cover _mocha -- --compilers ejs:ejsify 'src/**/*.spec.js'"
|
||||||
"test:e2e": "cross-env NODE_ENV=test mocha 'src/**/*.e2e.js'",
|
|
||||||
"test:npm": "(cd .npmjs && npm i) && node test/npmParity && node test/npmJsonRpc && (rimraf .npmjs/node_modules)",
|
|
||||||
"prepush": "npm run lint:cached"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-cli": "6.26.0",
|
"babel-cli": "6.26.0",
|
||||||
@ -94,9 +91,7 @@
|
|||||||
"html-loader": "0.4.4",
|
"html-loader": "0.4.4",
|
||||||
"html-webpack-plugin": "2.30.1",
|
"html-webpack-plugin": "2.30.1",
|
||||||
"http-proxy-middleware": "0.17.3",
|
"http-proxy-middleware": "0.17.3",
|
||||||
"husky": "0.13.1",
|
|
||||||
"ignore-styles": "5.0.1",
|
"ignore-styles": "5.0.1",
|
||||||
"image-webpack-loader": "3.2.0",
|
|
||||||
"istanbul": "1.0.0-alpha.2",
|
"istanbul": "1.0.0-alpha.2",
|
||||||
"jsdom": "9.11.0",
|
"jsdom": "9.11.0",
|
||||||
"json-loader": "0.5.4",
|
"json-loader": "0.5.4",
|
||||||
@ -135,18 +130,8 @@
|
|||||||
"yargs": "6.6.0"
|
"yargs": "6.6.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@parity/abi": "^2",
|
"@parity/abi": "2.1.x",
|
||||||
"@parity/api": "^2",
|
"@parity/api": "2.1.x",
|
||||||
"@parity/jsonrpc": "^2",
|
|
||||||
"@parity/etherscan": "^2",
|
|
||||||
"@parity/ledger": "^2",
|
|
||||||
"@parity/shapeshift": "^2",
|
|
||||||
"@parity/shared": "^2",
|
|
||||||
"@parity/ui": "^2",
|
|
||||||
"@parity/plugin-signer-account": "paritytech/plugin-signer-account",
|
|
||||||
"@parity/plugin-signer-default": "paritytech/plugin-signer-default",
|
|
||||||
"@parity/plugin-signer-hardware": "paritytech/plugin-signer-hardware",
|
|
||||||
"@parity/plugin-signer-qr": "paritytech/plugin-signer-qr",
|
|
||||||
"@parity/dapp-account": "paritytech/dapp-account",
|
"@parity/dapp-account": "paritytech/dapp-account",
|
||||||
"@parity/dapp-accounts": "paritytech/dapp-accounts",
|
"@parity/dapp-accounts": "paritytech/dapp-accounts",
|
||||||
"@parity/dapp-address": "paritytech/dapp-address",
|
"@parity/dapp-address": "paritytech/dapp-address",
|
||||||
@ -173,12 +158,21 @@
|
|||||||
"@parity/dapp-vaults": "paritytech/dapp-vaults",
|
"@parity/dapp-vaults": "paritytech/dapp-vaults",
|
||||||
"@parity/dapp-wallet": "paritytech/dapp-wallet",
|
"@parity/dapp-wallet": "paritytech/dapp-wallet",
|
||||||
"@parity/dapp-web": "paritytech/dapp-web",
|
"@parity/dapp-web": "paritytech/dapp-web",
|
||||||
"isomorphic-fetch": "2.2.1",
|
"@parity/etherscan": "2.1.x",
|
||||||
|
"@parity/jsonrpc": "2.1.x",
|
||||||
|
"@parity/ledger": "2.1.x",
|
||||||
|
"@parity/plugin-signer-account": "paritytech/plugin-signer-account",
|
||||||
|
"@parity/plugin-signer-default": "paritytech/plugin-signer-default",
|
||||||
|
"@parity/plugin-signer-hardware": "paritytech/plugin-signer-hardware",
|
||||||
|
"@parity/plugin-signer-qr": "paritytech/plugin-signer-qr",
|
||||||
|
"@parity/shapeshift": "2.1.x",
|
||||||
|
"@parity/shared": "2.2.x",
|
||||||
|
"@parity/ui": "2.2.x",
|
||||||
|
"keythereum": "1.0.2",
|
||||||
"lodash.flatten": "4.4.0",
|
"lodash.flatten": "4.4.0",
|
||||||
"lodash.omitby": "4.6.0",
|
"lodash.omitby": "4.6.0",
|
||||||
"lodash.throttle": "4.1.1",
|
"lodash.throttle": "4.1.1",
|
||||||
"lodash.uniq": "4.5.0",
|
"lodash.uniq": "4.5.0",
|
||||||
"oo7": "paritytech/oo7#34fdb5991f4e59b2cf84260cab48cec9a57d88c0",
|
|
||||||
"prop-types": "15.5.10",
|
"prop-types": "15.5.10",
|
||||||
"react": "15.6.1",
|
"react": "15.6.1",
|
||||||
"react-dom": "15.6.1",
|
"react-dom": "15.6.1",
|
||||||
@ -189,7 +183,6 @@
|
|||||||
"redux": "3.6.0",
|
"redux": "3.6.0",
|
||||||
"solc": "ngotchac/solc-js",
|
"solc": "ngotchac/solc-js",
|
||||||
"store": "1.3.20",
|
"store": "1.3.20",
|
||||||
"web3": "0.17.0-beta",
|
"web3": "0.17.0-beta"
|
||||||
"whatwg-fetch": "2.0.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,9 +20,9 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import HardwareStore from '@parity/shared/mobx/hardwareStore';
|
import HardwareStore from '@parity/shared/lib/mobx/hardwareStore';
|
||||||
import UpgradeStore from '@parity/shared/mobx/upgradeParity';
|
import UpgradeStore from '@parity/shared/lib/mobx/upgradeParity';
|
||||||
import Errors from '@parity/ui/Errors';
|
import Errors from '@parity/ui/lib/Errors';
|
||||||
|
|
||||||
import Connection from '../Connection';
|
import Connection from '../Connection';
|
||||||
import DappRequests from '../DappRequests';
|
import DappRequests from '../DappRequests';
|
||||||
@ -145,11 +145,9 @@ class Application extends Component {
|
|||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const { blockNumber } = state.nodeStatus;
|
const { blockNumber } = state.nodeStatus;
|
||||||
const { hasAccounts } = state.personal;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
blockNumber,
|
blockNumber
|
||||||
hasAccounts
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,9 +19,9 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import GradientBg from '@parity/ui/GradientBg';
|
import GradientBg from '@parity/ui/lib/GradientBg';
|
||||||
import Input from '@parity/ui/Form/Input';
|
import Input from '@parity/ui/lib/Form/Input';
|
||||||
import { CompareIcon, ComputerIcon, DashboardIcon, VpnIcon } from '@parity/ui/Icons';
|
import { CompareIcon, ComputerIcon, DashboardIcon, VpnIcon } from '@parity/ui/lib/Icons';
|
||||||
|
|
||||||
import styles from './connection.css';
|
import styles from './connection.css';
|
||||||
|
|
||||||
|
@ -20,11 +20,10 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import Api from '@parity/api';
|
import Api from '@parity/api';
|
||||||
import builtinDapps from '@parity/shared/config/dappsBuiltin.json';
|
import builtinDapps from '@parity/shared/lib/config/dappsBuiltin.json';
|
||||||
import viewsDapps from '@parity/shared/config/dappsViews.json';
|
import viewsDapps from '@parity/shared/lib/config/dappsViews.json';
|
||||||
import DappsStore from '@parity/shared/mobx/dappsStore';
|
import DappsStore from '@parity/shared/lib/mobx/dappsStore';
|
||||||
import HistoryStore from '@parity/shared/mobx/historyStore';
|
import HistoryStore from '@parity/shared/lib/mobx/historyStore';
|
||||||
// import { Bond } from 'oo7';
|
|
||||||
|
|
||||||
import styles from './dapp.css';
|
import styles from './dapp.css';
|
||||||
|
|
||||||
@ -163,6 +162,5 @@ export default class Dapp extends Component {
|
|||||||
const frame = document.getElementById('dappFrame');
|
const frame = document.getElementById('dappFrame');
|
||||||
|
|
||||||
frame.style.opacity = 1;
|
frame.style.opacity = 1;
|
||||||
// frame.contentWindow.injectedBondCache = Bond.cache;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,9 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import Button from '@parity/ui/Button';
|
import Button from '@parity/ui/lib/Button';
|
||||||
|
|
||||||
import DappsStore from '@parity/shared/mobx/dappsStore';
|
import DappsStore from '@parity/shared/lib/mobx/dappsStore';
|
||||||
|
|
||||||
export default function Request ({ appId, className, approveRequest, denyRequest, queueId, request: { from, method } }) {
|
export default function Request ({ appId, className, approveRequest, denyRequest, queueId, request: { from, method } }) {
|
||||||
const _onApprove = () => approveRequest(queueId, false);
|
const _onApprove = () => approveRequest(queueId, false);
|
||||||
|
@ -23,8 +23,8 @@ $backgroundTwo: #e57a00;
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 2.75em;
|
bottom: 0;
|
||||||
z-index: 760; /* sits above requests */
|
z-index: 1001; /* sits above sync warning */
|
||||||
|
|
||||||
.request {
|
.request {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
import { action, computed, observable } from 'mobx';
|
import { action, computed, observable } from 'mobx';
|
||||||
import store from 'store';
|
import store from 'store';
|
||||||
|
|
||||||
import { sha3 } from '@parity/api/util/sha3';
|
import { sha3 } from '@parity/api/lib/util/sha3';
|
||||||
|
|
||||||
import filteredRequests from './filteredRequests';
|
import filteredRequests from './filteredRequests';
|
||||||
|
|
||||||
|
@ -21,12 +21,12 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import DappCard from '@parity/ui/DappCard';
|
import DappCard from '@parity/ui/lib/DappCard';
|
||||||
import Checkbox from '@parity/ui/Form/Checkbox';
|
import Checkbox from '@parity/ui/lib/Form/Checkbox';
|
||||||
import Page from '@parity/ui/Page';
|
import Page from '@parity/ui/lib/Page';
|
||||||
import SectionList from '@parity/ui/SectionList';
|
import SectionList from '@parity/ui/lib/SectionList';
|
||||||
|
|
||||||
import DappsStore from '@parity/shared/mobx/dappsStore';
|
import DappsStore from '@parity/shared/lib/mobx/dappsStore';
|
||||||
|
|
||||||
import styles from './dapps.css';
|
import styles from './dapps.css';
|
||||||
|
|
||||||
@ -85,8 +85,8 @@ class Dapps extends Component {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{ this.renderList(this.store.visibleViews) }
|
|
||||||
{ this.renderList(this.store.visibleLocal) }
|
{ this.renderList(this.store.visibleLocal) }
|
||||||
|
{ this.renderList(this.store.visibleViews) }
|
||||||
{ this.renderList(this.store.visibleBuiltin) }
|
{ this.renderList(this.store.visibleBuiltin) }
|
||||||
{ this.renderList(this.store.visibleNetwork, externalOverlay) }
|
{ this.renderList(this.store.visibleNetwork, externalOverlay) }
|
||||||
</Page>
|
</Page>
|
||||||
|
@ -18,10 +18,10 @@ import { observer } from 'mobx-react';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import Button from '@parity/ui/Button';
|
import Button from '@parity/ui/lib/Button';
|
||||||
import { CloseIcon, CheckIcon } from '@parity/ui/Icons';
|
import { CloseIcon, CheckIcon } from '@parity/ui/lib/Icons';
|
||||||
|
|
||||||
import Store from '@parity/shared/mobx/extensionStore';
|
import Store from '@parity/shared/lib/mobx/extensionStore';
|
||||||
import styles from './extension.css';
|
import styles from './extension.css';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
|
@ -19,7 +19,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
|
|
||||||
import Checkbox from '@parity/ui/Form/Checkbox';
|
import Checkbox from '@parity/ui/lib/Form/Checkbox';
|
||||||
|
|
||||||
import styles from '../firstRun.css';
|
import styles from '../firstRun.css';
|
||||||
|
|
||||||
|
@ -21,11 +21,11 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
import { createIdentityImg } from '@parity/api/util/identity';
|
import { createIdentityImg } from '@parity/api/lib/util/identity';
|
||||||
import { newError } from '@parity/shared/redux/actions';
|
import { newError } from '@parity/shared/lib/redux/actions';
|
||||||
import Button from '@parity/ui/Button';
|
import Button from '@parity/ui/lib/Button';
|
||||||
import Portal from '@parity/ui/Portal';
|
import Portal from '@parity/ui/lib/Portal';
|
||||||
import { CheckIcon, DoneIcon, NextIcon, PrintIcon, ReplayIcon } from '@parity/ui/Icons';
|
import { CheckIcon, DoneIcon, NextIcon, PrintIcon, ReplayIcon } from '@parity/ui/lib/Icons';
|
||||||
|
|
||||||
import ParityLogo from '@parity/shared/assets/images/parity-logo-black-no-text.svg';
|
import ParityLogo from '@parity/shared/assets/images/parity-logo-black-no-text.svg';
|
||||||
import { NewAccount, AccountDetails } from '@parity/dapp-accounts/src/CreateAccount';
|
import { NewAccount, AccountDetails } from '@parity/dapp-accounts/src/CreateAccount';
|
||||||
|
@ -24,16 +24,16 @@ import { Link } from 'react-router';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import store from 'store';
|
import store from 'store';
|
||||||
|
|
||||||
import AccountCard from '@parity/ui/AccountCard';
|
import AccountCard from '@parity/ui/lib/AccountCard';
|
||||||
import Button from '@parity/ui/Button';
|
import Button from '@parity/ui/lib/Button';
|
||||||
import ContainerTitle from '@parity/ui/Container/Title';
|
import ContainerTitle from '@parity/ui/lib/Container/Title';
|
||||||
import IdentityIcon from '@parity/ui/IdentityIcon';
|
import IdentityIcon from '@parity/ui/lib/IdentityIcon';
|
||||||
import GradientBg from '@parity/ui/GradientBg';
|
import GradientBg from '@parity/ui/lib/GradientBg';
|
||||||
import SelectionList from '@parity/ui/SelectionList';
|
import SelectionList from '@parity/ui/lib/SelectionList';
|
||||||
import SignerPending from '@parity/ui/SignerPending';
|
import SignerPending from '@parity/ui/lib/SignerPending';
|
||||||
import { CancelIcon } from '@parity/ui/Icons';
|
import { CancelIcon } from '@parity/ui/lib/Icons';
|
||||||
|
|
||||||
import DappsStore from '@parity/shared/mobx/dappsStore';
|
import DappsStore from '@parity/shared/lib/mobx/dappsStore';
|
||||||
import Signer from '../Signer/Embedded';
|
import Signer from '../Signer/Embedded';
|
||||||
|
|
||||||
import AccountStore from './accountStore';
|
import AccountStore from './accountStore';
|
||||||
|
@ -18,7 +18,7 @@ import React, { Component } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import GradientBg from '@parity/ui/GradientBg';
|
import GradientBg from '@parity/ui/lib/GradientBg';
|
||||||
|
|
||||||
import styles from './pinMatrix.css';
|
import styles from './pinMatrix.css';
|
||||||
|
|
||||||
|
@ -21,12 +21,12 @@ import ReactDOM from 'react-dom';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
import { hideRequest } from '@parity/shared/redux/providers/requestsActions';
|
import { hideRequest } from '@parity/shared/lib/redux/providers/requestsActions';
|
||||||
import MethodDecoding from '@parity/ui/MethodDecoding';
|
import MethodDecoding from '@parity/ui/lib/MethodDecoding';
|
||||||
import IdentityIcon from '@parity/ui/IdentityIcon';
|
import IdentityIcon from '@parity/ui/lib/IdentityIcon';
|
||||||
import Progress from '@parity/ui/Progress';
|
import Progress from '@parity/ui/lib/Progress';
|
||||||
import ScrollableText from '@parity/ui/ScrollableText';
|
import ScrollableText from '@parity/ui/lib/ScrollableText';
|
||||||
import ShortenedHash from '@parity/ui/ShortenedHash';
|
import ShortenedHash from '@parity/ui/lib/ShortenedHash';
|
||||||
|
|
||||||
import styles from './requests.css';
|
import styles from './requests.css';
|
||||||
|
|
||||||
|
@ -20,8 +20,8 @@ import { connect } from 'react-redux';
|
|||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
import * as RequestsActions from '@parity/shared/redux/providers/signerActions';
|
import * as RequestsActions from '@parity/shared/lib/redux/providers/signerActions';
|
||||||
import Container from '@parity/ui/Container';
|
import Container from '@parity/ui/lib/Container';
|
||||||
|
|
||||||
import PendingList from '../PendingList';
|
import PendingList from '../PendingList';
|
||||||
import PendingStore from '../pendingStore';
|
import PendingStore from '../pendingStore';
|
||||||
|
@ -19,7 +19,7 @@ import { FormattedMessage } from 'react-intl';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
import SignerLayout from '@parity/ui/Signer/Layout';
|
import SignerLayout from '@parity/ui/lib/Signer/Layout';
|
||||||
|
|
||||||
import PluginStore from '../pluginStore';
|
import PluginStore from '../pluginStore';
|
||||||
import styles from './pendingItem.css';
|
import styles from './pendingItem.css';
|
||||||
|
@ -19,8 +19,8 @@ import PropTypes from 'prop-types';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
|
|
||||||
import { closeSnackbar } from '@parity/shared/redux/providers/snackbarActions';
|
import { closeSnackbar } from '@parity/shared/lib/redux/providers/snackbarActions';
|
||||||
import SnackbarUI from '@parity/ui/Snackbar';
|
import SnackbarUI from '@parity/ui/lib/Snackbar';
|
||||||
|
|
||||||
function Snackbar ({ closeSnackbar, cooldown = 3500, message, open = false }) {
|
function Snackbar ({ closeSnackbar, cooldown = 3500, message, open = false }) {
|
||||||
return (
|
return (
|
||||||
|
@ -19,14 +19,14 @@ import PropTypes from 'prop-types';
|
|||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import BlockNumber from '@parity/ui/BlockNumber';
|
import BlockNumber from '@parity/ui/lib/BlockNumber';
|
||||||
import ClientVersion from '@parity/ui/ClientVersion';
|
import ClientVersion from '@parity/ui/lib/ClientVersion';
|
||||||
import GradientBg from '@parity/ui/GradientBg';
|
import GradientBg from '@parity/ui/lib/GradientBg';
|
||||||
import IdentityIcon from '@parity/ui/IdentityIcon';
|
import IdentityIcon from '@parity/ui/lib/IdentityIcon';
|
||||||
import NetChain from '@parity/ui/NetChain';
|
import NetChain from '@parity/ui/lib/NetChain';
|
||||||
import NetPeers from '@parity/ui/NetPeers';
|
import NetPeers from '@parity/ui/lib/NetPeers';
|
||||||
import SignerPending from '@parity/ui/SignerPending';
|
import SignerPending from '@parity/ui/lib/SignerPending';
|
||||||
import StatusIndicator from '@parity/ui/StatusIndicator';
|
import StatusIndicator from '@parity/ui/lib/StatusIndicator';
|
||||||
|
|
||||||
import Consensus from './Consensus';
|
import Consensus from './Consensus';
|
||||||
import AccountStore from '../ParityBar/accountStore';
|
import AccountStore from '../ParityBar/accountStore';
|
||||||
|
@ -18,7 +18,7 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
import StatusIndicator from '@parity/ui/StatusIndicator';
|
import StatusIndicator from '@parity/ui/lib/StatusIndicator';
|
||||||
|
|
||||||
import styles from './syncWarning.css';
|
import styles from './syncWarning.css';
|
||||||
|
|
||||||
|
@ -19,10 +19,10 @@ import React, { Component } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { STEP_COMPLETED, STEP_ERROR, STEP_INFO, STEP_UPDATING } from '@parity/shared/mobx/upgradeParity';
|
import { STEP_COMPLETED, STEP_ERROR, STEP_INFO, STEP_UPDATING } from '@parity/shared/lib/mobx/upgradeParity';
|
||||||
import Button from '@parity/ui/Button';
|
import Button from '@parity/ui/lib/Button';
|
||||||
import Portal from '@parity/ui/Portal';
|
import Portal from '@parity/ui/lib/Portal';
|
||||||
import { CancelIcon, DoneIcon, ErrorIcon, NextIcon, UpdateIcon, UpdateWaitIcon } from '@parity/ui/Icons';
|
import { CancelIcon, DoneIcon, ErrorIcon, NextIcon, UpdateIcon, UpdateWaitIcon } from '@parity/ui/lib/Icons';
|
||||||
|
|
||||||
import styles from './upgradeParity.css';
|
import styles from './upgradeParity.css';
|
||||||
|
|
||||||
|
118
js/src/api-local/accounts/account.js
Normal file
118
js/src/api-local/accounts/account.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const { createKeyObject, decryptPrivateKey } = require('../ethkey');
|
||||||
|
|
||||||
|
class Account {
|
||||||
|
constructor (persist, data = {}) {
|
||||||
|
const {
|
||||||
|
keyObject = null,
|
||||||
|
meta = {},
|
||||||
|
name = ''
|
||||||
|
} = data;
|
||||||
|
|
||||||
|
this._persist = persist;
|
||||||
|
this._keyObject = keyObject;
|
||||||
|
this._name = name;
|
||||||
|
this._meta = meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
isValidPassword (password) {
|
||||||
|
if (!this._keyObject) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return decryptPrivateKey(this._keyObject, password)
|
||||||
|
.then((privateKey) => {
|
||||||
|
if (!privateKey) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export () {
|
||||||
|
const exported = Object.assign({}, this._keyObject);
|
||||||
|
|
||||||
|
exported.meta = JSON.stringify(this._meta);
|
||||||
|
exported.name = this._name;
|
||||||
|
|
||||||
|
return exported;
|
||||||
|
}
|
||||||
|
|
||||||
|
get address () {
|
||||||
|
return `0x${this._keyObject.address.toLowerCase()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
get name () {
|
||||||
|
return this._name;
|
||||||
|
}
|
||||||
|
|
||||||
|
set name (name) {
|
||||||
|
this._name = name;
|
||||||
|
|
||||||
|
this._persist();
|
||||||
|
}
|
||||||
|
|
||||||
|
get meta () {
|
||||||
|
return JSON.stringify(this._meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
set meta (meta) {
|
||||||
|
this._meta = JSON.parse(meta);
|
||||||
|
|
||||||
|
this._persist();
|
||||||
|
}
|
||||||
|
|
||||||
|
get uuid () {
|
||||||
|
if (!this._keyObject) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._keyObject.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptPrivateKey (password) {
|
||||||
|
return decryptPrivateKey(this._keyObject, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
changePassword (key, password) {
|
||||||
|
return createKeyObject(key, password).then((keyObject) => {
|
||||||
|
this._keyObject = keyObject;
|
||||||
|
|
||||||
|
this._persist();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON () {
|
||||||
|
return {
|
||||||
|
keyObject: this._keyObject,
|
||||||
|
name: this._name,
|
||||||
|
meta: this._meta
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Account.fromPrivateKey = function (persist, key, password) {
|
||||||
|
return createKeyObject(key, password).then((keyObject) => {
|
||||||
|
const account = new Account(persist, { keyObject });
|
||||||
|
|
||||||
|
return account;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Account;
|
238
js/src/api-local/accounts/accounts.js
Normal file
238
js/src/api-local/accounts/accounts.js
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const EventEmitter = require('eventemitter3');
|
||||||
|
const { debounce } = require('lodash');
|
||||||
|
const localStore = require('store');
|
||||||
|
|
||||||
|
const Account = require('./account');
|
||||||
|
const { decryptPrivateKey } = require('../ethkey');
|
||||||
|
|
||||||
|
const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
|
||||||
|
const LS_STORE_KEY = '_parity::localAccounts';
|
||||||
|
|
||||||
|
class Accounts extends EventEmitter {
|
||||||
|
constructor (data = localStore.get(LS_STORE_KEY) || {}) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.persist = debounce(() => {
|
||||||
|
this._lastState = JSON.stringify(this);
|
||||||
|
|
||||||
|
localStore.set(LS_STORE_KEY, this);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
this._addAccount = this._addAccount.bind(this);
|
||||||
|
this._lastState = JSON.stringify(data);
|
||||||
|
|
||||||
|
window.addEventListener('storage', ({ key, newValue }) => {
|
||||||
|
if (key !== LS_STORE_KEY) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newValue !== this._lastState) {
|
||||||
|
console.log('Data changed in a second tab, syncing state');
|
||||||
|
|
||||||
|
this.restore(JSON.parse(newValue));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.restore(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
restore (data) {
|
||||||
|
const {
|
||||||
|
last = NULL_ADDRESS,
|
||||||
|
dappsDefault = NULL_ADDRESS,
|
||||||
|
store = {}
|
||||||
|
} = data;
|
||||||
|
|
||||||
|
this._last = last;
|
||||||
|
this._dappsDefaultAddress = dappsDefault;
|
||||||
|
this._store = {};
|
||||||
|
|
||||||
|
if (Array.isArray(store)) {
|
||||||
|
// Recover older version that stored accounts as an array
|
||||||
|
store.forEach((data) => {
|
||||||
|
const account = new Account(this.persist, data);
|
||||||
|
|
||||||
|
this._store[account.address] = account;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Object.keys(store).forEach((key) => {
|
||||||
|
this._store[key] = new Account(this.persist, store[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_addAccount (account) {
|
||||||
|
const { address } = account;
|
||||||
|
|
||||||
|
if (address in this._store && this._store[address].uuid) {
|
||||||
|
throw new Error(`Account ${address} already exists!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._store[address] = account;
|
||||||
|
this.lastAddress = address;
|
||||||
|
|
||||||
|
this.persist();
|
||||||
|
|
||||||
|
return account.address;
|
||||||
|
}
|
||||||
|
|
||||||
|
create (secret, password) {
|
||||||
|
const privateKey = Buffer.from(secret.slice(2), 'hex');
|
||||||
|
|
||||||
|
return Account
|
||||||
|
.fromPrivateKey(this.persist, privateKey, password)
|
||||||
|
.then(this._addAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreFromWallet (wallet, password) {
|
||||||
|
return decryptPrivateKey(wallet, password)
|
||||||
|
.then((privateKey) => {
|
||||||
|
if (!privateKey) {
|
||||||
|
throw new Error('Invalid password');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Account.fromPrivateKey(this.persist, privateKey, password);
|
||||||
|
})
|
||||||
|
.then(this._addAccount);
|
||||||
|
}
|
||||||
|
|
||||||
|
set lastAddress (value) {
|
||||||
|
this._last = value.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
get lastAddress () {
|
||||||
|
return this._last;
|
||||||
|
}
|
||||||
|
|
||||||
|
get dappsDefaultAddress () {
|
||||||
|
if (this._dappsDefaultAddress === NULL_ADDRESS) {
|
||||||
|
return this._last;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._dappsDefaultAddress in this._store) {
|
||||||
|
return this._dappsDefaultAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL_ADDRESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
set dappsDefaultAddress (value) {
|
||||||
|
this._dappsDefaultAddress = value.toLowerCase();
|
||||||
|
|
||||||
|
this.emit('dappsDefaultAddressChange', this._dappsDefaultAddress);
|
||||||
|
|
||||||
|
this.persist();
|
||||||
|
}
|
||||||
|
|
||||||
|
get (address) {
|
||||||
|
address = address.toLowerCase();
|
||||||
|
|
||||||
|
const account = this._store[address];
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
throw new Error(`Account not found: ${address}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastAddress = address;
|
||||||
|
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLazyCreate (address) {
|
||||||
|
address = address.toLowerCase();
|
||||||
|
|
||||||
|
this.lastAddress = address;
|
||||||
|
|
||||||
|
if (!(address in this._store)) {
|
||||||
|
this._store[address] = new Account(this.persist);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._store[address];
|
||||||
|
}
|
||||||
|
|
||||||
|
remove (address, password) {
|
||||||
|
address = address.toLowerCase();
|
||||||
|
|
||||||
|
const account = this.get(address);
|
||||||
|
|
||||||
|
if (!account) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!account.uuid) {
|
||||||
|
this.removeUnsafe(address);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return account
|
||||||
|
.isValidPassword(password)
|
||||||
|
.then((isValid) => {
|
||||||
|
if (!isValid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (address === this.lastAddress) {
|
||||||
|
this.lastAddress = NULL_ADDRESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.removeUnsafe(address);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeUnsafe (address) {
|
||||||
|
address = address.toLowerCase();
|
||||||
|
|
||||||
|
delete this._store[address];
|
||||||
|
|
||||||
|
this.persist();
|
||||||
|
}
|
||||||
|
|
||||||
|
allAddresses () {
|
||||||
|
return Object.keys(this._store);
|
||||||
|
}
|
||||||
|
|
||||||
|
accountAddresses () {
|
||||||
|
return Object
|
||||||
|
.keys(this._store)
|
||||||
|
.filter((address) => this._store[address].uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
map (mapper) {
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
Object.keys(this._store).forEach((key) => {
|
||||||
|
result[key] = mapper(this._store[key]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON () {
|
||||||
|
return {
|
||||||
|
last: this._last,
|
||||||
|
dappsDefault: this._dappsDefaultAddress,
|
||||||
|
store: this._store
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Accounts;
|
21
js/src/api-local/accounts/index.js
Normal file
21
js/src/api-local/accounts/index.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const Accounts = require('./accounts');
|
||||||
|
|
||||||
|
const accounts = new Accounts();
|
||||||
|
|
||||||
|
module.exports = accounts;
|
160
js/src/api-local/ethkey/ethkey.js
Normal file
160
js/src/api-local/ethkey/ethkey.js
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/* global WebAssembly */
|
||||||
|
|
||||||
|
const wasmBuffer = require('./ethkey.wasm.js');
|
||||||
|
|
||||||
|
const NOOP = () => {};
|
||||||
|
|
||||||
|
// WASM memory setup
|
||||||
|
const WASM_PAGE_SIZE = 65536;
|
||||||
|
const STATIC_BASE = 1024;
|
||||||
|
const STATICTOP = STATIC_BASE + WASM_PAGE_SIZE * 2;
|
||||||
|
const STACK_BASE = align(STATICTOP + 16);
|
||||||
|
const STACKTOP = STACK_BASE;
|
||||||
|
const TOTAL_STACK = 5 * 1024 * 1024;
|
||||||
|
const TOTAL_MEMORY = 16777216;
|
||||||
|
const STACK_MAX = STACK_BASE + TOTAL_STACK;
|
||||||
|
const DYNAMIC_BASE = STACK_MAX + 64;
|
||||||
|
const DYNAMICTOP_PTR = STACK_MAX;
|
||||||
|
|
||||||
|
function mockWebAssembly () {
|
||||||
|
function throwWasmError () {
|
||||||
|
throw new Error('Missing WebAssembly support');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple mock replacement
|
||||||
|
return {
|
||||||
|
Memory: class {
|
||||||
|
constructor () {
|
||||||
|
this.buffer = new ArrayBuffer(2048);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Table: class {
|
||||||
|
},
|
||||||
|
Module: class {
|
||||||
|
},
|
||||||
|
Instance: class {
|
||||||
|
constructor () {
|
||||||
|
this.exports = {
|
||||||
|
'_input_ptr': () => 0,
|
||||||
|
'_secret_ptr': () => 0,
|
||||||
|
'_public_ptr': () => 0,
|
||||||
|
'_address_ptr': () => 0,
|
||||||
|
'_ecpointg': NOOP,
|
||||||
|
'_brain': throwWasmError,
|
||||||
|
'_verify_secret': throwWasmError
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { Memory, Table, Module, Instance } = typeof WebAssembly !== 'undefined' ? WebAssembly : mockWebAssembly();
|
||||||
|
|
||||||
|
const wasmMemory = new Memory({
|
||||||
|
initial: TOTAL_MEMORY / WASM_PAGE_SIZE,
|
||||||
|
maximum: TOTAL_MEMORY / WASM_PAGE_SIZE
|
||||||
|
});
|
||||||
|
|
||||||
|
const wasmTable = new Table({
|
||||||
|
initial: 8,
|
||||||
|
maximum: 8,
|
||||||
|
element: 'anyfunc'
|
||||||
|
});
|
||||||
|
|
||||||
|
// TypedArray views into the memory
|
||||||
|
const wasmMemoryU8 = new Uint8Array(wasmMemory.buffer);
|
||||||
|
const wasmMemoryU32 = new Uint32Array(wasmMemory.buffer);
|
||||||
|
|
||||||
|
// Keep DYNAMIC_BASE in memory
|
||||||
|
wasmMemoryU32[DYNAMICTOP_PTR >> 2] = align(DYNAMIC_BASE);
|
||||||
|
|
||||||
|
function align (mem) {
|
||||||
|
const ALIGN_SIZE = 16;
|
||||||
|
|
||||||
|
return (Math.ceil(mem / ALIGN_SIZE) * ALIGN_SIZE) | 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function slice (ptr, len) {
|
||||||
|
return wasmMemoryU8.subarray(ptr, ptr + len);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required by emscripten
|
||||||
|
function abort (what) {
|
||||||
|
throw new Error(what || 'WASM abort');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required by emscripten
|
||||||
|
function abortOnCannotGrowMemory () {
|
||||||
|
abort(`Cannot enlarge memory arrays.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required by emscripten
|
||||||
|
function enlargeMemory () {
|
||||||
|
abortOnCannotGrowMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required by emscripten
|
||||||
|
function getTotalMemory () {
|
||||||
|
return TOTAL_MEMORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required by emscripten - used to perform memcpy on large data
|
||||||
|
function memcpy (dest, src, len) {
|
||||||
|
wasmMemoryU8.set(wasmMemoryU8.subarray(src, src + len), dest);
|
||||||
|
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronously compile WASM from the buffer
|
||||||
|
const wasmModule = new Module(wasmBuffer);
|
||||||
|
|
||||||
|
// Instantiated WASM module
|
||||||
|
const instance = new Instance(wasmModule, {
|
||||||
|
global: {},
|
||||||
|
env: {
|
||||||
|
DYNAMICTOP_PTR,
|
||||||
|
STACKTOP,
|
||||||
|
STACK_MAX,
|
||||||
|
abort,
|
||||||
|
enlargeMemory,
|
||||||
|
getTotalMemory,
|
||||||
|
abortOnCannotGrowMemory,
|
||||||
|
___lock: NOOP,
|
||||||
|
___syscall6: () => 0,
|
||||||
|
___setErrNo: (no) => no,
|
||||||
|
_abort: abort,
|
||||||
|
___syscall140: () => 0,
|
||||||
|
_emscripten_memcpy_big: memcpy,
|
||||||
|
___syscall54: () => 0,
|
||||||
|
___unlock: NOOP,
|
||||||
|
_llvm_trap: abort,
|
||||||
|
___syscall146: () => 0,
|
||||||
|
'memory': wasmMemory,
|
||||||
|
'table': wasmTable,
|
||||||
|
tableBase: 0,
|
||||||
|
memoryBase: STATIC_BASE
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const extern = instance.exports;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
extern,
|
||||||
|
slice
|
||||||
|
};
|
1
js/src/api-local/ethkey/ethkey.wasm.js
Normal file
1
js/src/api-local/ethkey/ethkey.wasm.js
Normal file
File diff suppressed because one or more lines are too long
55
js/src/api-local/ethkey/index.js
Normal file
55
js/src/api-local/ethkey/index.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const workerPool = require('./workerPool');
|
||||||
|
|
||||||
|
function createKeyObject (key, password) {
|
||||||
|
return workerPool.action('createKeyObject', { key, password })
|
||||||
|
.then((obj) => JSON.parse(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
function decryptPrivateKey (keyObject, password) {
|
||||||
|
return workerPool
|
||||||
|
.action('decryptPrivateKey', { keyObject, password })
|
||||||
|
.then((privateKey) => {
|
||||||
|
if (privateKey) {
|
||||||
|
return Buffer.from(privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function phraseToAddress (phrase) {
|
||||||
|
return phraseToWallet(phrase)
|
||||||
|
.then((wallet) => wallet.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
function phraseToWallet (phrase) {
|
||||||
|
return workerPool.action('phraseToWallet', phrase);
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifySecret (secret) {
|
||||||
|
return workerPool.action('verifySecret', secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createKeyObject,
|
||||||
|
decryptPrivateKey,
|
||||||
|
phraseToAddress,
|
||||||
|
phraseToWallet,
|
||||||
|
verifySecret
|
||||||
|
};
|
59
js/src/api-local/ethkey/index.spec.js
Normal file
59
js/src/api-local/ethkey/index.spec.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const { randomPhrase } = require('@parity/wordlist');
|
||||||
|
|
||||||
|
const { phraseToAddress, phraseToWallet } = require('./index');
|
||||||
|
|
||||||
|
// TODO: Skipping until Node.js 8.0 comes out and we can test WebAssembly
|
||||||
|
describe.skip('api/local/ethkey', () => {
|
||||||
|
describe('phraseToAddress', function () {
|
||||||
|
this.timeout(30000);
|
||||||
|
|
||||||
|
it('generates a valid address', () => {
|
||||||
|
const phrase = randomPhrase(12);
|
||||||
|
|
||||||
|
return phraseToAddress(phrase).then((address) => {
|
||||||
|
expect(address.length).to.be.equal(42);
|
||||||
|
expect(address.slice(0, 4)).to.be.equal('0x00');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates valid address for empty phrase', () => {
|
||||||
|
return phraseToAddress('').then((address) => {
|
||||||
|
expect(address).to.be.equal('0x00a329c0648769a73afac7f9381e08fb43dbea72');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('phraseToWallet', function () {
|
||||||
|
this.timeout(30000);
|
||||||
|
|
||||||
|
it('generates a valid wallet object', () => {
|
||||||
|
const phrase = randomPhrase(12);
|
||||||
|
|
||||||
|
return phraseToWallet(phrase).then((wallet) => {
|
||||||
|
expect(wallet.address.length).to.be.equal(42);
|
||||||
|
expect(wallet.secret.length).to.be.equal(66);
|
||||||
|
expect(wallet.public.length).to.be.equal(130);
|
||||||
|
|
||||||
|
expect(wallet.address.slice(0, 4)).to.be.equal('0x00');
|
||||||
|
expect(wallet.secret.slice(0, 2)).to.be.equal('0x');
|
||||||
|
expect(wallet.public.slice(0, 2)).to.be.equal('0x');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
139
js/src/api-local/ethkey/worker.js
Normal file
139
js/src/api-local/ethkey/worker.js
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const { bytesToHex } = require('@parity/api/lib/util/format');
|
||||||
|
|
||||||
|
const { extern, slice } = require('./ethkey.js');
|
||||||
|
|
||||||
|
const isWorker = typeof self !== 'undefined';
|
||||||
|
|
||||||
|
// Stay compatible between environments
|
||||||
|
if (!isWorker) {
|
||||||
|
const scope = typeof global === 'undefined' ? window : global;
|
||||||
|
|
||||||
|
scope.self = scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
// keythereum should never be used outside of the browser
|
||||||
|
let keythereum = require('keythereum');
|
||||||
|
|
||||||
|
if (isWorker) {
|
||||||
|
keythereum = self.keythereum;
|
||||||
|
}
|
||||||
|
|
||||||
|
function route ({ action, payload }) {
|
||||||
|
if (action in actions) {
|
||||||
|
return actions[action](payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const input = slice(extern._input_ptr(), 1024);
|
||||||
|
const secret = slice(extern._secret_ptr(), 32);
|
||||||
|
const publicKey = slice(extern._public_ptr(), 64);
|
||||||
|
const address = slice(extern._address_ptr(), 20);
|
||||||
|
|
||||||
|
extern._ecpointg();
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
phraseToWallet (phrase) {
|
||||||
|
const phraseUtf8 = Buffer.from(phrase, 'utf8');
|
||||||
|
|
||||||
|
if (phraseUtf8.length > input.length) {
|
||||||
|
throw new Error('Phrase is too long!');
|
||||||
|
}
|
||||||
|
|
||||||
|
input.set(phraseUtf8);
|
||||||
|
|
||||||
|
extern._brain(phraseUtf8.length);
|
||||||
|
|
||||||
|
const wallet = {
|
||||||
|
secret: bytesToHex(secret),
|
||||||
|
public: bytesToHex(publicKey),
|
||||||
|
address: bytesToHex(address)
|
||||||
|
};
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
},
|
||||||
|
|
||||||
|
verifySecret (key) {
|
||||||
|
const keyBuf = Buffer.from(key.slice(2), 'hex');
|
||||||
|
|
||||||
|
secret.set(keyBuf);
|
||||||
|
|
||||||
|
return extern._verify_secret();
|
||||||
|
},
|
||||||
|
|
||||||
|
createKeyObject ({ key, password }) {
|
||||||
|
key = Buffer.from(key);
|
||||||
|
password = Buffer.from(password);
|
||||||
|
|
||||||
|
const iv = keythereum.crypto.randomBytes(16);
|
||||||
|
const salt = keythereum.crypto.randomBytes(32);
|
||||||
|
const keyObject = keythereum.dump(password, key, salt, iv);
|
||||||
|
|
||||||
|
return JSON.stringify(keyObject);
|
||||||
|
},
|
||||||
|
|
||||||
|
decryptPrivateKey ({ keyObject, password }) {
|
||||||
|
password = Buffer.from(password);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const key = keythereum.recover(password, keyObject);
|
||||||
|
|
||||||
|
// Convert to array to safely send from the worker
|
||||||
|
return Array.from(key);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.onmessage = function ({ data }) {
|
||||||
|
try {
|
||||||
|
const result = route(data);
|
||||||
|
|
||||||
|
postMessage([null, result]);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
postMessage([err.toString(), null]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Emulate a web worker in Node.js
|
||||||
|
class KeyWorker {
|
||||||
|
postMessage (data) {
|
||||||
|
// Force async
|
||||||
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
const result = route(data);
|
||||||
|
|
||||||
|
this.onmessage({ data: [null, result] });
|
||||||
|
} catch (err) {
|
||||||
|
this.onmessage({ data: [err, null] });
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
onmessage (event) {
|
||||||
|
// no-op to be overriden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exports != null) {
|
||||||
|
exports.KeyWorker = KeyWorker;
|
||||||
|
}
|
110
js/src/api-local/ethkey/workerPool.js
Normal file
110
js/src/api-local/ethkey/workerPool.js
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Allow a web worker in the browser, with a fallback for Node.js
|
||||||
|
const hasWebWorkers = typeof Worker !== 'undefined';
|
||||||
|
const KeyWorker = hasWebWorkers
|
||||||
|
? require('worker-loader!./worker') // eslint-disable-line import/no-webpack-loader-syntax
|
||||||
|
: require('./worker').KeyWorker;
|
||||||
|
|
||||||
|
class WorkerContainer {
|
||||||
|
constructor () {
|
||||||
|
this.busy = false;
|
||||||
|
this._worker = new KeyWorker();
|
||||||
|
}
|
||||||
|
|
||||||
|
action (action, payload) {
|
||||||
|
if (this.busy) {
|
||||||
|
throw new Error('Cannot issue an action on a busy worker!');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.busy = true;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this._worker.postMessage({ action, payload });
|
||||||
|
this._worker.onmessage = ({ data }) => {
|
||||||
|
const [err, result] = data;
|
||||||
|
|
||||||
|
this.busy = false;
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
// `err` ought to be a String
|
||||||
|
reject(new Error(err));
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WorkerPool {
|
||||||
|
constructor () {
|
||||||
|
this.pool = [
|
||||||
|
new WorkerContainer(),
|
||||||
|
new WorkerContainer()
|
||||||
|
];
|
||||||
|
|
||||||
|
this.queue = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
_getContainer () {
|
||||||
|
return this.pool.find((container) => !container.busy);
|
||||||
|
}
|
||||||
|
|
||||||
|
action (action, payload) {
|
||||||
|
let container = this.pool.find((container) => !container.busy);
|
||||||
|
|
||||||
|
let promise;
|
||||||
|
|
||||||
|
// const start = Date.now();
|
||||||
|
|
||||||
|
if (container) {
|
||||||
|
promise = container.action(action, payload);
|
||||||
|
} else {
|
||||||
|
promise = new Promise((resolve, reject) => {
|
||||||
|
this.queue.push([action, payload, resolve]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise
|
||||||
|
.catch((err) => {
|
||||||
|
this.processQueue();
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
this.processQueue();
|
||||||
|
|
||||||
|
// console.log('Work done in ', Date.now() - start);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
processQueue () {
|
||||||
|
let container = this._getContainer();
|
||||||
|
|
||||||
|
while (container && this.queue.length > 0) {
|
||||||
|
const [action, payload, resolve] = this.queue.shift();
|
||||||
|
|
||||||
|
resolve(container.action(action, payload));
|
||||||
|
container = this._getContainer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new WorkerPool();
|
309
js/src/api-local/localAccountsMiddleware.js
Normal file
309
js/src/api-local/localAccountsMiddleware.js
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const EthereumTx = require('ethereumjs-tx');
|
||||||
|
|
||||||
|
const { Middleware } = require('@parity/api/lib/transport');
|
||||||
|
const { inNumber16 } = require('@parity/api/lib/format/input');
|
||||||
|
const { randomPhrase } = require('@parity/wordlist');
|
||||||
|
|
||||||
|
const accounts = require('./accounts');
|
||||||
|
const transactions = require('./transactions');
|
||||||
|
const { phraseToWallet, phraseToAddress, verifySecret } = require('./ethkey');
|
||||||
|
|
||||||
|
class LocalAccountsMiddleware extends Middleware {
|
||||||
|
constructor (transport) {
|
||||||
|
super(transport);
|
||||||
|
|
||||||
|
const NOOP = () => {};
|
||||||
|
const register = this.register.bind(this);
|
||||||
|
const registerSubscribe = this.registerSubscribe.bind(this);
|
||||||
|
|
||||||
|
register('eth_accounts', () => {
|
||||||
|
return accounts.accountAddresses();
|
||||||
|
});
|
||||||
|
|
||||||
|
register('eth_coinbase', () => {
|
||||||
|
return accounts.lastAddress;
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_accountsInfo', () => {
|
||||||
|
return accounts.map(({ name }) => {
|
||||||
|
return { name };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_allAccountsInfo', () => {
|
||||||
|
return accounts.map(({ name, meta, uuid }) => {
|
||||||
|
return { name, meta, uuid };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_changePassword', ([address, oldPassword, newPassword]) => {
|
||||||
|
const account = accounts.get(address);
|
||||||
|
|
||||||
|
return account
|
||||||
|
.decryptPrivateKey(oldPassword)
|
||||||
|
.then((privateKey) => {
|
||||||
|
if (!privateKey) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
account.changePassword(privateKey, newPassword);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_checkRequest', ([id]) => {
|
||||||
|
return transactions.hash(id) || Promise.resolve(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_dappsList', () => {
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_defaultAccount', () => {
|
||||||
|
return accounts.dappsDefaultAddress;
|
||||||
|
});
|
||||||
|
|
||||||
|
registerSubscribe('parity_defaultAccount', (_, callback) => {
|
||||||
|
callback(null, accounts.dappsDefaultAddress);
|
||||||
|
|
||||||
|
accounts.on('dappsDefaultAddressChange', (address) => {
|
||||||
|
callback(null, accounts.dappsDefaultAddress);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_exportAccount', ([address, password]) => {
|
||||||
|
const account = accounts.get(address);
|
||||||
|
|
||||||
|
if (!password) {
|
||||||
|
password = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return account.isValidPassword(password)
|
||||||
|
.then((isValid) => {
|
||||||
|
if (!isValid) {
|
||||||
|
throw new Error('Invalid password');
|
||||||
|
}
|
||||||
|
|
||||||
|
return account.export();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_generateSecretPhrase', () => {
|
||||||
|
return randomPhrase(12);
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_getNewDappsAddresses', () => {
|
||||||
|
return accounts.accountAddresses();
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_getNewDappsDefaultAddress', () => {
|
||||||
|
return accounts.dappsDefaultAddress;
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_hardwareAccountsInfo', () => {
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
|
||||||
|
registerSubscribe('parity_hardwareAccountsInfo', NOOP);
|
||||||
|
|
||||||
|
register('parity_newAccountFromPhrase', ([phrase, password]) => {
|
||||||
|
return phraseToWallet(phrase)
|
||||||
|
.then((wallet) => {
|
||||||
|
return accounts.create(wallet.secret, password);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_newAccountFromSecret', ([secret, password]) => {
|
||||||
|
return verifySecret(secret)
|
||||||
|
.then((isValid) => {
|
||||||
|
if (!isValid) {
|
||||||
|
throw new Error('Invalid secret key');
|
||||||
|
}
|
||||||
|
|
||||||
|
return accounts.create(secret, password);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_newAccountFromWallet', ([json, password]) => {
|
||||||
|
if (!password) {
|
||||||
|
password = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return accounts.restoreFromWallet(JSON.parse(json), password);
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_setAccountMeta', ([address, meta]) => {
|
||||||
|
accounts.getLazyCreate(address).meta = meta;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_setAccountName', ([address, name]) => {
|
||||||
|
accounts.getLazyCreate(address).name = name;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_setNewDappsDefaultAddress', ([address]) => {
|
||||||
|
accounts.dappsDefaultAddress = address;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_postTransaction', ([tx]) => {
|
||||||
|
if (!tx.from) {
|
||||||
|
tx.from = accounts.lastAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.nonce = null;
|
||||||
|
tx.condition = null;
|
||||||
|
|
||||||
|
return transactions.add(tx);
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_phraseToAddress', ([phrase]) => {
|
||||||
|
return phraseToAddress(phrase);
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_useLocalAccounts', () => {
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_listGethAccounts', () => {
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_listOpenedVaults', () => {
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_listRecentDapps', () => {
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_listVaults', () => {
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_lockedHardwareAccountsInfo', () => {
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_hashContent', () => {
|
||||||
|
throw new Error('Functionality unavailable on a public wallet.');
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_killAccount', ([address, password]) => {
|
||||||
|
return accounts.remove(address, password);
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_removeAddress', ([address]) => {
|
||||||
|
return accounts.remove(address, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_testPassword', ([address, password]) => {
|
||||||
|
const account = accounts.get(address);
|
||||||
|
|
||||||
|
return account.isValidPassword(password);
|
||||||
|
});
|
||||||
|
|
||||||
|
register('parity_upgradeReady', () => {
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
register('signer_confirmRequest', ([id, modify, password]) => {
|
||||||
|
const {
|
||||||
|
gasPrice,
|
||||||
|
gas: gasLimit,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
value,
|
||||||
|
data
|
||||||
|
} = Object.assign(transactions.get(id), modify);
|
||||||
|
|
||||||
|
transactions.lock(id);
|
||||||
|
|
||||||
|
const account = accounts.get(from);
|
||||||
|
|
||||||
|
return Promise
|
||||||
|
.all([
|
||||||
|
this.rpcRequest('parity_nextNonce', [from]),
|
||||||
|
account.decryptPrivateKey(password)
|
||||||
|
])
|
||||||
|
.catch((err) => {
|
||||||
|
transactions.unlock(id);
|
||||||
|
|
||||||
|
// transaction got unlocked, can propagate rejection further
|
||||||
|
throw err;
|
||||||
|
})
|
||||||
|
.then(([nonce, privateKey]) => {
|
||||||
|
if (!privateKey) {
|
||||||
|
transactions.unlock(id);
|
||||||
|
|
||||||
|
throw new Error('Invalid password');
|
||||||
|
}
|
||||||
|
|
||||||
|
const tx = new EthereumTx({
|
||||||
|
nonce,
|
||||||
|
to,
|
||||||
|
data,
|
||||||
|
gasLimit: inNumber16(gasLimit),
|
||||||
|
gasPrice: inNumber16(gasPrice),
|
||||||
|
value: inNumber16(value)
|
||||||
|
});
|
||||||
|
|
||||||
|
tx.sign(privateKey);
|
||||||
|
|
||||||
|
const serializedTx = `0x${tx.serialize().toString('hex')}`;
|
||||||
|
|
||||||
|
return this.rpcRequest('eth_sendRawTransaction', [serializedTx]);
|
||||||
|
})
|
||||||
|
.then((hash) => {
|
||||||
|
transactions.confirm(id, hash);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
register('signer_generateAuthorizationToken', () => {
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
|
||||||
|
register('signer_rejectRequest', ([id]) => {
|
||||||
|
return transactions.reject(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
register('signer_requestsToConfirm', () => {
|
||||||
|
return transactions.requestsToConfirm();
|
||||||
|
});
|
||||||
|
|
||||||
|
registerSubscribe('signer_subscribePending', (_, callback) => {
|
||||||
|
callback(null, transactions.requestsToConfirm());
|
||||||
|
|
||||||
|
transactions.on('update', () => {
|
||||||
|
callback(null, transactions.requestsToConfirm());
|
||||||
|
});
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = LocalAccountsMiddleware;
|
160
js/src/api-local/localAccountsMiddleware.spec.js
Normal file
160
js/src/api-local/localAccountsMiddleware.spec.js
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/* eslint-disable no-unused-expressions */
|
||||||
|
|
||||||
|
const JsonRpcBase = require('@parity/api/lib/transport/jsonRpcBase');
|
||||||
|
|
||||||
|
const LocalAccountsMiddleware = require('./localAccountsMiddleware');
|
||||||
|
|
||||||
|
const RPC_RESPONSE = Symbol('RPC response');
|
||||||
|
const ADDRESS = '0x00a329c0648769a73afac7f9381e08fb43dbea72';
|
||||||
|
const SECRET = '0x4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7';
|
||||||
|
const PASSWORD = 'password';
|
||||||
|
|
||||||
|
const FOO_PHRASE = 'foobar';
|
||||||
|
const FOO_PASSWORD = 'foopass';
|
||||||
|
const FOO_ADDRESS = '0x007ef7ac1058e5955e366ab9d6b6c4ebcc937e7e';
|
||||||
|
|
||||||
|
class MockedTransport extends JsonRpcBase {
|
||||||
|
_execute (method, params) {
|
||||||
|
return RPC_RESPONSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip till all CI runs on Node 8+
|
||||||
|
describe.skip('api/local/LocalAccountsMiddleware', function () {
|
||||||
|
this.timeout(30000);
|
||||||
|
|
||||||
|
let transport;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
transport = new MockedTransport();
|
||||||
|
transport.addMiddleware(LocalAccountsMiddleware);
|
||||||
|
|
||||||
|
// Same as `parity_newAccountFromPhrase` with empty phrase
|
||||||
|
return transport
|
||||||
|
.execute('parity_newAccountFromSecret', [SECRET, PASSWORD])
|
||||||
|
.catch((_err) => {
|
||||||
|
// Ignore the error - all instances of LocalAccountsMiddleware
|
||||||
|
// share account storage
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('registers all necessary methods', () => {
|
||||||
|
return Promise
|
||||||
|
.all([
|
||||||
|
'eth_accounts',
|
||||||
|
'eth_coinbase',
|
||||||
|
'parity_accountsInfo',
|
||||||
|
'parity_allAccountsInfo',
|
||||||
|
'parity_changePassword',
|
||||||
|
'parity_checkRequest',
|
||||||
|
'parity_defaultAccount',
|
||||||
|
'parity_generateSecretPhrase',
|
||||||
|
'parity_getNewDappsAddresses',
|
||||||
|
'parity_hardwareAccountsInfo',
|
||||||
|
'parity_newAccountFromPhrase',
|
||||||
|
'parity_newAccountFromSecret',
|
||||||
|
'parity_setAccountMeta',
|
||||||
|
'parity_setAccountName',
|
||||||
|
'parity_postTransaction',
|
||||||
|
'parity_phraseToAddress',
|
||||||
|
'parity_useLocalAccounts',
|
||||||
|
'parity_listGethAccounts',
|
||||||
|
'parity_listOpenedVaults',
|
||||||
|
'parity_listRecentDapps',
|
||||||
|
'parity_listVaults',
|
||||||
|
'parity_killAccount',
|
||||||
|
'parity_testPassword',
|
||||||
|
'signer_confirmRequest',
|
||||||
|
'signer_rejectRequest',
|
||||||
|
'signer_requestsToConfirm'
|
||||||
|
].map((method) => {
|
||||||
|
return transport
|
||||||
|
.execute(method)
|
||||||
|
.then((result) => {
|
||||||
|
expect(result).not.to.be.equal(RPC_RESPONSE);
|
||||||
|
})
|
||||||
|
// Some errors are expected here since we are calling methods
|
||||||
|
// without parameters.
|
||||||
|
.catch((_) => {});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows non-registered methods through', () => {
|
||||||
|
return transport
|
||||||
|
.execute('eth_getBalance', ['0x407d73d8a49eeb85d32cf465507dd71d507100c1'])
|
||||||
|
.then((result) => {
|
||||||
|
expect(result).to.be.equal(RPC_RESPONSE);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can handle `eth_accounts`', () => {
|
||||||
|
return transport
|
||||||
|
.execute('eth_accounts')
|
||||||
|
.then((accounts) => {
|
||||||
|
expect(accounts.length).to.be.equal(1);
|
||||||
|
expect(accounts[0]).to.be.equal(ADDRESS);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can handle `parity_defaultAccount`', () => {
|
||||||
|
return transport
|
||||||
|
.execute('parity_defaultAccount')
|
||||||
|
.then((address) => {
|
||||||
|
expect(address).to.be.equal(ADDRESS);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can handle `parity_phraseToAddress`', () => {
|
||||||
|
return transport
|
||||||
|
.execute('parity_phraseToAddress', [''])
|
||||||
|
.then((address) => {
|
||||||
|
expect(address).to.be.equal(ADDRESS);
|
||||||
|
|
||||||
|
return transport.execute('parity_phraseToAddress', [FOO_PHRASE]);
|
||||||
|
})
|
||||||
|
.then((address) => {
|
||||||
|
expect(address).to.be.equal(FOO_ADDRESS);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can create and kill an account', () => {
|
||||||
|
return transport
|
||||||
|
.execute('parity_newAccountFromPhrase', [FOO_PHRASE, FOO_PASSWORD])
|
||||||
|
.then((address) => {
|
||||||
|
expect(address).to.be.equal(FOO_ADDRESS);
|
||||||
|
|
||||||
|
return transport.execute('eth_accounts');
|
||||||
|
})
|
||||||
|
.then((accounts) => {
|
||||||
|
expect(accounts.length).to.be.equal(2);
|
||||||
|
expect(accounts.includes(FOO_ADDRESS)).to.be.true;
|
||||||
|
|
||||||
|
return transport.execute('parity_killAccount', [FOO_ADDRESS, FOO_PASSWORD]);
|
||||||
|
})
|
||||||
|
.then((result) => {
|
||||||
|
expect(result).to.be.true;
|
||||||
|
|
||||||
|
return transport.execute('eth_accounts');
|
||||||
|
})
|
||||||
|
.then((accounts) => {
|
||||||
|
expect(accounts.length).to.be.equal(1);
|
||||||
|
expect(accounts.includes(FOO_ADDRESS)).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
161
js/src/api-local/transactions.js
Normal file
161
js/src/api-local/transactions.js
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const EventEmitter = require('eventemitter3');
|
||||||
|
|
||||||
|
const { toHex } = require('@parity/api/lib/util/format');
|
||||||
|
const { TransportError } = require('@parity/api/lib/transport');
|
||||||
|
|
||||||
|
const AWAITING = Symbol('awaiting');
|
||||||
|
const LOCKED = Symbol('locked');
|
||||||
|
const CONFIRMED = Symbol('confirmed');
|
||||||
|
const REJECTED = Symbol('rejected');
|
||||||
|
|
||||||
|
class Transactions extends EventEmitter {
|
||||||
|
constructor () {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// should only really be needed in the constructor and tests
|
||||||
|
reset () {
|
||||||
|
this._id = 1;
|
||||||
|
this._states = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
nextId () {
|
||||||
|
return toHex(this._id++);
|
||||||
|
}
|
||||||
|
|
||||||
|
add (tx) {
|
||||||
|
const id = this.nextId();
|
||||||
|
|
||||||
|
this._states[id] = {
|
||||||
|
status: AWAITING,
|
||||||
|
transaction: tx
|
||||||
|
};
|
||||||
|
|
||||||
|
this.emit('update');
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
get (id) {
|
||||||
|
const state = this._states[id];
|
||||||
|
|
||||||
|
if (!state || state.status !== AWAITING) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.transaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (id) {
|
||||||
|
const state = this._states[id];
|
||||||
|
|
||||||
|
if (!state || state.status !== AWAITING) {
|
||||||
|
throw new Error('Trying to lock an invalid transaction');
|
||||||
|
}
|
||||||
|
|
||||||
|
state.status = LOCKED;
|
||||||
|
|
||||||
|
this.emit('update');
|
||||||
|
}
|
||||||
|
|
||||||
|
unlock (id) {
|
||||||
|
const state = this._states[id];
|
||||||
|
|
||||||
|
if (!state || state.status !== LOCKED) {
|
||||||
|
throw new Error('Trying to unlock an invalid transaction');
|
||||||
|
}
|
||||||
|
|
||||||
|
state.status = AWAITING;
|
||||||
|
|
||||||
|
this.emit('update');
|
||||||
|
}
|
||||||
|
|
||||||
|
hash (id) {
|
||||||
|
const state = this._states[id];
|
||||||
|
|
||||||
|
if (!state) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (state.status) {
|
||||||
|
case REJECTED:
|
||||||
|
throw TransportError.requestRejected();
|
||||||
|
case CONFIRMED:
|
||||||
|
return state.hash;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm (id, hash) {
|
||||||
|
const state = this._states[id];
|
||||||
|
const status = state ? state.status : null;
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case AWAITING: break;
|
||||||
|
case LOCKED: break;
|
||||||
|
default: throw new Error('Trying to confirm an invalid transaction');
|
||||||
|
}
|
||||||
|
|
||||||
|
state.hash = hash;
|
||||||
|
state.status = CONFIRMED;
|
||||||
|
|
||||||
|
this.emit('update');
|
||||||
|
}
|
||||||
|
|
||||||
|
reject (id) {
|
||||||
|
const state = this._states[id];
|
||||||
|
|
||||||
|
if (!state) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.status = REJECTED;
|
||||||
|
|
||||||
|
this.emit('update');
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestsToConfirm () {
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
Object.keys(this._states).forEach((id) => {
|
||||||
|
const state = this._states[id];
|
||||||
|
|
||||||
|
if (state.status === AWAITING) {
|
||||||
|
result.push({
|
||||||
|
id,
|
||||||
|
origin: {
|
||||||
|
signer: '0x0'
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
sendTransaction: state.transaction
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new Transactions();
|
88
js/src/api-local/transactions.spec.js
Normal file
88
js/src/api-local/transactions.spec.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/* eslint-disable no-unused-expressions */
|
||||||
|
|
||||||
|
const { TransportError } = require('@parity/api/lib/transport/error');
|
||||||
|
|
||||||
|
const transactions = require('./transactions');
|
||||||
|
|
||||||
|
const DUMMY_TX = 'dummy';
|
||||||
|
|
||||||
|
describe('api/local/transactions', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
transactions.reset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can store transactions', () => {
|
||||||
|
const id1 = transactions.add(DUMMY_TX);
|
||||||
|
const id2 = transactions.add(DUMMY_TX);
|
||||||
|
const requests = transactions.requestsToConfirm();
|
||||||
|
|
||||||
|
expect(id1).to.be.equal('0x1');
|
||||||
|
expect(id2).to.be.equal('0x2');
|
||||||
|
expect(requests.length).to.be.equal(2);
|
||||||
|
expect(requests[0].id).to.be.equal(id1);
|
||||||
|
expect(requests[1].id).to.be.equal(id2);
|
||||||
|
expect(requests[0].payload.sendTransaction).to.be.equal(DUMMY_TX);
|
||||||
|
expect(requests[1].payload.sendTransaction).to.be.equal(DUMMY_TX);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can confirm transactions', () => {
|
||||||
|
const id1 = transactions.add(DUMMY_TX);
|
||||||
|
const id2 = transactions.add(DUMMY_TX);
|
||||||
|
|
||||||
|
const hash1 = '0x1111111111111111111111111111111111111111';
|
||||||
|
const hash2 = '0x2222222222222222222222222222222222222222';
|
||||||
|
|
||||||
|
transactions.confirm(id1, hash1);
|
||||||
|
transactions.confirm(id2, hash2);
|
||||||
|
|
||||||
|
const requests = transactions.requestsToConfirm();
|
||||||
|
|
||||||
|
expect(requests.length).to.be.equal(0);
|
||||||
|
expect(transactions.hash(id1)).to.be.equal(hash1);
|
||||||
|
expect(transactions.hash(id2)).to.be.equal(hash2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can reject transactions', () => {
|
||||||
|
const id = transactions.add(DUMMY_TX);
|
||||||
|
|
||||||
|
transactions.reject(id);
|
||||||
|
|
||||||
|
const requests = transactions.requestsToConfirm();
|
||||||
|
|
||||||
|
expect(requests.length).to.be.equal(0);
|
||||||
|
expect(() => transactions.hash(id)).to.throw(TransportError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can lock and confirm transactions', () => {
|
||||||
|
const id = transactions.add(DUMMY_TX);
|
||||||
|
const hash = '0x1111111111111111111111111111111111111111';
|
||||||
|
|
||||||
|
transactions.lock(id);
|
||||||
|
|
||||||
|
const requests = transactions.requestsToConfirm();
|
||||||
|
|
||||||
|
expect(requests.length).to.be.equal(0);
|
||||||
|
expect(transactions.get(id)).to.be.null;
|
||||||
|
expect(transactions.hash(id)).to.be.null;
|
||||||
|
|
||||||
|
transactions.confirm(id, hash);
|
||||||
|
|
||||||
|
expect(transactions.hash(id)).to.be.equal(hash);
|
||||||
|
});
|
||||||
|
});
|
@ -6,7 +6,6 @@
|
|||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<link rel="icon" href="/parity-logo-black-no-text.png" type="image/png">
|
<link rel="icon" href="/parity-logo-black-no-text.png" type="image/png">
|
||||||
<title>dev::Parity.js</title>
|
<title>dev::Parity.js</title>
|
||||||
<script src="/parity-utils/parity.js"></script>
|
|
||||||
<style>
|
<style>
|
||||||
.box {
|
.box {
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
@ -22,7 +21,12 @@
|
|||||||
<div class="box">
|
<div class="box">
|
||||||
best block #<span id="blockNumber">unknown</span>
|
best block #<span id="blockNumber">unknown</span>
|
||||||
</div>
|
</div>
|
||||||
|
<script src="/parity-utils/inject.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
console.log('window.ethereum', window.ethereum);
|
||||||
|
console.log('window.ethereum.isParity', window.ethereum.isParity);
|
||||||
|
console.log('window.parity', window.parity);
|
||||||
|
|
||||||
window.parity.api.subscribe('eth_blockNumber', function (error, blockNumber) {
|
window.parity.api.subscribe('eth_blockNumber', function (error, blockNumber) {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.log('error', error);
|
console.log('error', error);
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<link rel="icon" href="/parity-logo-black-no-text.png" type="image/png">
|
<link rel="icon" href="/parity-logo-black-no-text.png" type="image/png">
|
||||||
<title>dev::Web3</title>
|
<title>dev::Web3</title>
|
||||||
<script src="/parity-utils/web3.js"></script>
|
|
||||||
<style>
|
<style>
|
||||||
.box {
|
.box {
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
@ -22,9 +21,18 @@
|
|||||||
<div class="box">
|
<div class="box">
|
||||||
best block #<span id="blockNumber">unknown</span>
|
best block #<span id="blockNumber">unknown</span>
|
||||||
</div>
|
</div>
|
||||||
|
<script src="/parity-utils/inject.js"></script>
|
||||||
|
<script src="https://rawgit.com/ethereum/web3.js/develop/dist/web3.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
console.log('window.ethereum', window.ethereum);
|
||||||
|
console.log('window.ethereum.isParity', window.ethereum.isParity);
|
||||||
|
console.log('window.web3.currentProvider', window.web3.currentProvider, window.web3.currentProvider.send, window.web3.currentProvider.sendAsync);
|
||||||
|
console.log('window.web3.currentProvider.isParity', window.web3.currentProvider.isParity);
|
||||||
|
|
||||||
|
const web3 = new Web3(window.web3.currentProvider);
|
||||||
|
|
||||||
window.setInterval(function () {
|
window.setInterval(function () {
|
||||||
window.web3.eth.getBlockNumber(function (error, blockNumber) {
|
web3.eth.getBlockNumber(function (error, blockNumber) {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('error', error);
|
console.error('error', error);
|
||||||
return;
|
return;
|
||||||
|
@ -22,12 +22,12 @@ import { AppContainer } from 'react-hot-loader';
|
|||||||
|
|
||||||
import injectTapEventPlugin from 'react-tap-event-plugin';
|
import injectTapEventPlugin from 'react-tap-event-plugin';
|
||||||
|
|
||||||
import ContractInstances from '@parity/shared/contracts';
|
import ContractInstances from '@parity/shared/lib/contracts';
|
||||||
import { initStore } from '@parity/shared/redux';
|
import { initStore } from '@parity/shared/lib/redux';
|
||||||
import { setApi } from '@parity/shared/redux/providers/apiActions';
|
import { setApi } from '@parity/shared/lib/redux/providers/apiActions';
|
||||||
import ContextProvider from '@parity/ui/ContextProvider';
|
import ContextProvider from '@parity/ui/lib/ContextProvider';
|
||||||
import muiTheme from '@parity/ui/Theme';
|
import muiTheme from '@parity/ui/lib/Theme';
|
||||||
import { patchApi } from '@parity/shared/util/tx';
|
import { patchApi } from '@parity/shared/lib/util/tx';
|
||||||
|
|
||||||
import SecureApi from './secureApi';
|
import SecureApi from './secureApi';
|
||||||
|
|
||||||
|
@ -23,11 +23,11 @@ import injectTapEventPlugin from 'react-tap-event-plugin';
|
|||||||
import { IndexRoute, Redirect, Route, Router, hashHistory } from 'react-router';
|
import { IndexRoute, Redirect, Route, Router, hashHistory } from 'react-router';
|
||||||
import qs from 'querystring';
|
import qs from 'querystring';
|
||||||
|
|
||||||
import ContractInstances from '@parity/shared/contracts';
|
import ContractInstances from '@parity/shared/lib/contracts';
|
||||||
import { initStore } from '@parity/shared/redux';
|
import { initStore } from '@parity/shared/lib/redux';
|
||||||
import ContextProvider from '@parity/ui/ContextProvider';
|
import ContextProvider from '@parity/ui/lib/ContextProvider';
|
||||||
|
|
||||||
import '@parity/shared/environment';
|
import '@parity/shared/lib/environment';
|
||||||
|
|
||||||
import Application from './Application';
|
import Application from './Application';
|
||||||
import Dapp from './Dapp';
|
import Dapp from './Dapp';
|
||||||
|
@ -14,8 +14,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import 'whatwg-fetch';
|
|
||||||
|
|
||||||
import Api from '@parity/api';
|
import Api from '@parity/api';
|
||||||
import Web3 from 'web3';
|
import Web3 from 'web3';
|
||||||
|
|
||||||
@ -52,8 +50,12 @@ function initProvider () {
|
|||||||
|
|
||||||
function initWeb3 (ethereum) {
|
function initWeb3 (ethereum) {
|
||||||
// FIXME: Use standard provider for web3
|
// FIXME: Use standard provider for web3
|
||||||
const http = new Web3.providers.HttpProvider('/rpc/');
|
const provider = new Api.Provider.SendAsync(ethereum);
|
||||||
const web3 = new Web3(http);
|
const web3 = new Web3(provider);
|
||||||
|
|
||||||
|
if (!web3.currentProvider) {
|
||||||
|
web3.currentProvider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
// set default account
|
// set default account
|
||||||
web3.eth.getAccounts((error, accounts) => {
|
web3.eth.getAccounts((error, accounts) => {
|
||||||
@ -78,9 +80,11 @@ function initParity (ethereum) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const ethereum = initProvider();
|
if (typeof window !== 'undefined' && !window.isParity) {
|
||||||
|
const ethereum = initProvider();
|
||||||
|
|
||||||
initWeb3(ethereum);
|
initWeb3(ethereum);
|
||||||
initParity(ethereum);
|
initParity(ethereum);
|
||||||
|
|
||||||
console.warn('Deprecation: Dapps should only used the exposed EthereumProvider on `window.ethereum`, the use of `window.parity` and `window.web3` will be removed in future versions of this injector');
|
console.warn('Deprecation: Dapps should only used the exposed EthereumProvider on `window.ethereum`, the use of `window.parity` and `window.web3` will be removed in future versions of this injector');
|
||||||
|
}
|
||||||
|
23
js/src/inject.script.js
Normal file
23
js/src/inject.script.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const script = document.createElement('script');
|
||||||
|
|
||||||
|
script.type = 'text/javascript';
|
||||||
|
script.onload = function () {};
|
||||||
|
script.src = '/parity-utils/inject.js';
|
||||||
|
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(script);
|
@ -18,7 +18,7 @@ import uniq from 'lodash.uniq';
|
|||||||
import store from 'store';
|
import store from 'store';
|
||||||
|
|
||||||
import Api from '@parity/api';
|
import Api from '@parity/api';
|
||||||
import { LOG_KEYS, getLogger } from '@parity/shared/config';
|
import { LOG_KEYS, getLogger } from '@parity/shared/lib/config';
|
||||||
|
|
||||||
const log = getLogger(LOG_KEYS.Signer);
|
const log = getLogger(LOG_KEYS.Signer);
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
import flatten from 'lodash.flatten';
|
import flatten from 'lodash.flatten';
|
||||||
|
|
||||||
import { sha3 } from '@parity/api/util/sha3';
|
import { sha3 } from '@parity/api/lib/util/sha3';
|
||||||
import VisibleStore from '@parity/shared/mobx/dappsStore';
|
import VisibleStore from '@parity/shared/lib/mobx/dappsStore';
|
||||||
|
|
||||||
import RequestStore from './DappRequests/store';
|
import RequestStore from './DappRequests/store';
|
||||||
import filteredRequests from './DappRequests/filteredRequests';
|
import filteredRequests from './DappRequests/filteredRequests';
|
||||||
|
@ -14,9 +14,9 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import parity from '@parity/jsonrpc/interfaces/parity';
|
import parity from '@parity/jsonrpc/lib/interfaces/parity';
|
||||||
import signer from '@parity/jsonrpc/interfaces/signer';
|
import signer from '@parity/jsonrpc/lib/interfaces/signer';
|
||||||
import trace from '@parity/jsonrpc/interfaces/trace';
|
import trace from '@parity/jsonrpc/lib/interfaces/trace';
|
||||||
|
|
||||||
export default function web3extensions (web3) {
|
export default function web3extensions (web3) {
|
||||||
const { Method } = web3._extend;
|
const { Method } = web3._extend;
|
||||||
|
@ -29,8 +29,8 @@ const rulesEs6 = require('./rules/es6');
|
|||||||
const rulesParity = require('./rules/parity');
|
const rulesParity = require('./rules/parity');
|
||||||
const Shared = require('./shared');
|
const Shared = require('./shared');
|
||||||
|
|
||||||
const DAPPS_BUILTIN = require('@parity/shared/config/dappsBuiltin.json');
|
const DAPPS_BUILTIN = require('@parity/shared/lib/config/dappsBuiltin.json');
|
||||||
const DAPPS_VIEWS = require('@parity/shared/config/dappsViews.json');
|
const DAPPS_VIEWS = require('@parity/shared/lib/config/dappsViews.json');
|
||||||
const DAPPS_ALL = []
|
const DAPPS_ALL = []
|
||||||
.concat(DAPPS_BUILTIN, DAPPS_VIEWS)
|
.concat(DAPPS_BUILTIN, DAPPS_VIEWS)
|
||||||
.filter((dapp) => !dapp.skipBuild)
|
.filter((dapp) => !dapp.skipBuild)
|
||||||
@ -52,7 +52,7 @@ const entry = isEmbed
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
cache: !isProd,
|
cache: !isProd,
|
||||||
devtool: isProd
|
devtool: isProd
|
||||||
? '#source-map'
|
? false
|
||||||
: '#eval',
|
: '#eval',
|
||||||
context: path.join(__dirname, '../src'),
|
context: path.join(__dirname, '../src'),
|
||||||
entry,
|
entry,
|
||||||
@ -183,6 +183,14 @@ module.exports = {
|
|||||||
|
|
||||||
new CopyWebpackPlugin(
|
new CopyWebpackPlugin(
|
||||||
flatten([
|
flatten([
|
||||||
|
{
|
||||||
|
from: path.join(__dirname, '../src/dev.web3.html'),
|
||||||
|
to: 'dev.web3/index.html'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: path.join(__dirname, '../src/dev.parity.html'),
|
||||||
|
to: 'dev.parity/index.html'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
from: path.join(__dirname, '../src/error_pages.css'),
|
from: path.join(__dirname, '../src/error_pages.css'),
|
||||||
to: 'styles.css'
|
to: 'styles.css'
|
||||||
|
@ -46,4 +46,3 @@ compiler.run(function handler (err, stats) {
|
|||||||
process.stdout.write(output);
|
process.stdout.write(output);
|
||||||
process.stdout.write('\n\n');
|
process.stdout.write('\n\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -14,8 +14,6 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Run with `webpack --config webpack.libraries.js`
|
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const rulesEs6 = require('./rules/es6');
|
const rulesEs6 = require('./rules/es6');
|
||||||
@ -28,12 +26,12 @@ const DEST = process.env.BUILD_DEST || '.build';
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
context: path.join(__dirname, '../src'),
|
context: path.join(__dirname, '../src'),
|
||||||
devtool: isProd
|
devtool: isProd
|
||||||
? '#source-map'
|
? false
|
||||||
: '#eval',
|
: '#eval',
|
||||||
entry: {
|
entry: {
|
||||||
inject: ['./inject.js'],
|
inject: ['./inject.js'],
|
||||||
parity: ['./inject.js'],
|
parity: ['./inject.script.js'],
|
||||||
web3: ['./inject.js']
|
web3: ['./inject.script.js']
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
path: path.join(__dirname, '../', DEST),
|
path: path.join(__dirname, '../', DEST),
|
||||||
@ -43,9 +41,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {}
|
||||||
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
node: {
|
node: {
|
@ -1,127 +0,0 @@
|
|||||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
|
||||||
// This file is part of Parity.
|
|
||||||
|
|
||||||
// Parity is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
|
|
||||||
// Parity is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
const path = require('path');
|
|
||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
|
||||||
const packageJson = require('../package.json');
|
|
||||||
|
|
||||||
const rulesEs6 = require('./rules/es6');
|
|
||||||
const rulesParity = require('./rules/parity');
|
|
||||||
const Shared = require('./shared');
|
|
||||||
|
|
||||||
const LIBRARY = process.env.LIBRARY;
|
|
||||||
|
|
||||||
if (!LIBRARY) {
|
|
||||||
console.error('$LIBRARY environment variable not defined');
|
|
||||||
process.exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const SRC = LIBRARY.toLowerCase();
|
|
||||||
const OUTPUT_PATH = path.join(__dirname, '../.npmjs', SRC);
|
|
||||||
|
|
||||||
const TEST_CONTEXT = SRC === 'parity'
|
|
||||||
? '../npm/parity/test/'
|
|
||||||
: `../packages/${SRC}/`;
|
|
||||||
|
|
||||||
console.log(`Building ${LIBRARY} from library.${SRC}.js to .npmjs/${SRC}`);
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
context: path.join(__dirname, '../src'),
|
|
||||||
target: 'node',
|
|
||||||
entry: `library.${SRC}.js`,
|
|
||||||
output: {
|
|
||||||
path: OUTPUT_PATH,
|
|
||||||
filename: 'library.js',
|
|
||||||
library: LIBRARY,
|
|
||||||
libraryTarget: 'umd',
|
|
||||||
umdNamedDefine: true
|
|
||||||
},
|
|
||||||
externals: {
|
|
||||||
'node-fetch': 'node-fetch'
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
rulesParity,
|
|
||||||
rulesEs6,
|
|
||||||
{
|
|
||||||
test: /(\.jsx|\.js)$/,
|
|
||||||
use: ['babel-loader'],
|
|
||||||
exclude: /node_modules/
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
node: {
|
|
||||||
fs: 'empty'
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
alias: {},
|
|
||||||
modules: [
|
|
||||||
path.join(__dirname, '../node_modules')
|
|
||||||
],
|
|
||||||
extensions: ['.json', '.js', '.jsx']
|
|
||||||
},
|
|
||||||
|
|
||||||
plugins: Shared.getPlugins().concat([
|
|
||||||
new CopyWebpackPlugin([
|
|
||||||
{
|
|
||||||
from: `../npm/${SRC}/package.json`,
|
|
||||||
to: 'package.json',
|
|
||||||
transform: function (content, path) {
|
|
||||||
const json = JSON.parse(content.toString());
|
|
||||||
|
|
||||||
json.devDependencies.chai = packageJson.devDependencies.chai;
|
|
||||||
json.devDependencies.mocha = packageJson.devDependencies.mocha;
|
|
||||||
json.devDependencies.nock = packageJson.devDependencies.nock;
|
|
||||||
json.scripts.test = 'mocha \'test/*.spec.js\'';
|
|
||||||
json.version = packageJson.version;
|
|
||||||
|
|
||||||
return new Buffer(JSON.stringify(json, null, ' '), 'utf-8');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
from: '../LICENSE'
|
|
||||||
},
|
|
||||||
|
|
||||||
// Copy the base test config
|
|
||||||
{
|
|
||||||
from: '../npm/test',
|
|
||||||
to: 'test'
|
|
||||||
},
|
|
||||||
|
|
||||||
// Copy the actual tests
|
|
||||||
{
|
|
||||||
context: TEST_CONTEXT,
|
|
||||||
from: '**/*.spec.js',
|
|
||||||
to: 'test',
|
|
||||||
transform: function (content, path) {
|
|
||||||
let output = content.toString();
|
|
||||||
|
|
||||||
// Don't skip tests
|
|
||||||
output = output.replace(/describe\.skip/, 'describe');
|
|
||||||
|
|
||||||
// Require parent library
|
|
||||||
output = output.replace('require(\'./\')', 'require(\'../\')');
|
|
||||||
|
|
||||||
return new Buffer(output, 'utf-8');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
from: `../npm/${SRC}/README.md`,
|
|
||||||
to: 'README.md'
|
|
||||||
}
|
|
||||||
], { copyUnmodified: true })
|
|
||||||
])
|
|
||||||
};
|
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
include: /node_modules\/(get-own-enumerable-property-symbols|ethereumjs-tx|stringify-object)/,
|
include: /(get-own-enumerable-property-symbols|ethereumjs-tx|stringify-object)/,
|
||||||
use: [ {
|
use: [ {
|
||||||
loader: 'happypack/loader',
|
loader: 'happypack/loader',
|
||||||
options: {
|
options: {
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
include: /node_modules\/(@parity|oo7)\//,
|
include: /(dapp-|plugin-|oo7)/,
|
||||||
use: [ {
|
use: [ {
|
||||||
loader: 'happypack/loader',
|
loader: 'happypack/loader',
|
||||||
options: {
|
options: {
|
||||||
|
Loading…
Reference in New Issue
Block a user