Merge branch 'task/fix-safe-creation' of github.com:gnosis/safe-react into task/fix-safe-creation
This commit is contained in:
commit
4bc3ac360b
36
.babelrc
36
.babelrc
|
@ -7,19 +7,41 @@
|
|||
{
|
||||
"forceAllTransforms": true
|
||||
}
|
||||
],
|
||||
["@babel/preset-stage-0", { "decoratorsLegacy": true }]
|
||||
]
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-syntax-dynamic-import",
|
||||
"transform-es3-member-expression-literals",
|
||||
"transform-es3-property-literals"
|
||||
"@babel/plugin-transform-member-expression-literals",
|
||||
"@babel/plugin-transform-property-literals",
|
||||
"@babel/plugin-syntax-import-meta",
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-proposal-json-strings",
|
||||
[
|
||||
"@babel/plugin-proposal-decorators",
|
||||
{
|
||||
"legacy": true
|
||||
}
|
||||
],
|
||||
"@babel/plugin-proposal-function-sent",
|
||||
"@babel/plugin-proposal-export-namespace-from",
|
||||
"@babel/plugin-proposal-numeric-separator",
|
||||
"@babel/plugin-proposal-throw-expressions",
|
||||
"@babel/plugin-proposal-export-default-from",
|
||||
"@babel/plugin-proposal-logical-assignment-operators",
|
||||
"@babel/plugin-proposal-optional-chaining",
|
||||
[
|
||||
"@babel/plugin-proposal-pipeline-operator",
|
||||
{
|
||||
"proposal": "minimal"
|
||||
}
|
||||
],
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||
"@babel/plugin-proposal-do-expressions",
|
||||
"@babel/plugin-proposal-function-bind"
|
||||
],
|
||||
"env": {
|
||||
"test": {
|
||||
"plugins": [
|
||||
"dynamic-import-node"
|
||||
]
|
||||
"plugins": ["dynamic-import-node"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ services:
|
|||
- docker
|
||||
language: node_js
|
||||
node_js:
|
||||
- "9"
|
||||
- "10"
|
||||
os:
|
||||
- linux
|
||||
env:
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
// @flow
|
||||
if (typeof Promise === 'undefined') {
|
||||
// Rejection tracking prevents a common issue where React gets into an
|
||||
// inconsistent state due to an error, but it gets swallowed by a Promise,
|
||||
// and the user has no idea what causes React's erratic future behavior.
|
||||
require('promise/lib/rejection-tracking').enable();
|
||||
window.Promise = require('promise/lib/es6-extensions.js');
|
||||
require('promise/lib/rejection-tracking').enable()
|
||||
window.Promise = require('promise/lib/es6-extensions.js')
|
||||
}
|
||||
|
||||
// fetch() polyfill for making API calls.
|
||||
require('whatwg-fetch');
|
||||
require('whatwg-fetch')
|
||||
|
||||
// Object.assign() is commonly used with React.
|
||||
// It will use the native implementation if it's present and isn't buggy.
|
||||
Object.assign = require('object-assign');
|
||||
Object.assign = require('object-assign')
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
/*eslint-disable*/
|
||||
const autoprefixer = require('autoprefixer');
|
||||
const cssmixins = require('postcss-mixins');
|
||||
const cssvars = require('postcss-simple-vars');
|
||||
const webpack = require('webpack');
|
||||
const HtmlWebPackPlugin = require("html-webpack-plugin");
|
||||
const autoprefixer = require('autoprefixer')
|
||||
const cssmixins = require('postcss-mixins')
|
||||
const cssvars = require('postcss-simple-vars')
|
||||
const webpack = require('webpack')
|
||||
const HtmlWebPackPlugin = require('html-webpack-plugin')
|
||||
|
||||
const paths = require('./paths');
|
||||
const getClientEnvironment = require('./env');
|
||||
const paths = require('./paths')
|
||||
const getClientEnvironment = require('./env')
|
||||
|
||||
const publicPath = '/';
|
||||
const publicPath = '/'
|
||||
|
||||
// `publicUrl` we will provide it to our app
|
||||
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
|
||||
// Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz.
|
||||
var publicUrl = '';
|
||||
var publicUrl = ''
|
||||
// Get environment variables to inject into our app.
|
||||
var env = getClientEnvironment(publicUrl);
|
||||
var env = getClientEnvironment(publicUrl)
|
||||
|
||||
const cssvariables = require(paths.appSrc + '/theme/variables');
|
||||
const cssvariables = require(paths.appSrc + '/theme/variables')
|
||||
|
||||
const postcssPlugins = [
|
||||
autoprefixer({
|
||||
|
@ -26,22 +26,22 @@ const postcssPlugins = [
|
|||
'last 4 versions',
|
||||
'Firefox ESR',
|
||||
'not ie < 9', // React doesn't support IE8 anyway
|
||||
]
|
||||
],
|
||||
}),
|
||||
cssmixins,
|
||||
cssvars({
|
||||
variables: function () {
|
||||
return Object.assign({}, cssvariables);
|
||||
variables: function() {
|
||||
return Object.assign({}, cssvariables)
|
||||
},
|
||||
silent: false
|
||||
silent: false,
|
||||
}),
|
||||
];
|
||||
]
|
||||
|
||||
module.exports = {
|
||||
devtool: 'eval-source-map',
|
||||
mode: 'development',
|
||||
entry: [
|
||||
"babel-polyfill",
|
||||
'babel-polyfill',
|
||||
// Include an alternative client for WebpackDevServer. A client's job is to
|
||||
// connect to WebpackDevServer by a socket and get notified about changes.
|
||||
// When you save a file, the client will either apply hot updates (in case
|
||||
|
@ -56,17 +56,13 @@ module.exports = {
|
|||
// We ship a few polyfills by default:
|
||||
require.resolve('./polyfills'),
|
||||
// Finally, this is your app's code:
|
||||
paths.appIndexJs
|
||||
paths.appIndexJs,
|
||||
// We include the app code last so that if there is a runtime error during
|
||||
// initialization, it doesn't blow up the WebpackDevServer client, and
|
||||
// changing JS code would still trigger a refresh.
|
||||
],
|
||||
resolve: {
|
||||
modules: [
|
||||
paths.appSrc,
|
||||
'node_modules',
|
||||
paths.appContracts,
|
||||
],
|
||||
modules: [paths.appSrc, 'node_modules', paths.appContracts],
|
||||
// These are the reasonable defaults supported by the Node ecosystem.
|
||||
// We also include JSX as a common component filename extension to support
|
||||
// some tools, although we do not recommend using it, see:
|
||||
|
@ -75,7 +71,7 @@ module.exports = {
|
|||
alias: {
|
||||
'~': paths.appSrc,
|
||||
'#': paths.appContracts,
|
||||
}
|
||||
},
|
||||
},
|
||||
output: {
|
||||
// Next line is not used in dev but WebpackDevServer crashes without it:
|
||||
|
@ -87,7 +83,7 @@ module.exports = {
|
|||
// containing code from all our entry points, and the Webpack runtime.
|
||||
filename: 'static/js/bundle.js',
|
||||
// This is the URL that app is served from. We use "/" in development.
|
||||
publicPath: publicPath
|
||||
publicPath: publicPath,
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
|
@ -95,20 +91,20 @@ module.exports = {
|
|||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: "babel-loader"
|
||||
}
|
||||
loader: 'babel-loader',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(scss|css)$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
{ loader: 'css-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
modules: true,
|
||||
minimize: false,
|
||||
localIdentName: '[name]__[local]___[hash:base64:5]',
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
|
@ -123,22 +119,24 @@ module.exports = {
|
|||
test: /\.html$/,
|
||||
use: [
|
||||
{
|
||||
loader: "html-loader",
|
||||
options: { minimize: false }
|
||||
}
|
||||
]
|
||||
loader: 'html-loader',
|
||||
options: { minimize: false },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(jpe?g|png|svg)$/i,
|
||||
exclude: /node_modules/,
|
||||
use: [{
|
||||
loader: "file-loader",
|
||||
options: {
|
||||
name: 'img/[hash].[ext]'
|
||||
}
|
||||
}]
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: 'img/[hash].[ext]',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebPackPlugin({
|
||||
|
@ -152,6 +150,6 @@ module.exports = {
|
|||
node: {
|
||||
fs: 'empty',
|
||||
net: 'empty',
|
||||
tls: 'empty'
|
||||
}
|
||||
};
|
||||
tls: 'empty',
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*eslint-disable*/
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
|
||||
const autoprefixer = require('autoprefixer')
|
||||
const cssmixins = require('postcss-mixins');
|
||||
const cssmixins = require('postcss-mixins')
|
||||
const cssvars = require('postcss-simple-vars')
|
||||
const webpack = require('webpack')
|
||||
|
||||
|
@ -9,8 +9,10 @@ const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
|
|||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
const ManifestPlugin = require('webpack-manifest-plugin')
|
||||
const url = require('url')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
|
||||
|
||||
const url = require('url')
|
||||
const paths = require('./paths')
|
||||
const getClientEnvironment = require('./env')
|
||||
|
||||
|
@ -77,7 +79,7 @@ module.exports = {
|
|||
bail: true,
|
||||
optimization: {
|
||||
splitChunks: {
|
||||
chunks: "all",
|
||||
chunks: 'all',
|
||||
/* https://stackoverflow.com/questions/48985780/webpack-4-create-vendor-chunk
|
||||
cacheGroups: {
|
||||
vendor: {
|
||||
|
@ -90,11 +92,9 @@ module.exports = {
|
|||
},
|
||||
*/
|
||||
},
|
||||
minimizer: [new OptimizeCSSAssetsPlugin({})],
|
||||
},
|
||||
entry: [
|
||||
require.resolve('./polyfills'),
|
||||
paths.appIndexJs,
|
||||
],
|
||||
entry: [require.resolve('./polyfills'), paths.appIndexJs],
|
||||
output: {
|
||||
// The build folder.
|
||||
path: paths.appBuild,
|
||||
|
@ -107,11 +107,7 @@ module.exports = {
|
|||
publicPath,
|
||||
},
|
||||
resolve: {
|
||||
modules: [
|
||||
paths.appSrc,
|
||||
'node_modules',
|
||||
paths.appContracts,
|
||||
],
|
||||
modules: [paths.appSrc, 'node_modules', paths.appContracts],
|
||||
// These are the reasonable defaults supported by the Node ecosystem.
|
||||
// We also include JSX as a common component filename extension to support
|
||||
// some tools, although we do not recommend using it, see:
|
||||
|
@ -134,36 +130,35 @@ module.exports = {
|
|||
},
|
||||
{
|
||||
test: /\.(scss|css)$/,
|
||||
use: ExtractTextPlugin.extract({
|
||||
fallback: 'style-loader',
|
||||
use: [
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
modules: true,
|
||||
minimize: true,
|
||||
},
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
modules: true,
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
plugins: postcssPlugins,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
plugins: postcssPlugins,
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(jpe?g|png|svg)$/i,
|
||||
exclude: /node_modules/,
|
||||
use: [{
|
||||
loader: "file-loader",
|
||||
options: {
|
||||
name: 'img/[hash].[ext]'
|
||||
}
|
||||
}]
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: 'img/[hash].[ext]',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -190,10 +185,9 @@ module.exports = {
|
|||
// It is absolutely essential that NODE_ENV was set to production here.
|
||||
// Otherwise React will be compiled in the very slow development mode.
|
||||
new webpack.DefinePlugin(env),
|
||||
// Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`.
|
||||
new ExtractTextPlugin({
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'static/css/[name].[hash:8].css',
|
||||
allChunks: true
|
||||
allChunks: 'static/css/[id].[hash:8].css',
|
||||
}),
|
||||
// Generate a manifest file which contains a mapping of all asset filenames
|
||||
// to their corresponding output file so that tools can pick it up without
|
||||
|
|
File diff suppressed because it is too large
Load Diff
101
package.json
101
package.json
|
@ -31,93 +31,112 @@
|
|||
"devDependencies": {
|
||||
"@babel/cli": "^7.0.0-beta.40",
|
||||
"@babel/core": "^7.0.0-beta.40",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0-beta.40",
|
||||
"@babel/plugin-proposal-class-properties": "^7.0.0",
|
||||
"@babel/plugin-proposal-decorators": "^7.0.0",
|
||||
"@babel/plugin-proposal-do-expressions": "^7.0.0",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.0.0",
|
||||
"@babel/plugin-proposal-export-namespace-from": "^7.0.0",
|
||||
"@babel/plugin-proposal-function-bind": "^7.0.0",
|
||||
"@babel/plugin-proposal-function-sent": "^7.0.0",
|
||||
"@babel/plugin-proposal-json-strings": "^7.0.0",
|
||||
"@babel/plugin-proposal-logical-assignment-operators": "^7.0.0",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
|
||||
"@babel/plugin-proposal-numeric-separator": "^7.0.0",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
|
||||
"@babel/plugin-proposal-pipeline-operator": "^7.0.0",
|
||||
"@babel/plugin-proposal-throw-expressions": "^7.0.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||
"@babel/plugin-syntax-import-meta": "^7.0.0",
|
||||
"@babel/plugin-transform-member-expression-literals": "^7.2.0",
|
||||
"@babel/plugin-transform-property-literals": "^7.2.0",
|
||||
"@babel/polyfill": "^7.0.0-beta.40",
|
||||
"@babel/preset-env": "^7.0.0-beta.40",
|
||||
"@babel/preset-flow": "^7.0.0-beta.40",
|
||||
"@babel/preset-react": "^7.0.0-beta.40",
|
||||
"@babel/preset-stage-0": "^7.0.0-beta.40",
|
||||
"@sambego/storybook-state": "^1.0.7",
|
||||
"@storybook/addon-actions": "^3.3.15",
|
||||
"@storybook/addon-knobs": "^3.3.15",
|
||||
"@storybook/addon-links": "^3.3.15",
|
||||
"@storybook/react": "^3.3.15",
|
||||
"autoprefixer": "^8.1.0",
|
||||
"@storybook/addon-actions": "^5.0.0",
|
||||
"@storybook/addon-knobs": "^5.0.0",
|
||||
"@storybook/addon-links": "^5.0.0",
|
||||
"@storybook/react": "^5.0.0",
|
||||
"autoprefixer": "^9.4.10",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-eslint": "8",
|
||||
"babel-jest": "^22.4.1",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-jest": "^24.1.0",
|
||||
"babel-loader": "^8.0.0-beta.0",
|
||||
"babel-plugin-dynamic-import-node": "^1.2.0",
|
||||
"babel-plugin-dynamic-import-node": "^2.2.0",
|
||||
"babel-plugin-transform-es3-member-expression-literals": "^6.22.0",
|
||||
"babel-plugin-transform-es3-property-literals": "^6.22.0",
|
||||
"bignumber.js": "^7.2.1",
|
||||
"bignumber.js": "^8.1.1",
|
||||
"classnames": "^2.2.5",
|
||||
"css-loader": "^0.28.10",
|
||||
"css-loader": "^2.1.0",
|
||||
"detect-port": "^1.2.2",
|
||||
"dotenv": "^5.0.1",
|
||||
"eslint": "^4.18.2",
|
||||
"eslint-config-airbnb": "^16.1.0",
|
||||
"eslint-plugin-flowtype": "^2.46.1",
|
||||
"eslint": "^5.15.1",
|
||||
"eslint-config-airbnb": "^17.1.0",
|
||||
"eslint-plugin-flowtype": "^3.4.2",
|
||||
"eslint-plugin-import": "^2.9.0",
|
||||
"eslint-plugin-jest": "^21.13.0",
|
||||
"eslint-plugin-jest": "^22.3.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.0.3",
|
||||
"eslint-plugin-react": "^7.7.0",
|
||||
"ethereumjs-abi": "^0.6.5",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"file-loader": "^1.1.11",
|
||||
"flow-bin": "^0.79.1",
|
||||
"fs-extra": "^5.0.0",
|
||||
"file-loader": "^3.0.1",
|
||||
"flow-bin": "^0.95.1",
|
||||
"fs-extra": "^7.0.1",
|
||||
"html-loader": "^0.5.5",
|
||||
"html-webpack-plugin": "^3.0.4",
|
||||
"immutable": "^4.0.0-rc.9",
|
||||
"jest": "^22.4.2",
|
||||
"jest": "^24.1.0",
|
||||
"json-loader": "^0.5.7",
|
||||
"material-ui-search-bar": "^1.0.0-beta.13",
|
||||
"postcss-loader": "^2.1.1",
|
||||
"mini-css-extract-plugin": "^0.5.0",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"postcss-mixins": "^6.2.0",
|
||||
"postcss-simple-vars": "^4.1.0",
|
||||
"postcss-simple-vars": "^5.0.2",
|
||||
"pre-commit": "^1.2.2",
|
||||
"react": "^16.4.0",
|
||||
"react-dev-utils": "^5.0.1",
|
||||
"react-dev-utils": "^8.0.0",
|
||||
"react-dom": "^16.4.0",
|
||||
"react-redux": "^5.0.7",
|
||||
"react-router-redux": "^5.0.0-alpha.9",
|
||||
"redux": "^3.7.2",
|
||||
"react-redux": "^6.0.1",
|
||||
"redux": "^4.0.1",
|
||||
"redux-actions": "^2.3.0",
|
||||
"redux-thunk": "^2.2.0",
|
||||
"reselect": "^3.0.1",
|
||||
"reselect": "^4.0.0",
|
||||
"run-with-testrpc": "^0.3.0",
|
||||
"storybook-host": "^4.1.5",
|
||||
"storybook-host": "^5.0.3",
|
||||
"storybook-router": "^0.3.3",
|
||||
"style-loader": "^0.20.2",
|
||||
"truffle": "^4.1.11",
|
||||
"truffle-contract": "^1.1.8",
|
||||
"truffle-solidity-loader": "0.0.8",
|
||||
"uglifyjs-webpack-plugin": "^1.2.2",
|
||||
"web3": "0.18.4",
|
||||
"style-loader": "^0.23.1",
|
||||
"truffle": "^5.0.6",
|
||||
"truffle-contract": "^4.0.7",
|
||||
"truffle-solidity-loader": "^0.1.6",
|
||||
"uglifyjs-webpack-plugin": "^2.1.2",
|
||||
"webpack": "^4.1.1",
|
||||
"webpack-bundle-analyzer": "^2.11.1",
|
||||
"webpack-cli": "^2.0.8",
|
||||
"webpack-bundle-analyzer": "^3.1.0",
|
||||
"webpack-cli": "^3.2.3",
|
||||
"webpack-dev-server": "^3.1.0",
|
||||
"webpack-manifest-plugin": "^2.0.0-rc.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gnosis.pm/util-contracts": "^0.2.14",
|
||||
"@gnosis.pm/util-contracts": "^2.0.0",
|
||||
"@material-ui/core": "^3.0.1",
|
||||
"@material-ui/icons": "^3.0.1",
|
||||
"connected-react-router": "^6.3.1",
|
||||
"final-form": "^4.2.1",
|
||||
"history": "^4.7.2",
|
||||
"react-final-form": "^3.1.2",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
||||
"react-final-form": "^4.1.0",
|
||||
"react-loadable": "^5.3.1",
|
||||
"react-router-dom": "^4.2.2",
|
||||
"recompose": "^0.27.1"
|
||||
"recompose": "^0.30.0",
|
||||
"web3": "1.0.0-beta.37"
|
||||
},
|
||||
"jest": {
|
||||
"verbose": true,
|
||||
"collectCoverageFrom": [
|
||||
"src/**/*.{js,jsx}"
|
||||
],
|
||||
"setupTestFrameworkScriptFile": "<rootDir>/config/jest/jest.setup.js",
|
||||
"setupFilesAfterEnv": [
|
||||
"<rootDir>/config/jest/jest.setup.js"
|
||||
],
|
||||
"setupFiles": [
|
||||
"<rootDir>/config/webpack.config.test.js",
|
||||
"<rootDir>/config/polyfills.js",
|
||||
|
|
260
scripts/start.js
260
scripts/start.js
|
@ -1,180 +1,174 @@
|
|||
/*eslint-disable*/
|
||||
process.env.NODE_ENV = 'development';
|
||||
process.env.NODE_ENV = 'development'
|
||||
|
||||
// Load environment variables from .env file. Suppress warnings using silent
|
||||
// if this file is missing. dotenv will never modify any environment variables
|
||||
// that have already been set.
|
||||
// https://github.com/motdotla/dotenv
|
||||
require('dotenv').config({silent: true});
|
||||
require('dotenv').config({ silent: true })
|
||||
|
||||
var chalk = require('chalk');
|
||||
var webpack = require('webpack');
|
||||
var WebpackDevServer = require('webpack-dev-server');
|
||||
var historyApiFallback = require('connect-history-api-fallback');
|
||||
var httpProxyMiddleware = require('http-proxy-middleware');
|
||||
var detect = require('detect-port');
|
||||
var clearConsole = require('react-dev-utils/clearConsole');
|
||||
var checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
|
||||
var formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
|
||||
var getProcessForPort = require('react-dev-utils/getProcessForPort');
|
||||
var openBrowser = require('react-dev-utils/openBrowser');
|
||||
var pathExists = require('path-exists');
|
||||
var config = require('../config/webpack.config.dev');
|
||||
var paths = require('../config/paths');
|
||||
var chalk = require('chalk')
|
||||
var webpack = require('webpack')
|
||||
var WebpackDevServer = require('webpack-dev-server')
|
||||
var historyApiFallback = require('connect-history-api-fallback')
|
||||
var httpProxyMiddleware = require('http-proxy-middleware')
|
||||
var detect = require('detect-port')
|
||||
var clearConsole = require('react-dev-utils/clearConsole')
|
||||
var checkRequiredFiles = require('react-dev-utils/checkRequiredFiles')
|
||||
var formatWebpackMessages = require('react-dev-utils/formatWebpackMessages')
|
||||
var getProcessForPort = require('react-dev-utils/getProcessForPort')
|
||||
var openBrowser = require('react-dev-utils/openBrowser')
|
||||
var pathExists = require('path-exists')
|
||||
var config = require('../config/webpack.config.dev')
|
||||
var paths = require('../config/paths')
|
||||
|
||||
var useYarn = pathExists.sync(paths.yarnLockFile);
|
||||
var cli = useYarn ? 'yarn' : 'npm';
|
||||
var isInteractive = process.stdout.isTTY;
|
||||
var useYarn = pathExists.sync(paths.yarnLockFile)
|
||||
var cli = useYarn ? 'yarn' : 'npm'
|
||||
var isInteractive = process.stdout.isTTY
|
||||
|
||||
// Warn and crash if required files are missing
|
||||
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
|
||||
process.exit(1);
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Tools like Cloud9 rely on this.
|
||||
var DEFAULT_PORT = process.env.PORT || 3000;
|
||||
var compiler;
|
||||
var handleCompile;
|
||||
|
||||
// You can safely remove this after ejecting.
|
||||
// We only use this block for testing of Create React App itself:
|
||||
var isSmokeTest = process.argv.some(arg => arg.indexOf('--smoke-test') > -1);
|
||||
if (isSmokeTest) {
|
||||
handleCompile = function (err, stats) {
|
||||
if (err || stats.hasErrors() || stats.hasWarnings()) {
|
||||
process.exit(1);
|
||||
} else {
|
||||
process.exit(0);
|
||||
}
|
||||
};
|
||||
}
|
||||
var DEFAULT_PORT = process.env.PORT || 3000
|
||||
var compiler
|
||||
var handleCompile
|
||||
|
||||
function setupCompiler(host, port, protocol) {
|
||||
// "Compiler" is a low-level interface to Webpack.
|
||||
// It lets us listen to some events and provide our own custom messages.
|
||||
compiler = webpack(config, handleCompile);
|
||||
compiler = webpack(config, handleCompile)
|
||||
|
||||
// "invalid" event fires when you have changed a file, and Webpack is
|
||||
// recompiling a bundle. WebpackDevServer takes care to pause serving the
|
||||
// bundle, so if you refresh, it'll wait instead of serving the old one.
|
||||
// "invalid" is short for "bundle invalidated", it doesn't imply any errors.
|
||||
compiler.plugin('invalid', function() {
|
||||
compiler.hooks.invalid.tap('invalid', () => {
|
||||
if (isInteractive) {
|
||||
clearConsole();
|
||||
clearConsole()
|
||||
}
|
||||
console.log('Compiling...');
|
||||
});
|
||||
console.log('Compiling...')
|
||||
})
|
||||
|
||||
var isFirstCompile = true;
|
||||
var isFirstCompile = true
|
||||
|
||||
// "done" event fires when Webpack has finished recompiling the bundle.
|
||||
// Whether or not you have warnings or errors, you will get this event.
|
||||
compiler.plugin('done', function(stats) {
|
||||
compiler.hooks.done.tap('done', function(stats) {
|
||||
if (isInteractive) {
|
||||
clearConsole();
|
||||
clearConsole()
|
||||
}
|
||||
|
||||
// We have switched off the default Webpack output in WebpackDevServer
|
||||
// options so we are going to "massage" the warnings and errors and present
|
||||
// them in a readable focused way.
|
||||
var messages = formatWebpackMessages(stats.toJson({}, true));
|
||||
var isSuccessful = !messages.errors.length && !messages.warnings.length;
|
||||
var showInstructions = isSuccessful && (isInteractive || isFirstCompile);
|
||||
var messages = formatWebpackMessages(stats.toJson({}, true))
|
||||
var isSuccessful = !messages.errors.length && !messages.warnings.length
|
||||
var showInstructions = isSuccessful && (isInteractive || isFirstCompile)
|
||||
|
||||
if (isSuccessful) {
|
||||
console.log(chalk.green('Compiled successfully!'));
|
||||
console.log(chalk.green('Compiled successfully!'))
|
||||
}
|
||||
|
||||
if (showInstructions) {
|
||||
console.log();
|
||||
console.log('The app is running at:');
|
||||
console.log();
|
||||
console.log(' ' + chalk.cyan(protocol + '://' + host + ':' + port + '/'));
|
||||
console.log();
|
||||
console.log('Note that the development build is not optimized.');
|
||||
console.log('To create a production build, use ' + chalk.cyan(cli + ' run build') + '.');
|
||||
console.log();
|
||||
isFirstCompile = false;
|
||||
console.log()
|
||||
console.log('The app is running at:')
|
||||
console.log()
|
||||
console.log(' ' + chalk.cyan(protocol + '://' + host + ':' + port + '/'))
|
||||
console.log()
|
||||
console.log('Note that the development build is not optimized.')
|
||||
console.log('To create a production build, use ' + chalk.cyan(cli + ' run build') + '.')
|
||||
console.log()
|
||||
isFirstCompile = false
|
||||
}
|
||||
|
||||
// If errors exist, only show errors.
|
||||
if (messages.errors.length) {
|
||||
console.log(chalk.red('Failed to compile.'));
|
||||
console.log();
|
||||
console.log(chalk.red('Failed to compile.'))
|
||||
console.log()
|
||||
messages.errors.forEach(message => {
|
||||
console.log(message);
|
||||
console.log();
|
||||
});
|
||||
return;
|
||||
console.log(message)
|
||||
console.log()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Show warnings if no errors were found.
|
||||
if (messages.warnings.length) {
|
||||
console.log(chalk.yellow('Compiled with warnings.'));
|
||||
console.log();
|
||||
console.log(chalk.yellow('Compiled with warnings.'))
|
||||
console.log()
|
||||
messages.warnings.forEach(message => {
|
||||
console.log(message);
|
||||
console.log();
|
||||
});
|
||||
console.log(message)
|
||||
console.log()
|
||||
})
|
||||
// Teach some ESLint tricks.
|
||||
console.log('You may use special comments to disable some warnings.');
|
||||
console.log('Use ' + chalk.yellow('// eslint-disable-next-line') + ' to ignore the next line.');
|
||||
console.log('Use ' + chalk.yellow('/* eslint-disable */') + ' to ignore all warnings in a file.');
|
||||
console.log('You may use special comments to disable some warnings.')
|
||||
console.log('Use ' + chalk.yellow('// eslint-disable-next-line') + ' to ignore the next line.')
|
||||
console.log('Use ' + chalk.yellow('/* eslint-disable */') + ' to ignore all warnings in a file.')
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
// We need to provide a custom onError function for httpProxyMiddleware.
|
||||
// It allows us to log custom error messages on the console.
|
||||
function onProxyError(proxy) {
|
||||
return function(err, req, res){
|
||||
var host = req.headers && req.headers.host;
|
||||
return function(err, req, res) {
|
||||
var host = req.headers && req.headers.host
|
||||
console.log(
|
||||
chalk.red('Proxy error:') + ' Could not proxy request ' + chalk.cyan(req.url) +
|
||||
' from ' + chalk.cyan(host) + ' to ' + chalk.cyan(proxy) + '.'
|
||||
);
|
||||
chalk.red('Proxy error:') +
|
||||
' Could not proxy request ' +
|
||||
chalk.cyan(req.url) +
|
||||
' from ' +
|
||||
chalk.cyan(host) +
|
||||
' to ' +
|
||||
chalk.cyan(proxy) +
|
||||
'.',
|
||||
)
|
||||
console.log(
|
||||
'See https://nodejs.org/api/errors.html#errors_common_system_errors for more information (' +
|
||||
chalk.cyan(err.code) + ').'
|
||||
);
|
||||
console.log();
|
||||
chalk.cyan(err.code) +
|
||||
').',
|
||||
)
|
||||
console.log()
|
||||
|
||||
// And immediately send the proper error response to the client.
|
||||
// Otherwise, the request will eventually timeout with ERR_EMPTY_RESPONSE on the client side.
|
||||
if (res.writeHead && !res.headersSent) {
|
||||
res.writeHead(500);
|
||||
res.writeHead(500)
|
||||
}
|
||||
res.end('Proxy error: Could not proxy request ' + req.url + ' from ' +
|
||||
host + ' to ' + proxy + ' (' + err.code + ').'
|
||||
);
|
||||
res.end(
|
||||
'Proxy error: Could not proxy request ' + req.url + ' from ' + host + ' to ' + proxy + ' (' + err.code + ').',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function addMiddleware(devServer) {
|
||||
// `proxy` lets you to specify a fallback server during development.
|
||||
// Every unrecognized request will be forwarded to it.
|
||||
var proxy = require(paths.appPackageJson).proxy;
|
||||
devServer.use(historyApiFallback({
|
||||
// Paths with dots should still use the history fallback.
|
||||
// See https://github.com/facebookincubator/create-react-app/issues/387.
|
||||
disableDotRule: true,
|
||||
// For single page apps, we generally want to fallback to /index.html.
|
||||
// However we also want to respect `proxy` for API calls.
|
||||
// So if `proxy` is specified, we need to decide which fallback to use.
|
||||
// We use a heuristic: if request `accept`s text/html, we pick /index.html.
|
||||
// Modern browsers include text/html into `accept` header when navigating.
|
||||
// However API calls like `fetch()` won’t generally accept text/html.
|
||||
// If this heuristic doesn’t work well for you, don’t use `proxy`.
|
||||
htmlAcceptHeaders: proxy ?
|
||||
['text/html'] :
|
||||
['text/html', '*/*']
|
||||
}));
|
||||
var proxy = require(paths.appPackageJson).proxy
|
||||
devServer.use(
|
||||
historyApiFallback({
|
||||
// Paths with dots should still use the history fallback.
|
||||
// See https://github.com/facebookincubator/create-react-app/issues/387.
|
||||
disableDotRule: true,
|
||||
// For single page apps, we generally want to fallback to /index.html.
|
||||
// However we also want to respect `proxy` for API calls.
|
||||
// So if `proxy` is specified, we need to decide which fallback to use.
|
||||
// We use a heuristic: if request `accept`s text/html, we pick /index.html.
|
||||
// Modern browsers include text/html into `accept` header when navigating.
|
||||
// However API calls like `fetch()` won’t generally accept text/html.
|
||||
// If this heuristic doesn’t work well for you, don’t use `proxy`.
|
||||
htmlAcceptHeaders: proxy ? ['text/html'] : ['text/html', '*/*'],
|
||||
}),
|
||||
)
|
||||
if (proxy) {
|
||||
if (typeof proxy !== 'string') {
|
||||
console.log(chalk.red('When specified, "proxy" in package.json must be a string.'));
|
||||
console.log(chalk.red('Instead, the type of "proxy" was "' + typeof proxy + '".'));
|
||||
console.log(chalk.red('Either remove "proxy" from package.json, or make it a string.'));
|
||||
process.exit(1);
|
||||
console.log(chalk.red('When specified, "proxy" in package.json must be a string.'))
|
||||
console.log(chalk.red('Instead, the type of "proxy" was "' + typeof proxy + '".'))
|
||||
console.log(chalk.red('Either remove "proxy" from package.json, or make it a string.'))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Otherwise, if proxy is specified, we will let it handle any request.
|
||||
|
@ -183,7 +177,7 @@ function addMiddleware(devServer) {
|
|||
// - /*.hot-update.json (WebpackDevServer uses this too for hot reloading)
|
||||
// - /sockjs-node/* (WebpackDevServer uses this for hot reloading)
|
||||
// Tip: use https://jex.im/regulex/ to visualize the regex
|
||||
var mayProxy = /^(?!\/(index\.html$|.*\.hot-update\.json$|sockjs-node\/)).*$/;
|
||||
var mayProxy = /^(?!\/(index\.html$|.*\.hot-update\.json$|sockjs-node\/)).*$/
|
||||
|
||||
// Pass the scope regex both to Express and to the middleware for proxying
|
||||
// of both HTTP and WebSockets to work without false positives.
|
||||
|
@ -195,25 +189,25 @@ function addMiddleware(devServer) {
|
|||
// requests. To prevent CORS issues, we have to change
|
||||
// the Origin to match the target URL.
|
||||
if (proxyReq.getHeader('origin')) {
|
||||
proxyReq.setHeader('origin', proxy);
|
||||
proxyReq.setHeader('origin', proxy)
|
||||
}
|
||||
},
|
||||
onError: onProxyError(proxy),
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
ws: true
|
||||
});
|
||||
devServer.use(mayProxy, hpm);
|
||||
ws: true,
|
||||
})
|
||||
devServer.use(mayProxy, hpm)
|
||||
|
||||
// Listen for the websocket 'upgrade' event and upgrade the connection.
|
||||
// If this is not done, httpProxyMiddleware will not try to upgrade until
|
||||
// an initial plain HTTP request is made.
|
||||
devServer.listeningApp.on('upgrade', hpm.upgrade);
|
||||
devServer.listeningApp.on('upgrade', hpm.upgrade)
|
||||
}
|
||||
|
||||
// Finally, by now we have certainly resolved the URL.
|
||||
// It may be /index.html, so let the dev server try serving it again.
|
||||
devServer.use(devServer.middleware);
|
||||
devServer.use(devServer.middleware)
|
||||
}
|
||||
|
||||
function runDevServer(host, port, protocol) {
|
||||
|
@ -253,54 +247,54 @@ function runDevServer(host, port, protocol) {
|
|||
// Reportedly, this avoids CPU overload on some systems.
|
||||
// https://github.com/facebookincubator/create-react-app/issues/293
|
||||
watchOptions: {
|
||||
ignored: /node_modules/
|
||||
ignored: /node_modules/,
|
||||
},
|
||||
// Enable HTTPS if the HTTPS environment variable is set to 'true'
|
||||
https: protocol === "https",
|
||||
host: host
|
||||
});
|
||||
https: protocol === 'https',
|
||||
host: host,
|
||||
})
|
||||
|
||||
// Our custom middleware proxies requests to /index.html or a remote API.
|
||||
addMiddleware(devServer);
|
||||
addMiddleware(devServer)
|
||||
|
||||
// Launch WebpackDevServer.
|
||||
devServer.listen(port, (err, result) => {
|
||||
if (err) {
|
||||
return console.log(err);
|
||||
return console.log(err)
|
||||
}
|
||||
|
||||
if (isInteractive) {
|
||||
clearConsole();
|
||||
clearConsole()
|
||||
}
|
||||
console.log(chalk.cyan('Starting the development server...'));
|
||||
console.log();
|
||||
console.log(chalk.cyan('Starting the development server...'))
|
||||
console.log()
|
||||
|
||||
if (isInteractive) {
|
||||
openBrowser(protocol + '://' + host + ':' + port + '/');
|
||||
openBrowser(protocol + '://' + host + ':' + port + '/')
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function run(port) {
|
||||
var protocol = process.env.HTTPS === 'true' ? "https" : "http";
|
||||
var host = process.env.HOST || 'localhost';
|
||||
setupCompiler(host, port, protocol);
|
||||
runDevServer(host, port, protocol);
|
||||
var protocol = process.env.HTTPS === 'true' ? 'https' : 'http'
|
||||
var host = process.env.HOST || 'localhost'
|
||||
setupCompiler(host, port, protocol)
|
||||
runDevServer(host, port, protocol)
|
||||
}
|
||||
|
||||
// We attempt to use the default port but if it is busy, we offer the user to
|
||||
// run on a different port. `detect()` Promise resolves to the next free port.
|
||||
detect(DEFAULT_PORT).then(port => {
|
||||
if (port === DEFAULT_PORT) {
|
||||
run(port);
|
||||
return;
|
||||
run(port)
|
||||
return
|
||||
}
|
||||
|
||||
if (isInteractive) {
|
||||
clearConsole();
|
||||
var existingProcess = getProcessForPort(DEFAULT_PORT);
|
||||
run(port);
|
||||
clearConsole()
|
||||
var existingProcess = getProcessForPort(DEFAULT_PORT)
|
||||
run(port)
|
||||
} else {
|
||||
console.log(chalk.red('Something is already running on port ' + DEFAULT_PORT + '.'));
|
||||
console.log(chalk.red('Something is already running on port ' + DEFAULT_PORT + '.'))
|
||||
}
|
||||
});
|
||||
})
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import Grow from '@material-ui/core/Grow'
|
||||
import ClickAwayListener from '@material-ui/core/ClickAwayListener'
|
||||
|
@ -49,7 +50,9 @@ const Layout = openHoc(({
|
|||
<React.Fragment>
|
||||
<Row className={classes.summary}>
|
||||
<Col start="xs" middle="xs" className={classes.logo}>
|
||||
<Img src={logo} height={32} alt="Gnosis Team Safe" />
|
||||
<Link to="/">
|
||||
<Img src={logo} height={32} alt="Gnosis Team Safe" />
|
||||
</Link>
|
||||
</Col>
|
||||
<Divider />
|
||||
<Spacer />
|
||||
|
@ -58,9 +61,7 @@ const Layout = openHoc(({
|
|||
{providerRef => (
|
||||
<Popper open={open} anchorEl={providerRef.current} placement="bottom-end">
|
||||
{({ TransitionProps }) => (
|
||||
<Grow
|
||||
{...TransitionProps}
|
||||
>
|
||||
<Grow {...TransitionProps}>
|
||||
<ClickAwayListener onClickAway={clickAway} mouseEvent="onClick" touchEvent={false}>
|
||||
<List className={classes.root} component="div">
|
||||
{providerDetails}
|
||||
|
|
|
@ -39,21 +39,19 @@ const ConnectDetails = ({ classes, onConnect }: Props) => (
|
|||
<React.Fragment>
|
||||
<div className={classes.container}>
|
||||
<Row margin="lg" align="center">
|
||||
<Paragraph className={classes.text} size="lg" noMargin weight="bolder">Connect a Wallet</Paragraph>
|
||||
<Paragraph className={classes.text} size="lg" noMargin weight="bolder">
|
||||
Connect a Wallet
|
||||
</Paragraph>
|
||||
</Row>
|
||||
</div>
|
||||
<Row className={classes.logo} margin="lg">
|
||||
<CircleDot keySize={32} circleSize={75} dotSize={25} dotTop={50} dotRight={25} center mode="error" />
|
||||
</Row>
|
||||
<Row className={classes.connect}>
|
||||
<Button
|
||||
onClick={onConnect}
|
||||
size="medium"
|
||||
variant="raised"
|
||||
color="primary"
|
||||
fullWidth
|
||||
>
|
||||
<Paragraph className={classes.connectText} size="sm" weight="regular" color="white" noMargin>CONNECT</Paragraph>
|
||||
<Button onClick={onConnect} size="medium" variant="contained" color="primary" fullWidth>
|
||||
<Paragraph className={classes.connectText} size="sm" weight="regular" color="white" noMargin>
|
||||
CONNECT
|
||||
</Paragraph>
|
||||
</Button>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
|
|
|
@ -12,7 +12,9 @@ import Img from '~/components/layout/Img'
|
|||
import Row from '~/components/layout/Row'
|
||||
import Block from '~/components/layout/Block'
|
||||
import Spacer from '~/components/Spacer'
|
||||
import { xs, sm, md, lg, background, secondary, warning, connected as connectedBg } from '~/theme/variables'
|
||||
import {
|
||||
xs, sm, md, lg, background, secondary, warning, connected as connectedBg,
|
||||
} from '~/theme/variables'
|
||||
import { upperFirst } from '~/utils/css'
|
||||
import { shortVersionOf } from '~/logic/wallets/ethAddresses'
|
||||
import { openAddressInEtherScan } from '~/logic/wallets/getWeb3'
|
||||
|
@ -108,25 +110,31 @@ const UserDetails = ({
|
|||
<React.Fragment>
|
||||
<Block className={classes.container}>
|
||||
<Row className={classes.identicon} margin="md" align="center">
|
||||
{ connected
|
||||
? <Identicon address={identiconAddress} diameter={60} />
|
||||
: <CircleDot keySize={30} circleSize={75} dotSize={25} dotTop={50} dotRight={25} mode="warning" hideDot />
|
||||
}
|
||||
{connected ? (
|
||||
<Identicon address={identiconAddress} diameter={60} />
|
||||
) : (
|
||||
<CircleDot keySize={30} circleSize={75} dotSize={25} dotTop={50} dotRight={25} mode="warning" hideDot />
|
||||
)}
|
||||
</Row>
|
||||
<Block align="center" className={classes.user}>
|
||||
<Paragraph className={classes.address} size="sm" noMargin>{address}</Paragraph>
|
||||
{ userAddress &&
|
||||
<Paragraph className={classes.address} size="sm" noMargin>
|
||||
{address}
|
||||
</Paragraph>
|
||||
{userAddress && (
|
||||
<OpenInNew
|
||||
className={classes.open}
|
||||
style={openIconStyle}
|
||||
onClick={openAddressInEtherScan(userAddress, network)}
|
||||
/>
|
||||
}
|
||||
)}
|
||||
</Block>
|
||||
</Block>
|
||||
<Hairline margin="xs" />
|
||||
<Row className={classes.details}>
|
||||
<Paragraph noMargin align="right" className={classes.labels}>Status </Paragraph>
|
||||
<Paragraph noMargin align="right" className={classes.labels}>
|
||||
Status
|
||||
{' '}
|
||||
</Paragraph>
|
||||
<Spacer />
|
||||
<Dot className={classNames(classes.dot, connected ? classes.connected : classes.warning)} />
|
||||
<Paragraph noMargin align="right" color={color} weight="bolder" className={classes.labels}>
|
||||
|
@ -135,7 +143,10 @@ const UserDetails = ({
|
|||
</Row>
|
||||
<Hairline margin="xs" />
|
||||
<Row className={classes.details}>
|
||||
<Paragraph noMargin align="right" className={classes.labels}>Client </Paragraph>
|
||||
<Paragraph noMargin align="right" className={classes.labels}>
|
||||
Client
|
||||
{' '}
|
||||
</Paragraph>
|
||||
<Spacer />
|
||||
<Img className={classes.logo} src={metamask} height={14} alt="Metamask client" />
|
||||
<Paragraph noMargin align="right" weight="bolder" className={classes.labels}>
|
||||
|
@ -144,7 +155,10 @@ const UserDetails = ({
|
|||
</Row>
|
||||
<Hairline margin="xs" />
|
||||
<Row className={classes.details}>
|
||||
<Paragraph noMargin align="right" className={classes.labels}>Network </Paragraph>
|
||||
<Paragraph noMargin align="right" className={classes.labels}>
|
||||
Network
|
||||
{' '}
|
||||
</Paragraph>
|
||||
<Spacer />
|
||||
<Img className={classes.logo} src={dot} height={14} alt="Network" />
|
||||
<Paragraph noMargin align="right" weight="bolder" className={classes.labels}>
|
||||
|
@ -153,14 +167,10 @@ const UserDetails = ({
|
|||
</Row>
|
||||
<Hairline margin="xs" />
|
||||
<Row className={classes.disconnect}>
|
||||
<Button
|
||||
onClick={onDisconnect}
|
||||
size="medium"
|
||||
variant="raised"
|
||||
color="primary"
|
||||
fullWidth
|
||||
>
|
||||
<Paragraph className={classes.disconnectText} size="sm" weight="bold" color="white" noMargin>DISCONNECT</Paragraph>
|
||||
<Button onClick={onDisconnect} size="medium" variant="contained" color="primary" fullWidth>
|
||||
<Paragraph className={classes.disconnectText} size="sm" weight="bold" color="white" noMargin>
|
||||
DISCONNECT
|
||||
</Paragraph>
|
||||
</Button>
|
||||
</Row>
|
||||
</React.Fragment>
|
||||
|
|
|
@ -48,7 +48,7 @@ const Controls = ({
|
|||
<Button
|
||||
style={secondButtonStyle}
|
||||
size="small"
|
||||
variant="raised"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
disabled={disabled}
|
||||
|
|
|
@ -43,13 +43,12 @@ const transitionProps = {
|
|||
class GnoStepper extends React.PureComponent<Props, State> {
|
||||
static Page = ({ children }: PageProps) => children
|
||||
|
||||
static FinishButton = ({
|
||||
component, to, title, ...props
|
||||
}) => (
|
||||
<Button component={component} to={to} variant="raised" color="primary" {...props}>
|
||||
static FinishButton = ({ component, to, title, ...props }) => (
|
||||
<Button component={component} to={to} variant="contained" color="primary" {...props}>
|
||||
{title}
|
||||
</Button>
|
||||
)
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
|
||||
|
@ -79,14 +78,12 @@ class GnoStepper extends React.PureComponent<Props, State> {
|
|||
return children({ ...props, updateInitialProps: this.updateInitialProps })
|
||||
}
|
||||
|
||||
updateInitialProps = (initialValues) => {
|
||||
updateInitialProps = initialValues => {
|
||||
this.setState({ values: initialValues })
|
||||
}
|
||||
|
||||
validate = (values: Object) => {
|
||||
const activePage = React.Children.toArray(this.props.children)[
|
||||
this.state.page
|
||||
]
|
||||
const activePage = React.Children.toArray(this.props.children)[this.state.page]
|
||||
return activePage.props.validate ? activePage.props.validate(values) : {}
|
||||
}
|
||||
|
||||
|
@ -132,9 +129,7 @@ class GnoStepper extends React.PureComponent<Props, State> {
|
|||
isLastPage = page => page === this.props.steps.length - 1
|
||||
|
||||
render() {
|
||||
const {
|
||||
steps, children, classes, disabledWhenValidating = false,
|
||||
} = this.props
|
||||
const { steps, children, classes, disabledWhenValidating = false } = this.props
|
||||
const { page, values } = this.state
|
||||
const activePage = this.getActivePageFrom(children)
|
||||
const lastPage = this.isLastPage(page)
|
||||
|
@ -142,11 +137,7 @@ class GnoStepper extends React.PureComponent<Props, State> {
|
|||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<GnoForm
|
||||
onSubmit={this.handleSubmit}
|
||||
initialValues={values}
|
||||
validation={this.validate}
|
||||
>
|
||||
<GnoForm onSubmit={this.handleSubmit} initialValues={values} validation={this.validate}>
|
||||
{(submitting: boolean, validating: boolean, ...rest: any) => {
|
||||
const disabled = disabledWhenValidating ? submitting || validating : submitting
|
||||
const controls = (
|
||||
|
@ -167,9 +158,7 @@ class GnoStepper extends React.PureComponent<Props, State> {
|
|||
{steps.map(label => (
|
||||
<FormStep key={label}>
|
||||
<StepLabel>{label}</StepLabel>
|
||||
<StepContent TransitionProps={transitionProps}>
|
||||
{activePage(controls, ...rest)}
|
||||
</StepContent>
|
||||
<StepContent TransitionProps={transitionProps}>{activePage(controls, ...rest)}</StepContent>
|
||||
</FormStep>
|
||||
))}
|
||||
</Stepper>
|
||||
|
|
|
@ -9,7 +9,7 @@ import { type Order } from '~/components/Table/sorting'
|
|||
|
||||
export type Column = {
|
||||
id: string,
|
||||
numeric: boolean,
|
||||
align?: string,
|
||||
order: boolean, // If data for sorting will be provided in a different attr
|
||||
disablePadding: boolean,
|
||||
label: string,
|
||||
|
@ -48,7 +48,7 @@ class GnoTableHead extends React.PureComponent<Props> {
|
|||
{columns.map((column: Column) => (
|
||||
<TableCell
|
||||
key={column.id}
|
||||
numeric={column.numeric}
|
||||
align={column.align}
|
||||
padding={column.disablePadding ? 'none' : 'default'}
|
||||
sortDirection={orderBy === column.id ? order : false}
|
||||
>
|
||||
|
|
|
@ -6,11 +6,9 @@ type Field = boolean | string | null | typeof undefined
|
|||
|
||||
export const required = (value: Field) => (value ? undefined : 'Required')
|
||||
|
||||
export const mustBeInteger = (value: string) =>
|
||||
(!Number.isInteger(Number(value)) || value.includes('.') ? 'Must be an integer' : undefined)
|
||||
export const mustBeInteger = (value: string) => (!Number.isInteger(Number(value)) || value.includes('.') ? 'Must be an integer' : undefined)
|
||||
|
||||
export const mustBeFloat = (value: number) =>
|
||||
(Number.isNaN(Number(value)) ? 'Must be a number' : undefined)
|
||||
export const mustBeFloat = (value: number) => (Number.isNaN(Number(value)) ? 'Must be a number' : undefined)
|
||||
|
||||
export const greaterThan = (min: number) => (value: string) => {
|
||||
if (Number.isNaN(Number(value)) || Number.parseFloat(value) > Number(min)) {
|
||||
|
@ -49,18 +47,16 @@ export const maxValue = (max: number) => (value: string) => {
|
|||
export const ok = () => undefined
|
||||
|
||||
export const mustBeEthereumAddress = (address: Field) => {
|
||||
const isAddress: boolean = getWeb3().isAddress(address)
|
||||
const isAddress: boolean = getWeb3().utils.isAddress(address)
|
||||
|
||||
return isAddress ? undefined : 'Address should be a valid Ethereum address'
|
||||
}
|
||||
|
||||
export const ADDRESS_REPEATED_ERROR = 'Address already introduced'
|
||||
|
||||
export const uniqueAddress = (addresses: string[]) => (value: string) =>
|
||||
(addresses.includes(value) ? ADDRESS_REPEATED_ERROR : undefined)
|
||||
export const uniqueAddress = (addresses: string[]) => (value: string) => (addresses.includes(value) ? ADDRESS_REPEATED_ERROR : undefined)
|
||||
|
||||
export const composeValidators = (...validators: Function[]): FieldValidator => (value: Field) =>
|
||||
validators.reduce((error, validator) => error || validator(value), undefined)
|
||||
export const composeValidators = (...validators: Function[]): FieldValidator => (value: Field) => validators.reduce((error, validator) => error || validator(value), undefined)
|
||||
|
||||
export const inLimit = (limit: number, base: number, baseText: string, symbol: string = 'ETH') => (value: string) => {
|
||||
const amount = Number(value)
|
||||
|
|
|
@ -3,12 +3,14 @@ import {
|
|||
TX_SERVICE_HOST,
|
||||
ENABLED_TX_SERVICE_REMOVAL_SENDER,
|
||||
SIGNATURES_VIA_METAMASK,
|
||||
RELAY_API_URL,
|
||||
} from '~/config/names'
|
||||
|
||||
const devConfig = {
|
||||
[TX_SERVICE_HOST]: 'http://localhost:8000/api/v1/',
|
||||
[ENABLED_TX_SERVICE_REMOVAL_SENDER]: false,
|
||||
[SIGNATURES_VIA_METAMASK]: false,
|
||||
[RELAY_API_URL]: 'https://safe-relay.staging.gnosisdev.com/api/v1/',
|
||||
}
|
||||
|
||||
export default devConfig
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
TX_SERVICE_HOST,
|
||||
ENABLED_TX_SERVICE_REMOVAL_SENDER,
|
||||
SIGNATURES_VIA_METAMASK,
|
||||
RELAY_API_URL,
|
||||
} from '~/config/names'
|
||||
import devConfig from './development'
|
||||
import testConfig from './testing'
|
||||
|
@ -31,6 +32,8 @@ export const getTxServiceHost = () => {
|
|||
|
||||
export const getTxServiceUriFrom = (safeAddress: string) => `safes/${safeAddress}/transactions/`
|
||||
|
||||
export const getRelayUrl = () => getConfig()[RELAY_API_URL]
|
||||
|
||||
export const allowedRemoveSenderInTxHistoryService = () => {
|
||||
const config = getConfig()
|
||||
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
export const TX_SERVICE_HOST = 'tsh'
|
||||
export const ENABLED_TX_SERVICE_REMOVAL_SENDER = 'trs'
|
||||
export const SIGNATURES_VIA_METAMASK = 'svm'
|
||||
export const RELAY_API_URL = 'rau'
|
||||
|
|
|
@ -3,12 +3,14 @@ import {
|
|||
TX_SERVICE_HOST,
|
||||
ENABLED_TX_SERVICE_REMOVAL_SENDER,
|
||||
SIGNATURES_VIA_METAMASK,
|
||||
RELAY_API_URL,
|
||||
} from '~/config/names'
|
||||
|
||||
const prodConfig = {
|
||||
[TX_SERVICE_HOST]: 'https://safe-transaction-history.dev.gnosisdev.com/api/v1/',
|
||||
[ENABLED_TX_SERVICE_REMOVAL_SENDER]: false,
|
||||
[SIGNATURES_VIA_METAMASK]: false,
|
||||
[RELAY_API_URL]: 'https://safe-relay.staging.gnosisdev.com/api/v1/',
|
||||
}
|
||||
|
||||
export default prodConfig
|
||||
|
|
|
@ -3,12 +3,14 @@ import {
|
|||
TX_SERVICE_HOST,
|
||||
ENABLED_TX_SERVICE_REMOVAL_SENDER,
|
||||
SIGNATURES_VIA_METAMASK,
|
||||
RELAY_API_URL,
|
||||
} from '~/config/names'
|
||||
|
||||
const testConfig = {
|
||||
[TX_SERVICE_HOST]: 'http://localhost:8000/api/v1/',
|
||||
[ENABLED_TX_SERVICE_REMOVAL_SENDER]: false,
|
||||
[SIGNATURES_VIA_METAMASK]: false,
|
||||
[RELAY_API_URL]: 'https://safe-relay.staging.gnosisdev.com/api/v1/',
|
||||
}
|
||||
|
||||
export default testConfig
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
// @flow
|
||||
/* eslint-disable */
|
||||
import 'babel-polyfill'
|
||||
require('dotenv').config()
|
||||
|
||||
import { MuiThemeProvider } from '@material-ui/core/styles'
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Provider } from 'react-redux'
|
||||
import { ConnectedRouter } from 'react-router-redux'
|
||||
import { ConnectedRouter } from 'connected-react-router'
|
||||
import PageFrame from '~/components/layout/PageFrame'
|
||||
import { history, store } from '~/store'
|
||||
import theme from '~/theme/mui'
|
||||
|
@ -25,7 +27,4 @@ const Root = () => (
|
|||
</Provider>
|
||||
)
|
||||
|
||||
ReactDOM.render(
|
||||
<Root />,
|
||||
document.getElementById('root'),
|
||||
)
|
||||
ReactDOM.render(<Root />, document.getElementById('root'))
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import contract from 'truffle-contract'
|
||||
import { ensureOnce } from '~/utils/singleton'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
import GnosisSafeSol from '#/GnosisSafe.json'
|
||||
import ProxyFactorySol from '#/ProxyFactory.json'
|
||||
import { calculateGasOf, calculateGasPrice } from '~/logic/wallets/ethTransactions'
|
||||
|
@ -42,7 +41,7 @@ const instanciateMasterCopies = async () => {
|
|||
// ONLY USED IN TEST ENVIRONMENT
|
||||
const createMasterCopies = async () => {
|
||||
const web3 = getWeb3()
|
||||
const accounts = await promisify(cb => web3.eth.getAccounts(cb))
|
||||
const accounts = await web3.eth.getAccounts()
|
||||
const userAccount = accounts[0]
|
||||
|
||||
const ProxyFactory = getCreateProxyFactoryContract(web3)
|
||||
|
@ -60,13 +59,13 @@ export const getSafeMasterContract = async () => {
|
|||
return safeMaster
|
||||
}
|
||||
|
||||
export const deploySafeContract = async (
|
||||
safeAccounts: string[],
|
||||
numConfirmations: number,
|
||||
userAccount: string,
|
||||
) => {
|
||||
const gnosisSafeData = await safeMaster.contract.setup.getData(safeAccounts, numConfirmations, 0, '0x')
|
||||
const proxyFactoryData = proxyFactoryMaster.contract.createProxy.getData(safeMaster.address, gnosisSafeData)
|
||||
export const deploySafeContract = async (safeAccounts: string[], numConfirmations: number, userAccount: string) => {
|
||||
const gnosisSafeData = await safeMaster.contract.methods
|
||||
.setup(safeAccounts, numConfirmations, '0x0000000000000000000000000000000000000000', '0x')
|
||||
.encodeABI()
|
||||
const proxyFactoryData = proxyFactoryMaster.contract.methods
|
||||
.createProxy(safeMaster.address, gnosisSafeData)
|
||||
.encodeABI()
|
||||
const gas = await calculateGasOf(proxyFactoryData, userAccount, proxyFactoryMaster.address)
|
||||
const gasPrice = await calculateGasPrice()
|
||||
|
||||
|
@ -76,7 +75,7 @@ export const deploySafeContract = async (
|
|||
export const getGnosisSafeInstanceAt = async (safeAddress: string) => {
|
||||
const web3 = getWeb3()
|
||||
const GnosisSafe = await getGnosisSafeContract(web3)
|
||||
const gnosisSafe = GnosisSafe.at(safeAddress)
|
||||
const gnosisSafe = await GnosisSafe.at(safeAddress)
|
||||
|
||||
return gnosisSafe
|
||||
}
|
||||
|
|
|
@ -23,8 +23,17 @@ export const approveTransaction = async (
|
|||
// return executeTransaction(safeAddress, to, valueInWei, data, operation, nonce, sender)
|
||||
const safe = await getSafeEthereumInstance(safeAddress)
|
||||
const txGasEstimate = await generateTxGasEstimateFrom(safe, safeAddress, data, to, valueInWei, operation)
|
||||
const signature =
|
||||
await generateMetamaskSignature(safe, safeAddress, sender, to, valueInWei, nonce, data, operation, txGasEstimate)
|
||||
const signature = await generateMetamaskSignature(
|
||||
safe,
|
||||
safeAddress,
|
||||
sender,
|
||||
to,
|
||||
valueInWei,
|
||||
nonce,
|
||||
data,
|
||||
operation,
|
||||
txGasEstimate,
|
||||
)
|
||||
storeSignature(safeAddress, nonce, signature)
|
||||
|
||||
return undefined
|
||||
|
@ -33,7 +42,7 @@ export const approveTransaction = async (
|
|||
const gnosisSafe = await getSafeEthereumInstance(safeAddress)
|
||||
const contractTxHash = await gnosisSafe.getTransactionHash(to, valueInWei, data, operation, 0, 0, 0, 0, 0, nonce)
|
||||
|
||||
const approveData = gnosisSafe.contract.approveHash.getData(contractTxHash)
|
||||
const approveData = gnosisSafe.contract.methods.approveHash(contractTxHash).encodeABI()
|
||||
const gas = await calculateGasOf(approveData, sender, safeAddress)
|
||||
const txReceipt = await gnosisSafe.approveHash(contractTxHash, { from: sender, gas, gasPrice })
|
||||
|
||||
|
@ -60,16 +69,35 @@ export const executeTransaction = async (
|
|||
if (signaturesViaMetamask()) {
|
||||
const safe = await getSafeEthereumInstance(safeAddress)
|
||||
const txGasEstimate = await generateTxGasEstimateFrom(safe, safeAddress, data, to, valueInWei, operation)
|
||||
const signature =
|
||||
await generateMetamaskSignature(safe, safeAddress, sender, to, valueInWei, nonce, data, operation, txGasEstimate)
|
||||
const signature = await generateMetamaskSignature(
|
||||
safe,
|
||||
safeAddress,
|
||||
sender,
|
||||
to,
|
||||
valueInWei,
|
||||
nonce,
|
||||
data,
|
||||
operation,
|
||||
txGasEstimate,
|
||||
)
|
||||
storeSignature(safeAddress, nonce, signature)
|
||||
|
||||
const sigs = getSignaturesFrom(safeAddress, nonce)
|
||||
const threshold = await safe.getThreshold()
|
||||
const gas =
|
||||
await estimateDataGas(safe, to, valueInWei, data, operation, txGasEstimate, 0, nonce, Number(threshold), 0)
|
||||
const gas = await estimateDataGas(
|
||||
safe,
|
||||
to,
|
||||
valueInWei,
|
||||
data,
|
||||
operation,
|
||||
txGasEstimate,
|
||||
0,
|
||||
nonce,
|
||||
Number(threshold),
|
||||
0,
|
||||
)
|
||||
const numOwners = await safe.getOwners()
|
||||
const gasIncludingRemovingStoreUpfront = gas + txGasEstimate + (numOwners.length * 15000)
|
||||
const gasIncludingRemovingStoreUpfront = gas + txGasEstimate + numOwners.length * 15000
|
||||
|
||||
const txReceipt = await safe.execTransaction(
|
||||
to,
|
||||
|
@ -94,24 +122,17 @@ export const executeTransaction = async (
|
|||
|
||||
const gnosisSafe = await getSafeEthereumInstance(safeAddress)
|
||||
const signatures = buildSignaturesFrom(ownersWhoHasSigned, sender)
|
||||
const txExecutionData =
|
||||
gnosisSafe.contract.execTransaction.getData(to, valueInWei, data, operation, 0, 0, 0, 0, 0, signatures)
|
||||
const txExecutionData = gnosisSafe.contract.methods
|
||||
.execTransaction(to, valueInWei, data, operation, 0, 0, 0, 0, 0, signatures)
|
||||
.encodeABI()
|
||||
const gas = await calculateGasOf(txExecutionData, sender, safeAddress)
|
||||
const numOwners = await gnosisSafe.getOwners()
|
||||
const gasIncludingRemovingStoreUpfront = gas + (numOwners.length * 15000)
|
||||
const txReceipt = await gnosisSafe.execTransaction(
|
||||
to,
|
||||
valueInWei,
|
||||
data,
|
||||
operation,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
signatures,
|
||||
{ from: sender, gas: gasIncludingRemovingStoreUpfront, gasPrice },
|
||||
)
|
||||
const gasIncludingRemovingStoreUpfront = gas + numOwners.length * 15000
|
||||
const txReceipt = await gnosisSafe.execTransaction(to, valueInWei, data, operation, 0, 0, 0, 0, 0, signatures, {
|
||||
from: sender,
|
||||
gas: gasIncludingRemovingStoreUpfront,
|
||||
gasPrice,
|
||||
})
|
||||
const txHash = txReceipt.tx
|
||||
await checkReceiptStatus(txHash)
|
||||
|
||||
|
|
|
@ -26,14 +26,15 @@ const hasOneOwner = (safe: Safe) => {
|
|||
export const getSafeEthereumInstance = async (safeAddress: string) => {
|
||||
const web3 = getWeb3()
|
||||
const GnosisSafe = await getGnosisSafeContract(web3)
|
||||
return GnosisSafe.at(safeAddress)
|
||||
const safeInstance = await GnosisSafe.at(safeAddress)
|
||||
return safeInstance
|
||||
}
|
||||
|
||||
export const createTransaction = async (
|
||||
safe: Safe,
|
||||
name: string,
|
||||
to: string,
|
||||
value: number,
|
||||
value: string,
|
||||
nonce: number,
|
||||
sender: string,
|
||||
data: string = EMPTY_DATA,
|
||||
|
@ -41,7 +42,7 @@ export const createTransaction = async (
|
|||
const web3 = getWeb3()
|
||||
const safeAddress = safe.get('address')
|
||||
const threshold = safe.get('threshold')
|
||||
const valueInWei = web3.toWei(value, 'ether')
|
||||
const valueInWei = web3.utils.toWei(value, 'ether')
|
||||
const CALL = 0
|
||||
|
||||
const isExecution = hasOneOwner(safe) || threshold === 1
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
// @flow
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
import { BigNumber } from 'bignumber.js'
|
||||
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
||||
import { getSignaturesFrom } from '~/utils/localStorage/signatures'
|
||||
|
@ -21,7 +20,6 @@ const estimateDataGasCosts = (data) => {
|
|||
return data.match(/.{2}/g).reduce(reducer, 0)
|
||||
}
|
||||
|
||||
|
||||
export const estimateDataGas = (
|
||||
safe: any,
|
||||
to: string,
|
||||
|
@ -42,8 +40,9 @@ export const estimateDataGas = (
|
|||
const signatureCost = signatureCount * (68 + 2176 + 2176) // array count (3 -> r, s, v) * signature count
|
||||
|
||||
const sigs = getSignaturesFrom(safe.address, nonce)
|
||||
const payload = safe.contract.execTransaction
|
||||
.getData(to, valueInWei, data, operation, txGasEstimate, 0, gasPrice, gasToken, refundReceiver, sigs)
|
||||
const payload = safe.contract.methods
|
||||
.execTransaction(to, valueInWei, data, operation, txGasEstimate, 0, gasPrice, gasToken, refundReceiver, sigs)
|
||||
.encodeABI()
|
||||
|
||||
let dataGasEstimate = estimateDataGasCosts(payload) + signatureCost
|
||||
if (dataGasEstimate > 65536) {
|
||||
|
@ -64,19 +63,19 @@ export const generateTxGasEstimateFrom = async (
|
|||
operation: number,
|
||||
) => {
|
||||
try {
|
||||
const estimateData = safe.contract.requiredTxGas.getData(to, valueInWei, data, operation)
|
||||
const estimateResponse = await promisify(cb => getWeb3().eth.call({
|
||||
const estimateData = safe.contract.methods.requiredTxGas(to, valueInWei, data, operation).encodeABI()
|
||||
const estimateResponse = await getWeb3().eth.call({
|
||||
to: safeAddress,
|
||||
from: safeAddress,
|
||||
data: estimateData,
|
||||
}, cb))
|
||||
})
|
||||
const txGasEstimate = new BigNumber(estimateResponse.substring(138), 16)
|
||||
|
||||
// Add 10k else we will fail in case of nested calls
|
||||
return Promise.resolve(txGasEstimate.toNumber() + 10000)
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line
|
||||
console.log("Error calculating tx gas estimation " + error)
|
||||
console.log('Error calculating tx gas estimation ' + error)
|
||||
return Promise.resolve(0)
|
||||
}
|
||||
}
|
||||
|
@ -151,8 +150,16 @@ export const generateMetamaskSignature = async (
|
|||
txGasEstimate: number,
|
||||
) => {
|
||||
const web3 = getWeb3()
|
||||
const typedData =
|
||||
await generateTypedDataFrom(safe, safeAddress, to, valueInWei, nonce, data, operation, txGasEstimate)
|
||||
const typedData = await generateTypedDataFrom(
|
||||
safe,
|
||||
safeAddress,
|
||||
to,
|
||||
valueInWei,
|
||||
nonce,
|
||||
data,
|
||||
operation,
|
||||
txGasEstimate,
|
||||
)
|
||||
|
||||
const jsonTypedData = JSON.stringify(typedData)
|
||||
const signedTypedData = {
|
||||
|
@ -163,7 +170,7 @@ export const generateMetamaskSignature = async (
|
|||
params: [jsonTypedData, sender],
|
||||
from: sender,
|
||||
}
|
||||
const txSignedResponse = await promisify(cb => web3.currentProvider.sendAsync(signedTypedData, cb))
|
||||
const txSignedResponse = await web3.currentProvider.sendAsync(signedTypedData)
|
||||
|
||||
return txSignedResponse.result.replace(EMPTY_DATA, '')
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// @flow
|
||||
import { BigNumber } from 'bignumber.js'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
import { enhancedFetch } from '~/utils/fetch'
|
||||
|
||||
// const MAINNET_NETWORK = 1
|
||||
|
@ -13,7 +12,7 @@ export const checkReceiptStatus = async (hash: string) => {
|
|||
}
|
||||
|
||||
const web3 = getWeb3()
|
||||
const txReceipt = await promisify(cb => web3.eth.getTransactionReceipt(hash, cb))
|
||||
const txReceipt = await web3.eth.getTransactionReceipt(hash)
|
||||
|
||||
const { status } = txReceipt
|
||||
if (!status) {
|
||||
|
@ -53,7 +52,7 @@ export const calculateGasPrice = async () => {
|
|||
export const calculateGasOf = async (data: Object, from: string, to: string) => {
|
||||
const web3 = getWeb3()
|
||||
try {
|
||||
const gas = await promisify(cb => web3.eth.estimateGas({ data, from, to }, cb))
|
||||
const gas = await web3.eth.estimateGas({ data, from, to })
|
||||
|
||||
return gas * 2
|
||||
} catch (err) {
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
// @flow
|
||||
import { BigNumber } from 'bignumber.js'
|
||||
import Web3 from 'web3'
|
||||
import type { ProviderProps } from '~/logic/wallets/store/model/provider'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
|
||||
export const ETHEREUM_NETWORK = {
|
||||
MAIN: 'MAIN',
|
||||
|
@ -49,13 +47,13 @@ const isMetamask: Function = (web3Provider): boolean => {
|
|||
}
|
||||
|
||||
const getAccountFrom: Function = async (web3Provider): Promise<string | null> => {
|
||||
const accounts = await promisify(cb => web3Provider.eth.getAccounts(cb))
|
||||
const accounts = await web3Provider.eth.getAccounts()
|
||||
|
||||
return accounts && accounts.length > 0 ? accounts[0] : null
|
||||
}
|
||||
|
||||
const getNetworkIdFrom = async (web3Provider) => {
|
||||
const networkId = await promisify(cb => web3Provider.version.getNetwork(cb))
|
||||
const networkId = await web3Provider.eth.net.getId()
|
||||
|
||||
return networkId
|
||||
}
|
||||
|
@ -75,7 +73,7 @@ export const getProviderInfo: Function = async (): Promise<ProviderProps> => {
|
|||
console.log('Injected web3 detected.')
|
||||
}
|
||||
|
||||
const name = isMetamask(web3) ? WALLET_PROVIDER.METAMASK : 'UNKNOWN'
|
||||
const name = isMetamask(window.web3) ? WALLET_PROVIDER.METAMASK : 'UNKNOWN'
|
||||
const account = await getAccountFrom(web3)
|
||||
const network = await getNetworkIdFrom(web3)
|
||||
|
||||
|
@ -91,10 +89,11 @@ export const getProviderInfo: Function = async (): Promise<ProviderProps> => {
|
|||
}
|
||||
|
||||
export const getBalanceInEtherOf = async (safeAddress: string) => {
|
||||
const funds: BigNumber = await promisify(cb => web3.eth.getBalance(safeAddress, cb))
|
||||
const funds: String = await web3.eth.getBalance(safeAddress)
|
||||
|
||||
if (!funds) {
|
||||
return '0'
|
||||
}
|
||||
|
||||
return web3.fromWei(funds.toNumber(), 'ether').toString()
|
||||
return web3.utils.fromWei(funds, 'ether').toString()
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { BigNumber } from 'bignumber.js'
|
||||
|
||||
export const toNative = async (amt: string | number | BigNumber, decimal: number): Promise<BigNumber> => {
|
||||
export const toNative = (amt: string | number | BigNumber, decimal: number): BigNumber => {
|
||||
const web3 = getWeb3()
|
||||
|
||||
return web3.toBigNumber(amt).mul(10 ** decimal)
|
||||
return web3.utils.toBN(amt).mul(web3.utils.toBN(10 ** decimal))
|
||||
}
|
||||
|
|
|
@ -4,7 +4,15 @@ import Loadable from 'react-loadable'
|
|||
import { Switch, Redirect, Route } from 'react-router-dom'
|
||||
import Loader from '~/components/Loader'
|
||||
import Welcome from './welcome/container'
|
||||
import { SAFELIST_ADDRESS, OPEN_ADDRESS, SAFE_PARAM_ADDRESS, WELCOME_ADDRESS, SETTINS_ADDRESS, OPENING_ADDRESS, LOAD_ADDRESS } from './routes'
|
||||
import {
|
||||
SAFELIST_ADDRESS,
|
||||
OPEN_ADDRESS,
|
||||
SAFE_PARAM_ADDRESS,
|
||||
WELCOME_ADDRESS,
|
||||
SETTINS_ADDRESS,
|
||||
OPENING_ADDRESS,
|
||||
LOAD_ADDRESS,
|
||||
} from './routes'
|
||||
|
||||
const Safe = Loadable({
|
||||
loader: () => import('./safe/container'),
|
||||
|
@ -55,4 +63,3 @@ const Routes = () => (
|
|||
)
|
||||
|
||||
export default Routes
|
||||
|
||||
|
|
|
@ -3,7 +3,9 @@ import * as React from 'react'
|
|||
import contract from 'truffle-contract'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import Field from '~/components/forms/Field'
|
||||
import { composeValidators, required, noErrorsOn, mustBeEthereumAddress } from '~/components/forms/validator'
|
||||
import {
|
||||
composeValidators, required, noErrorsOn, mustBeEthereumAddress,
|
||||
} from '~/components/forms/validator'
|
||||
import TextField from '~/components/forms/TextField'
|
||||
import InputAdornment from '@material-ui/core/InputAdornment'
|
||||
import CheckCircle from '@material-ui/icons/CheckCircle'
|
||||
|
@ -12,7 +14,6 @@ import Paragraph from '~/components/layout/Paragraph'
|
|||
import OpenPaper from '~/components/Stepper/OpenPaper'
|
||||
import { FIELD_LOAD_NAME, FIELD_LOAD_ADDRESS } from '~/routes/load/components/fields'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
import SafeProxy from '#/Proxy.json'
|
||||
import { getSafeMasterContract } from '~/logic/contracts/safeContracts'
|
||||
|
||||
|
@ -47,7 +48,7 @@ export const safeFieldsValidation = async (values: Object) => {
|
|||
// https://solidity.readthedocs.io/en/latest/metadata.html#usage-for-source-code-verification
|
||||
const metaData = 'a165'
|
||||
|
||||
const code = await promisify(cb => web3.eth.getCode(safeAddress, cb))
|
||||
const code = await web3.eth.getCode(safeAddress)
|
||||
const codeWithoutMetadata = code.substring(0, code.lastIndexOf(metaData))
|
||||
|
||||
const proxyCode = SafeProxy.deployedBytecode
|
||||
|
@ -63,7 +64,7 @@ export const safeFieldsValidation = async (values: Object) => {
|
|||
// check mastercopy
|
||||
const proxy = contract(SafeProxy)
|
||||
proxy.setProvider(web3.currentProvider)
|
||||
const proxyInstance = proxy.at(safeAddress)
|
||||
const proxyInstance = await proxy.at(safeAddress)
|
||||
const proxyImplementation = await proxyInstance.implementation()
|
||||
|
||||
const safeMaster = await getSafeMasterContract()
|
||||
|
|
|
@ -7,7 +7,9 @@ import Identicon from '~/components/Identicon'
|
|||
import OpenPaper from '~/components/Stepper/OpenPaper'
|
||||
import Row from '~/components/layout/Row'
|
||||
import Paragraph from '~/components/layout/Paragraph'
|
||||
import { xs, sm, lg, border, secondary } from '~/theme/variables'
|
||||
import {
|
||||
xs, sm, lg, border, secondary,
|
||||
} from '~/theme/variables'
|
||||
import { openAddressInEtherScan, getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { FIELD_LOAD_NAME, FIELD_LOAD_ADDRESS } from '~/routes/load/components/fields'
|
||||
import { sameAddress } from '~/logic/wallets/ethAddresses'
|
||||
|
@ -62,6 +64,8 @@ class ReviewComponent extends React.PureComponent<Props, State> {
|
|||
isOwner: false,
|
||||
}
|
||||
|
||||
mounted = false
|
||||
|
||||
componentDidMount = async () => {
|
||||
this.mounted = true
|
||||
|
||||
|
@ -70,7 +74,7 @@ class ReviewComponent extends React.PureComponent<Props, State> {
|
|||
const web3 = getWeb3()
|
||||
|
||||
const GnosisSafe = getGnosisSafeContract(web3)
|
||||
const gnosisSafe = GnosisSafe.at(safeAddress)
|
||||
const gnosisSafe = await GnosisSafe.at(safeAddress)
|
||||
const owners = await gnosisSafe.getOwners()
|
||||
if (!owners) {
|
||||
return
|
||||
|
@ -86,8 +90,6 @@ class ReviewComponent extends React.PureComponent<Props, State> {
|
|||
this.mounted = false
|
||||
}
|
||||
|
||||
mounted = false
|
||||
|
||||
render() {
|
||||
const { values, classes, network } = this.props
|
||||
const { isOwner } = this.state
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
// @flow
|
||||
export const FIELD_LOAD_NAME: string = 'name'
|
||||
export const FIELD_LOAD_ADDRESS: string = 'address'
|
||||
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
import * as React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import Page from '~/components/layout/Page'
|
||||
import { getAccountsFrom, getThresholdFrom, getNamesFrom, getSafeNameFrom } from '~/routes/open/utils/safeDataExtractor'
|
||||
import {
|
||||
getAccountsFrom, getThresholdFrom, getNamesFrom, getSafeNameFrom,
|
||||
} from '~/routes/open/utils/safeDataExtractor'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { getGnosisSafeContract, deploySafeContract, initContracts } from '~/logic/contracts/safeContracts'
|
||||
import { checkReceiptStatus } from '~/logic/wallets/ethTransactions'
|
||||
|
@ -34,8 +36,9 @@ export const createSafe = async (values: Object, userAccount: string, addSafe: A
|
|||
await initContracts()
|
||||
const safe = await deploySafeContract(accounts, numConfirmations, userAccount)
|
||||
checkReceiptStatus(safe.tx)
|
||||
|
||||
const param = safe.logs[0].args.proxy
|
||||
const safeContract = GnosisSafe.at(param)
|
||||
const safeContract = await GnosisSafe.at(param)
|
||||
|
||||
addSafe(name, safeContract.address, numConfirmations, owners, accounts)
|
||||
|
||||
|
@ -63,7 +66,7 @@ class Open extends React.Component<Props> {
|
|||
history.push(OPENING_ADDRESS)
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line
|
||||
console.log('Error while creating the Safe' + error)
|
||||
console.error('Error while creating the Safe: ' + error)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,14 +12,13 @@ import Review from './Review'
|
|||
import selector, { type SelectorProps } from './selector'
|
||||
import actions, { type Actions } from './actions'
|
||||
|
||||
const getSteps = () => [
|
||||
'Fill Owner Form', 'Review Add order operation',
|
||||
]
|
||||
const getSteps = () => ['Fill Owner Form', 'Review Add order operation']
|
||||
|
||||
type Props = SelectorProps & Actions & {
|
||||
safe: Safe,
|
||||
threshold: number,
|
||||
}
|
||||
type Props = SelectorProps &
|
||||
Actions & {
|
||||
safe: Safe,
|
||||
threshold: number,
|
||||
}
|
||||
|
||||
type State = {
|
||||
done: boolean,
|
||||
|
@ -44,8 +43,8 @@ export const addOwner = async (values: Object, safe: Safe, threshold: number, ex
|
|||
const newOwnerAddress = values[OWNER_ADDRESS_PARAM]
|
||||
const newOwnerName = values[NAME_PARAM]
|
||||
|
||||
const data = gnosisSafe.contract.addOwnerWithThreshold.getData(newOwnerAddress, newThreshold)
|
||||
await createTransaction(safe, `Add Owner ${newOwnerName}`, safeAddress, 0, nonce, executor, data)
|
||||
const data = gnosisSafe.contract.methods.addOwnerWithThreshold(newOwnerAddress, newThreshold).encodeABI()
|
||||
await createTransaction(safe, `Add Owner ${newOwnerName}`, safeAddress, '0', nonce, executor, data)
|
||||
setOwners(safeAddress, safe.get('owners').push(makeOwner({ name: newOwnerName, address: newOwnerAddress })))
|
||||
}
|
||||
|
||||
|
@ -90,15 +89,16 @@ class AddOwner extends React.Component<Props, State> {
|
|||
onReset={this.onReset}
|
||||
>
|
||||
<Stepper.Page numOwners={safe.get('owners').count()} threshold={safe.get('threshold')} addresses={addresses}>
|
||||
{ AddOwnerForm }
|
||||
</Stepper.Page>
|
||||
<Stepper.Page>
|
||||
{ Review }
|
||||
{AddOwnerForm}
|
||||
</Stepper.Page>
|
||||
<Stepper.Page>{Review}</Stepper.Page>
|
||||
</Stepper>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(selector, actions)(AddOwner)
|
||||
export default connect(
|
||||
selector,
|
||||
actions,
|
||||
)(AddOwner)
|
||||
|
|
|
@ -37,10 +37,9 @@ type State = {
|
|||
filter: string,
|
||||
}
|
||||
|
||||
const filterBy = (filter: string, tokens: List<Token>): List<Token> =>
|
||||
tokens.filter((token: Token) => !filter ||
|
||||
token.get('symbol').toLowerCase().includes(filter.toLowerCase()) ||
|
||||
token.get('name').toLowerCase().includes(filter.toLowerCase()))
|
||||
const filterBy = (filter: string, tokens: List<Token>): List<Token> => tokens.filter((token: Token) => !filter
|
||||
|| token.get('symbol').toLowerCase().includes(filter.toLowerCase())
|
||||
|| token.get('name').toLowerCase().includes(filter.toLowerCase()))
|
||||
|
||||
|
||||
class Tokens extends React.Component<Props, State> {
|
||||
|
@ -71,6 +70,7 @@ class Tokens extends React.Component<Props, State> {
|
|||
|
||||
render() {
|
||||
const { onClose, classes, tokens } = this.props
|
||||
const { filter } = this.state
|
||||
const searchClasses = {
|
||||
input: classes.searchInput,
|
||||
root: classes.searchRoot,
|
||||
|
@ -78,7 +78,7 @@ class Tokens extends React.Component<Props, State> {
|
|||
searchContainer: classes.searchContainer,
|
||||
}
|
||||
|
||||
const filteredTokens = filterBy(this.state.filter, tokens)
|
||||
const filteredTokens = filterBy(filter, tokens)
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
@ -102,7 +102,7 @@ class Tokens extends React.Component<Props, State> {
|
|||
<Spacer />
|
||||
<Divider />
|
||||
<Spacer />
|
||||
<Button variant="contained" size="small" color="secondary" className={classes.add} disabled>
|
||||
<Button variant="contained" size="small" color="secondary" className={classes.add}>
|
||||
+ ADD CUSTOM TOKEN
|
||||
</Button>
|
||||
</Row>
|
||||
|
@ -112,7 +112,7 @@ class Tokens extends React.Component<Props, State> {
|
|||
{filteredTokens.map((token: Token) => (
|
||||
<ListItem key={token.get('address')} className={classes.token}>
|
||||
<ListItemIcon>
|
||||
<Img src={token.get('logoUrl')} height={28} alt={token.get('name')} />
|
||||
<Img src={token.get('logoUri')} height={28} alt={token.get('name')} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={token.get('symbol')} secondary={token.get('name')} />
|
||||
<ListItemSecondaryAction>
|
||||
|
@ -132,4 +132,3 @@ class Tokens extends React.Component<Props, State> {
|
|||
const TokenComponent = withStyles(styles)(Tokens)
|
||||
|
||||
export default connect(undefined, actions)(TokenComponent)
|
||||
|
||||
|
|
|
@ -31,7 +31,6 @@ export const generateColumns = () => {
|
|||
const assetRow: Column = {
|
||||
id: BALANCE_TABLE_ASSET_ID,
|
||||
order: false,
|
||||
numeric: false,
|
||||
disablePadding: false,
|
||||
label: 'Asset',
|
||||
custom: false,
|
||||
|
@ -40,8 +39,8 @@ export const generateColumns = () => {
|
|||
|
||||
const balanceRow: Column = {
|
||||
id: BALANCE_TABLE_BALANCE_ID,
|
||||
align: 'right',
|
||||
order: true,
|
||||
numeric: true,
|
||||
disablePadding: false,
|
||||
label: 'Balance',
|
||||
custom: false,
|
||||
|
@ -50,7 +49,6 @@ export const generateColumns = () => {
|
|||
const actions: Column = {
|
||||
id: 'actions',
|
||||
order: false,
|
||||
numeric: false,
|
||||
disablePadding: false,
|
||||
label: '',
|
||||
custom: true,
|
||||
|
|
|
@ -16,7 +16,9 @@ import Paragraph from '~/components/layout/Paragraph'
|
|||
import Modal from '~/components/Modal'
|
||||
import { type Column, cellWidth } from '~/components/Table/TableHead'
|
||||
import Table from '~/components/Table'
|
||||
import { getBalanceData, generateColumns, BALANCE_TABLE_ASSET_ID, type BalanceRow, filterByZero } from './dataFetcher'
|
||||
import {
|
||||
getBalanceData, generateColumns, BALANCE_TABLE_ASSET_ID, type BalanceRow, filterByZero,
|
||||
} from './dataFetcher'
|
||||
import Tokens from './Tokens'
|
||||
import Send from './Send'
|
||||
import Receive from './Receive'
|
||||
|
@ -114,32 +116,50 @@ class Balances extends React.Component<Props, State> {
|
|||
>
|
||||
{(sortedData: Array<BalanceRow>) => sortedData.map((row: any, index: number) => (
|
||||
<TableRow tabIndex={-1} key={index} className={classes.hide}>
|
||||
{ autoColumns.map((column: Column) => (
|
||||
<TableCell key={column.id} style={cellWidth(column.width)} numeric={column.numeric} component="td">
|
||||
{autoColumns.map((column: Column) => (
|
||||
<TableCell key={column.id} style={cellWidth(column.width)} align={column.align} component="td">
|
||||
{row[column.id]}
|
||||
</TableCell>
|
||||
)) }
|
||||
))}
|
||||
<TableCell component="td">
|
||||
<Row align="end" className={classes.actions}>
|
||||
{ granted &&
|
||||
<Button variant="contained" size="small" color="secondary" className={classes.send} onClick={this.onShow('Send')}>
|
||||
{granted && (
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
color="secondary"
|
||||
className={classes.send}
|
||||
onClick={this.onShow('Send')}
|
||||
>
|
||||
<CallMade className={classNames(classes.leftIcon, classes.iconSmall)} />
|
||||
Send
|
||||
Send
|
||||
</Button>
|
||||
}
|
||||
<Button variant="contained" size="small" color="secondary" className={classes.receive} onClick={this.onShow('Receive')}>
|
||||
)}
|
||||
<Button
|
||||
variant="contained"
|
||||
size="small"
|
||||
color="secondary"
|
||||
className={classes.receive}
|
||||
onClick={this.onShow('Receive')}
|
||||
>
|
||||
<CallReceived className={classNames(classes.leftIcon, classes.iconSmall)} />
|
||||
Receive
|
||||
Receive
|
||||
</Button>
|
||||
</Row>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
))
|
||||
}
|
||||
</Table>
|
||||
<Modal title="Send Tokens" description="Send Tokens Form" handleClose={this.onHide('Send')} open={showSend}>
|
||||
<Send onClose={this.onHide('Send')} />
|
||||
</Modal>
|
||||
<Modal title="Receive Tokens" description="Receive Tokens Form" handleClose={this.onHide('Receive')} open={showReceive}>
|
||||
<Modal
|
||||
title="Receive Tokens"
|
||||
description="Receive Tokens Form"
|
||||
handleClose={this.onHide('Receive')}
|
||||
open={showReceive}
|
||||
>
|
||||
<Receive onClose={this.onHide('Receive')} />
|
||||
</Modal>
|
||||
</React.Fragment>
|
||||
|
|
|
@ -13,7 +13,10 @@ import Paragraph from '~/components/layout/Paragraph'
|
|||
import NoSafe from '~/components/NoSafe'
|
||||
import { type SelectorProps } from '~/routes/safe/container/selector'
|
||||
import { openAddressInEtherScan } from '~/logic/wallets/getWeb3'
|
||||
import { sm, xs, secondary, smallFontSize } from '~/theme/variables'
|
||||
import {
|
||||
sm, xs, secondary, smallFontSize,
|
||||
} from '~/theme/variables'
|
||||
import { copyToClipboard } from '~/utils/clipboard'
|
||||
import Balances from './Balances'
|
||||
|
||||
type Props = SelectorProps & {
|
||||
|
@ -74,10 +77,16 @@ class Layout extends React.Component<Props, State> {
|
|||
this.setState({ value })
|
||||
}
|
||||
|
||||
copyAddress = () => {
|
||||
const { safe } = this.props
|
||||
|
||||
if (safe.address) {
|
||||
copyToClipboard(safe.address)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
safe, provider, network, classes, granted, tokens, activeTokens,
|
||||
} = this.props
|
||||
const { safe, provider, network, classes, granted, tokens, activeTokens } = this.props
|
||||
const { value } = this.state
|
||||
|
||||
if (!safe) {
|
||||
|
@ -92,15 +101,15 @@ class Layout extends React.Component<Props, State> {
|
|||
<Identicon address={address} diameter={50} />
|
||||
<Block className={classes.name}>
|
||||
<Row>
|
||||
<Heading tag="h2" color="secondary">{safe.get('name')}</Heading>
|
||||
{ !granted &&
|
||||
<Block className={classes.readonly} >
|
||||
Read Only
|
||||
</Block>
|
||||
}
|
||||
<Heading tag="h2" color="secondary">
|
||||
{safe.get('name')}
|
||||
</Heading>
|
||||
{!granted && <Block className={classes.readonly}>Read Only</Block>}
|
||||
</Row>
|
||||
<Block align="center" className={classes.user}>
|
||||
<Paragraph size="md" color="disabled" noMargin>{address}</Paragraph>
|
||||
<Paragraph size="md" color="disabled" onClick={this.copyAddress} title="Click to copy" noMargin>
|
||||
{address}
|
||||
</Paragraph>
|
||||
<OpenInNew
|
||||
className={classes.open}
|
||||
style={openIconStyle}
|
||||
|
@ -110,25 +119,16 @@ class Layout extends React.Component<Props, State> {
|
|||
</Block>
|
||||
</Block>
|
||||
<Row>
|
||||
<Tabs
|
||||
value={value}
|
||||
onChange={this.handleChange}
|
||||
indicatorColor="secondary"
|
||||
textColor="secondary"
|
||||
>
|
||||
<Tabs value={value} onChange={this.handleChange} indicatorColor="secondary" textColor="secondary">
|
||||
<Tab label="Balances" />
|
||||
<Tab label="Transactions" />
|
||||
<Tab label="Settings" />
|
||||
</Tabs>
|
||||
</Row>
|
||||
<Hairline color="#c8ced4" />
|
||||
{value === 0 &&
|
||||
<Balances
|
||||
tokens={tokens}
|
||||
activeTokens={activeTokens}
|
||||
granted={granted}
|
||||
safeAddress={address}
|
||||
/>}
|
||||
{value === 0 && (
|
||||
<Balances tokens={tokens} activeTokens={activeTokens} granted={granted} safeAddress={address} />
|
||||
)}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ const NoRights = () => (
|
|||
<Button
|
||||
component={Link}
|
||||
to={SAFELIST_ADDRESS}
|
||||
variant="raised"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Safe List
|
||||
|
|
|
@ -48,10 +48,10 @@ export const removeOwner = async (
|
|||
const storedOwners = await gnosisSafe.getOwners()
|
||||
const index = storedOwners.findIndex(ownerAddress => ownerAddress === userToRemove)
|
||||
const prevAddress = index === 0 ? SENTINEL_ADDRESS : storedOwners[index - 1]
|
||||
const data = gnosisSafe.contract.removeOwner.getData(prevAddress, userToRemove, newThreshold)
|
||||
const data = gnosisSafe.contract.removeOwner(prevAddress, userToRemove, newThreshold).encodeABI()
|
||||
const text = name || userToRemove
|
||||
|
||||
return createTransaction(safe, `Remove Owner ${text}`, safeAddress, 0, nonce, executor, data)
|
||||
return createTransaction(safe, `Remove Owner ${text}`, safeAddress, '0', nonce, executor, data)
|
||||
}
|
||||
|
||||
class RemoveOwner extends React.Component<Props, State> {
|
||||
|
|
|
@ -72,10 +72,10 @@ const BalanceComponent = openHoc(({
|
|||
return (
|
||||
<ListItem key={symbol} className={classNames(classes.nested, symbol)}>
|
||||
<ListItemIcon>
|
||||
<Img src={token.get('logoUrl')} height={30} alt={name} />
|
||||
<Img src={token.get('logoUri')} height={30} alt={name} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={name} secondary={`${token.get('funds')} ${symbol}`} />
|
||||
<Button variant="raised" color="primary" onClick={onMoveFundsClick} disabled={disabled}>
|
||||
<Button variant="contained" color="primary" onClick={onMoveFundsClick} disabled={disabled}>
|
||||
{MOVE_FUNDS_BUTTON_TEXT}
|
||||
</Button>
|
||||
</ListItem>
|
||||
|
|
|
@ -24,7 +24,7 @@ const Confirmations = ({ confirmations, onEditThreshold }: Props) => (
|
|||
cut
|
||||
/>
|
||||
<Button
|
||||
variant="raised"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={onEditThreshold}
|
||||
>
|
||||
|
|
|
@ -22,7 +22,7 @@ const MultisigTransactionsComponent = ({ onSeeTxs }: Props) => {
|
|||
</Avatar>
|
||||
<ListItemText primary="Safe's Multisig Transaction" secondary={text} />
|
||||
<Button
|
||||
variant="raised"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={onSeeTxs}
|
||||
>
|
||||
|
|
|
@ -51,7 +51,7 @@ const Owners = openHoc(({
|
|||
}
|
||||
</ListItemIcon>
|
||||
<Button
|
||||
variant="raised"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={onAddOwner}
|
||||
>
|
||||
|
@ -73,10 +73,12 @@ const Owners = openHoc(({
|
|||
primary={owner.name}
|
||||
secondary={owner.address}
|
||||
/>
|
||||
{ !sameAddress(userAddress, owner.address) &&
|
||||
<IconButton aria-label="Delete" onClick={onRemoveIconClick}>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
{ !sameAddress(userAddress, owner.address)
|
||||
&& (
|
||||
<IconButton aria-label="Delete" onClick={onRemoveIconClick}>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
)
|
||||
}
|
||||
</ListItem>
|
||||
)
|
||||
|
|
|
@ -16,15 +16,14 @@ import selector, { type SelectorProps } from './selector'
|
|||
import SendTokenForm, { TKN_DESTINATION_PARAM, TKN_VALUE_PARAM } from './SendTokenForm'
|
||||
import ReviewTx from './ReviewTx'
|
||||
|
||||
const getSteps = () => [
|
||||
'Fill Move Token form', 'Review Move Token form',
|
||||
]
|
||||
const getSteps = () => ['Fill Move Token form', 'Review Move Token form']
|
||||
|
||||
type Props = SelectorProps & Actions & {
|
||||
safe: Safe,
|
||||
token: Token,
|
||||
onReset: () => void,
|
||||
}
|
||||
type Props = SelectorProps &
|
||||
Actions & {
|
||||
safe: Safe,
|
||||
token: Token,
|
||||
onReset: () => void,
|
||||
}
|
||||
|
||||
type State = {
|
||||
done: boolean,
|
||||
|
@ -36,21 +35,21 @@ const getTransferData = async (tokenAddress: string, to: string, amount: BigNumb
|
|||
const StandardToken = await getStandardTokenContract()
|
||||
const myToken = await StandardToken.at(tokenAddress)
|
||||
|
||||
return myToken.contract.transfer.getData(to, amount)
|
||||
return myToken.contract.transfer(to, amount).encodeABI()
|
||||
}
|
||||
|
||||
const processTokenTransfer = async (safe: Safe, token: Token, to: string, amount: number, userAddress: string) => {
|
||||
const processTokenTransfer = async (safe: Safe, token: Token, to: string, amount: string, userAddress: string) => {
|
||||
const safeAddress = safe.get('address')
|
||||
const gnosisSafe = await getSafeEthereumInstance(safeAddress)
|
||||
const nonce = await gnosisSafe.nonce()
|
||||
const symbol = token.get('symbol')
|
||||
const name = `Send ${amount} ${symbol} to ${to}`
|
||||
const value = isEther(symbol) ? amount : 0
|
||||
const value = isEther(symbol) ? amount : '0'
|
||||
const tokenAddress = token.get('address')
|
||||
const destination = isEther(symbol) ? to : tokenAddress
|
||||
const data = isEther(symbol)
|
||||
? EMPTY_DATA
|
||||
: await getTransferData(tokenAddress, to, await toNative(amount, token.get('decimals')))
|
||||
: await getTransferData(tokenAddress, to, toNative(amount, token.get('decimals')))
|
||||
|
||||
return createTransaction(safe, name, destination, value, nonce, userAddress, data)
|
||||
}
|
||||
|
@ -62,14 +61,16 @@ class SendToken extends React.Component<Props, State> {
|
|||
|
||||
onTransaction = async (values: Object) => {
|
||||
try {
|
||||
const { safe, token, userAddress } = this.props
|
||||
const {
|
||||
safe, token, userAddress, fetchTransactions,
|
||||
} = this.props
|
||||
|
||||
const amount = values[TKN_VALUE_PARAM]
|
||||
const destination = values[TKN_DESTINATION_PARAM]
|
||||
|
||||
await processTokenTransfer(safe, token, destination, amount, userAddress)
|
||||
await sleep(1500)
|
||||
this.props.fetchTransactions(safe.get('address'))
|
||||
fetchTransactions(safe.get('address'))
|
||||
this.setState({ done: true })
|
||||
} catch (error) {
|
||||
this.setState({ done: false })
|
||||
|
@ -79,8 +80,10 @@ class SendToken extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
onReset = () => {
|
||||
const { onReset } = this.props
|
||||
|
||||
this.setState({ done: false })
|
||||
this.props.onReset() // This is for show the TX list component
|
||||
onReset() // This is for show the TX list component
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -100,15 +103,16 @@ class SendToken extends React.Component<Props, State> {
|
|||
onReset={this.onReset}
|
||||
>
|
||||
<Stepper.Page funds={token.get('funds')} symbol={symbol}>
|
||||
{ SendTokenForm }
|
||||
</Stepper.Page>
|
||||
<Stepper.Page symbol={symbol}>
|
||||
{ ReviewTx }
|
||||
{SendTokenForm}
|
||||
</Stepper.Page>
|
||||
<Stepper.Page symbol={symbol}>{ReviewTx}</Stepper.Page>
|
||||
</Stepper>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(selector, actions)(SendToken)
|
||||
export default connect(
|
||||
selector,
|
||||
actions,
|
||||
)(SendToken)
|
||||
|
|
|
@ -32,14 +32,14 @@ class Threshold extends React.PureComponent<Props, State> {
|
|||
|
||||
onThreshold = async (values: Object) => {
|
||||
try {
|
||||
const { safe, userAddress } = this.props // , fetchThreshold } = this.props
|
||||
const { safe, userAddress, fetchTransactions } = this.props // , fetchThreshold } = this.props
|
||||
const newThreshold = values[THRESHOLD_PARAM]
|
||||
const safeAddress = safe.get('address')
|
||||
const gnosisSafe = await getSafeEthereumInstance(safeAddress)
|
||||
const nonce = await gnosisSafe.nonce()
|
||||
const data = gnosisSafe.contract.changeThreshold.getData(newThreshold)
|
||||
await createTransaction(safe, `Change Safe's threshold [${nonce}]`, safeAddress, 0, nonce, userAddress, data)
|
||||
await this.props.fetchTransactions(safeAddress)
|
||||
const data = gnosisSafe.contract.changeThreshold(newThreshold).encodeABI()
|
||||
await createTransaction(safe, `Change Safe's threshold [${nonce}]`, safeAddress, '0', nonce, userAddress, data)
|
||||
await fetchTransactions(safeAddress)
|
||||
this.setState({ done: true })
|
||||
} catch (error) {
|
||||
this.setState({ done: false })
|
||||
|
@ -49,8 +49,10 @@ class Threshold extends React.PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
onReset = () => {
|
||||
const { onReset } = this.props
|
||||
this.setState({ done: false })
|
||||
this.props.onReset()
|
||||
|
||||
onReset()
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -23,27 +23,41 @@ import { sameAddress } from '~/logic/wallets/ethAddresses'
|
|||
import { type Confirmation } from '~/routes/safe/store/model/confirmation'
|
||||
import selector, { type SelectorProps } from './selector'
|
||||
|
||||
type Props = Open & SelectorProps & {
|
||||
transaction: Transaction,
|
||||
safeName: string,
|
||||
threshold: number,
|
||||
onProcessTx: (tx: Transaction, alreadyConfirmed: number) => void,
|
||||
}
|
||||
type Props = Open &
|
||||
SelectorProps & {
|
||||
transaction: Transaction,
|
||||
safeName: string,
|
||||
threshold: number,
|
||||
onProcessTx: (tx: Transaction, alreadyConfirmed: number) => void,
|
||||
}
|
||||
|
||||
export const PROCESS_TXS = 'PROCESS TRANSACTION'
|
||||
|
||||
class GnoTransaction extends React.PureComponent<Props> {
|
||||
onProccesClick = () => this.props.onProcessTx(this.props.transaction, this.props.confirmed)
|
||||
onProccesClick = () => {
|
||||
const { onProcessTx, transaction, confirmed } = this.props
|
||||
|
||||
hasConfirmed = (userAddress: string, confirmations: List<Confirmation>): boolean =>
|
||||
confirmations.filter((conf: Confirmation) => sameAddress(userAddress, conf.get('owner').get('address')) && conf.get('type') === 'confirmation').count() > 0
|
||||
onProcessTx(transaction, confirmed)
|
||||
}
|
||||
|
||||
hasConfirmed = (userAddress: string, confirmations: List<Confirmation>): boolean => (
|
||||
confirmations
|
||||
.filter(
|
||||
(conf: Confirmation) => (
|
||||
sameAddress(userAddress, conf.get('owner').get('address')) && conf.get('type') === 'confirmation'
|
||||
),
|
||||
)
|
||||
.count() > 0
|
||||
)
|
||||
|
||||
render() {
|
||||
const {
|
||||
open, toggle, transaction, confirmed, safeName, userAddress, executionHash, threshold,
|
||||
} = this.props
|
||||
|
||||
const confirmationText = executionHash ? 'Already executed' : `${confirmed} of the ${threshold} confirmations needed`
|
||||
const confirmationText = executionHash
|
||||
? 'Already executed'
|
||||
: `${confirmed} of the ${threshold} confirmations needed`
|
||||
const userConfirmed = this.hasConfirmed(userAddress, transaction.get('confirmations'))
|
||||
|
||||
return (
|
||||
|
@ -63,45 +77,51 @@ class GnoTransaction extends React.PureComponent<Props> {
|
|||
</Avatar>
|
||||
<ListItemText primary="Status" secondary={confirmationText} />
|
||||
<ListItemIcon>
|
||||
{open
|
||||
? <IconButton disableRipple><ExpandLess /></IconButton>
|
||||
: <IconButton disableRipple><ExpandMore /></IconButton>
|
||||
}
|
||||
{open ? (
|
||||
<IconButton disableRipple>
|
||||
<ExpandLess />
|
||||
</IconButton>
|
||||
) : (
|
||||
<IconButton disableRipple>
|
||||
<ExpandMore />
|
||||
</IconButton>
|
||||
)}
|
||||
</ListItemIcon>
|
||||
</ListItem>
|
||||
</Row>
|
||||
<Row>
|
||||
<ListItem>
|
||||
{ executionHash &&
|
||||
{executionHash && (
|
||||
<React.Fragment>
|
||||
<Avatar><CompareArrows /></Avatar>
|
||||
<Avatar>
|
||||
<CompareArrows />
|
||||
</Avatar>
|
||||
<ListItemText cut primary="Transaction Hash" secondary={executionHash} />
|
||||
</React.Fragment>
|
||||
}
|
||||
{ !executionHash && userConfirmed &&
|
||||
)}
|
||||
{!executionHash && userConfirmed && (
|
||||
<React.Fragment>
|
||||
<Avatar><CompareArrows /></Avatar>
|
||||
<Avatar>
|
||||
<CompareArrows />
|
||||
</Avatar>
|
||||
<ListItemText cut primary="Confirmed" secondary="Waiting for the rest of confirmations" />
|
||||
</React.Fragment>
|
||||
}
|
||||
{ !executionHash && !userConfirmed &&
|
||||
<Button
|
||||
variant="raised"
|
||||
color="primary"
|
||||
onClick={this.onProccesClick}
|
||||
>
|
||||
)}
|
||||
{!executionHash && !userConfirmed && (
|
||||
<Button variant="contained" color="primary" onClick={this.onProccesClick}>
|
||||
{PROCESS_TXS}
|
||||
</Button>
|
||||
}
|
||||
)}
|
||||
</ListItem>
|
||||
</Row>
|
||||
{ open &&
|
||||
{open && (
|
||||
<Collapsed
|
||||
safeName={safeName}
|
||||
confirmations={transaction.get('confirmations')}
|
||||
destination={transaction.get('destination')}
|
||||
threshold={threshold}
|
||||
/> }
|
||||
/>
|
||||
)}
|
||||
<Hairline margin="md" />
|
||||
</React.Fragment>
|
||||
)
|
||||
|
|
|
@ -19,7 +19,7 @@ const buildOwnersFrom = (safeOwners: string[], storedOwners: Map<string, string>
|
|||
export const buildSafe = async (safeAddress: string, safeName: string) => {
|
||||
const web3 = getWeb3()
|
||||
const GnosisSafe = await getGnosisSafeContract(web3)
|
||||
const gnosisSafe = GnosisSafe.at(safeAddress)
|
||||
const gnosisSafe = await GnosisSafe.at(safeAddress)
|
||||
|
||||
const threshold = Number(await gnosisSafe.getThreshold())
|
||||
const owners = List(buildOwnersFrom(await gnosisSafe.getOwners(), getOwners(safeAddress)))
|
||||
|
@ -42,7 +42,7 @@ export default (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalSta
|
|||
return dispatch(updateSafe(safeRecord))
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.log("Error while updating safe information")
|
||||
console.error("Error while updating safe information: ", err)
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@ import { List } from 'immutable'
|
|||
import * as React from 'react'
|
||||
import Button from '~/components/layout/Button'
|
||||
import Link from '~/components/layout/Link'
|
||||
import Table, { TableBody, TableCell, TableHead, TableRow } from '~/components/layout/Table'
|
||||
import Table, {
|
||||
TableBody, TableCell, TableHead, TableRow,
|
||||
} from '~/components/layout/Table'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { SAFELIST_ADDRESS } from '~/routes/routes'
|
||||
|
||||
|
@ -17,8 +19,8 @@ const SafeTable = ({ safes }: Props) => (
|
|||
<TableCell>Open</TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Deployed Address</TableCell>
|
||||
<TableCell numeric>Confirmations</TableCell>
|
||||
<TableCell numeric>Number of owners</TableCell>
|
||||
<TableCell align="right">Confirmations</TableCell>
|
||||
<TableCell align="right">Number of owners</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
|
@ -26,13 +28,13 @@ const SafeTable = ({ safes }: Props) => (
|
|||
<TableRow key={safe.address}>
|
||||
<TableCell>
|
||||
<Link to={`${SAFELIST_ADDRESS}/${safe.address}`}>
|
||||
<Button variant="raised" size="small" color="primary">Open</Button>
|
||||
<Button variant="contained" size="small" color="primary">Open</Button>
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell padding="none">{safe.get('name')}</TableCell>
|
||||
<TableCell padding="none">{safe.get('address')}</TableCell>
|
||||
<TableCell padding="none" numeric>{safe.get('threshold')}</TableCell>
|
||||
<TableCell padding="none" numeric>{safe.get('owners').count()}</TableCell>
|
||||
<TableCell padding="none" align="right">{safe.get('threshold')}</TableCell>
|
||||
<TableCell padding="none" align="right">{safe.get('owners').count()}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
|
|
|
@ -5,7 +5,6 @@ import TextField from '~/components/forms/TextField'
|
|||
import { composeValidators, required, mustBeEthereumAddress, uniqueAddress } from '~/components/forms/validator'
|
||||
import Block from '~/components/layout/Block'
|
||||
import Heading from '~/components/layout/Heading'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
||||
import { getStandardTokenContract } from '~/routes/tokens/store/actions/fetchTokens'
|
||||
|
@ -17,7 +16,7 @@ type Props = {
|
|||
export const TOKEN_ADRESS_PARAM = 'tokenAddress'
|
||||
|
||||
export const token = async (tokenAddress: string) => {
|
||||
const code = await promisify(cb => getWeb3().eth.getCode(tokenAddress, cb))
|
||||
const code = await getWeb3().eth.getCode(tokenAddress)
|
||||
const isDeployed = code !== EMPTY_DATA
|
||||
|
||||
if (!isDeployed) {
|
||||
|
|
|
@ -3,11 +3,12 @@ import * as React from 'react'
|
|||
import Stepper from '~/components/Stepper'
|
||||
import { getHumanFriendlyToken } from '~/routes/tokens/store/actions/fetchTokens'
|
||||
import FirstPage, { TOKEN_ADRESS_PARAM } from '~/routes/tokens/component/AddToken/FirstPage'
|
||||
import SecondPage, { TOKEN_SYMBOL_PARAM, TOKEN_DECIMALS_PARAM, TOKEN_LOGO_URL_PARAM, TOKEN_NAME_PARAM } from '~/routes/tokens/component/AddToken/SecondPage'
|
||||
import SecondPage, {
|
||||
TOKEN_SYMBOL_PARAM, TOKEN_DECIMALS_PARAM, TOKEN_LOGO_URL_PARAM, TOKEN_NAME_PARAM,
|
||||
} from '~/routes/tokens/component/AddToken/SecondPage'
|
||||
import { makeToken, type Token } from '~/routes/tokens/store/model/token'
|
||||
import addTokenAction from '~/routes/tokens/store/actions/addToken'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
||||
import Review from './Review'
|
||||
|
||||
|
@ -39,7 +40,7 @@ export const addTokenFnc = async (values: Object, addToken: typeof addTokenActio
|
|||
name,
|
||||
symbol,
|
||||
decimals: Number(decimals),
|
||||
logoUrl: logo,
|
||||
logoUri: logo,
|
||||
status: true,
|
||||
removable: true,
|
||||
})
|
||||
|
@ -69,17 +70,18 @@ class AddToken extends React.Component<Props, State> {
|
|||
const tokenAddress = values[TOKEN_ADRESS_PARAM]
|
||||
const erc20Token = await getHumanFriendlyToken()
|
||||
const instance = await erc20Token.at(tokenAddress)
|
||||
const web3 = getWeb3()
|
||||
|
||||
const dataName = await instance.contract.name.getData()
|
||||
const nameResult = await promisify(cb => getWeb3().eth.call({ to: tokenAddress, data: dataName }, cb))
|
||||
const dataName = await instance.contract.methods.name().encodeABI()
|
||||
const nameResult = await web3.eth.call({ to: tokenAddress, data: dataName })
|
||||
const hasName = nameResult !== EMPTY_DATA
|
||||
|
||||
const dataSymbol = await instance.contract.symbol.getData()
|
||||
const symbolResult = await promisify(cb => getWeb3().eth.call({ to: tokenAddress, data: dataSymbol }, cb))
|
||||
const dataSymbol = await instance.contract.methods.symbol().encodeABI()
|
||||
const symbolResult = await web3.eth.call({ to: tokenAddress, data: dataSymbol })
|
||||
const hasSymbol = symbolResult !== EMPTY_DATA
|
||||
|
||||
const dataDecimals = await instance.contract.decimals.getData()
|
||||
const decimalsResult = await promisify(cb => getWeb3().eth.call({ to: tokenAddress, data: dataDecimals }, cb))
|
||||
const dataDecimals = await instance.contract.methods.decimals().encodeABI()
|
||||
const decimalsResult = await web3.eth.call({ to: tokenAddress, data: dataDecimals })
|
||||
const hasDecimals = decimalsResult !== EMPTY_DATA
|
||||
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@ class TokenComponent extends React.PureComponent<Props, State> {
|
|||
|
||||
render() {
|
||||
const { classes, token } = this.props
|
||||
const { checked } = this.state
|
||||
const name = token.get('name')
|
||||
const symbol = token.get('symbol')
|
||||
const disabled = isEther(symbol)
|
||||
|
@ -70,22 +71,24 @@ class TokenComponent extends React.PureComponent<Props, State> {
|
|||
<Typography variant="subheading" color="textSecondary">
|
||||
<Checkbox
|
||||
disabled={disabled}
|
||||
checked={!!this.state.checked}
|
||||
checked={!!checked}
|
||||
onChange={this.handleChange}
|
||||
color="primary"
|
||||
/>
|
||||
{symbol}
|
||||
{ token.get('removable') &&
|
||||
<IconButton aria-label="Delete" onClick={this.onRemoveClick}>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
{ token.get('removable')
|
||||
&& (
|
||||
<IconButton aria-label="Delete" onClick={this.onRemoveClick}>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
)
|
||||
}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Block>
|
||||
<CardMedia
|
||||
className={classes.cover}
|
||||
image={token.get('logoUrl')}
|
||||
image={token.get('logoUri')}
|
||||
title={name}
|
||||
/>
|
||||
</Card>
|
||||
|
|
|
@ -13,10 +13,10 @@ type Props = Actions & SelectorProps & {
|
|||
|
||||
class TokensView extends React.PureComponent<Props> {
|
||||
componentDidUpdate() {
|
||||
const { safeAddress } = this.props
|
||||
const { safeAddress, tokens, fetchTokens: loadTokens } = this.props
|
||||
|
||||
if (this.props.tokens.count() === 0) {
|
||||
this.props.fetchTokens(safeAddress)
|
||||
if (tokens.count() === 0) {
|
||||
loadTokens(safeAddress)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { List, Map } from 'immutable'
|
||||
import contract from 'truffle-contract'
|
||||
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||
import StandardToken from '@gnosis.pm/util-contracts/build/contracts/StandardToken.json'
|
||||
import StandardToken from '@gnosis.pm/util-contracts/build/contracts/GnosisStandardToken.json'
|
||||
import HumanFriendlyToken from '@gnosis.pm/util-contracts/build/contracts/HumanFriendlyToken.json'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { type GlobalState } from '~/store/index'
|
||||
|
@ -12,6 +12,7 @@ import { getActiveTokenAddresses, getTokens } from '~/utils/localStorage/tokens'
|
|||
import { getSafeEthToken } from '~/utils/tokens'
|
||||
import { enhancedFetch } from '~/utils/fetch'
|
||||
import addTokens from './addTokens'
|
||||
import { getRelayUrl } from '~/config/index'
|
||||
|
||||
const createStandardTokenContract = async () => {
|
||||
const web3 = getWeb3()
|
||||
|
@ -34,52 +35,66 @@ export const getStandardTokenContract = ensureOnce(createStandardTokenContract)
|
|||
|
||||
export const calculateBalanceOf = async (tokenAddress: string, address: string, decimals: number) => {
|
||||
const erc20Token = await getStandardTokenContract()
|
||||
const web3 = getWeb3()
|
||||
let balance = 0
|
||||
|
||||
return erc20Token.at(tokenAddress)
|
||||
.then(instance => instance.balanceOf(address).then(funds => funds.div(10 ** decimals).toString()))
|
||||
.catch(() => '0')
|
||||
try {
|
||||
const token = await erc20Token.at(tokenAddress)
|
||||
balance = await token.balanceOf(address)
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch token balances: ', err)
|
||||
}
|
||||
|
||||
return web3.utils
|
||||
.toBN(balance)
|
||||
.div(web3.utils.toBN(10 ** decimals))
|
||||
.toString()
|
||||
}
|
||||
|
||||
export const fetchTokensData = async () => {
|
||||
const url = 'https://gist.githubusercontent.com/rmeissner/98911fcf74b0ea9731e2dae2441c97a4/raw/'
|
||||
const apiUrl = getRelayUrl()
|
||||
const url = `${apiUrl}/tokens`
|
||||
const errMsg = 'Error querying safe balances'
|
||||
return enhancedFetch(url, errMsg)
|
||||
}
|
||||
|
||||
export const fetchTokens = (safeAddress: string) =>
|
||||
async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
const tokens: List<string> = getActiveTokenAddresses(safeAddress)
|
||||
const ethBalance = await getSafeEthToken(safeAddress)
|
||||
const customTokens = getTokens(safeAddress)
|
||||
const json = await exports.fetchTokensData()
|
||||
export const fetchTokens = (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
const tokens: List<string> = getActiveTokenAddresses(safeAddress)
|
||||
const ethBalance = await getSafeEthToken(safeAddress)
|
||||
const customTokens = getTokens(safeAddress)
|
||||
const { results } = await fetchTokensData()
|
||||
|
||||
try {
|
||||
const balancesRecords = await Promise.all(json.map(async (item: TokenProps) => {
|
||||
try {
|
||||
const balancesRecords = await Promise.all(
|
||||
results.map(async (item: TokenProps) => {
|
||||
const status = tokens.includes(item.address)
|
||||
const funds = status ? await calculateBalanceOf(item.address, safeAddress, item.decimals) : '0'
|
||||
|
||||
return makeToken({ ...item, status, funds })
|
||||
}))
|
||||
}),
|
||||
)
|
||||
|
||||
const customTokenRecords = await Promise.all(customTokens.map(async (item: TokenProps) => {
|
||||
const customTokenRecords = await Promise.all(
|
||||
customTokens.map(async (item: TokenProps) => {
|
||||
const status = tokens.includes(item.address)
|
||||
const funds = status ? await calculateBalanceOf(item.address, safeAddress, item.decimals) : '0'
|
||||
|
||||
return makeToken({ ...item, status, funds })
|
||||
}))
|
||||
}),
|
||||
)
|
||||
|
||||
const balances: Map<string, Token> = Map().withMutations((map) => {
|
||||
balancesRecords.forEach(record => map.set(record.get('address'), record))
|
||||
customTokenRecords.forEach(record => map.set(record.get('address'), record))
|
||||
const balances: Map<string, Token> = Map().withMutations((map) => {
|
||||
balancesRecords.forEach(record => map.set(record.get('address'), record))
|
||||
customTokenRecords.forEach(record => map.set(record.get('address'), record))
|
||||
|
||||
map.set(ethBalance.get('address'), ethBalance)
|
||||
})
|
||||
map.set(ethBalance.get('address'), ethBalance)
|
||||
})
|
||||
|
||||
return dispatch(addTokens(safeAddress, balances))
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.log("Error fetching token balances... " + err)
|
||||
return dispatch(addTokens(safeAddress, balances))
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.log('Error fetching tokens... ' + err)
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ export type TokenProps = {
|
|||
name: string,
|
||||
symbol: string,
|
||||
decimals: number,
|
||||
logoUrl: string,
|
||||
logoUri: string,
|
||||
funds: string,
|
||||
status: boolean,
|
||||
removable: boolean,
|
||||
|
@ -18,7 +18,7 @@ export const makeToken: RecordFactory<TokenProps> = Record({
|
|||
name: '',
|
||||
symbol: '',
|
||||
decimals: 0,
|
||||
logoUrl: '',
|
||||
logoUri: '',
|
||||
funds: '0',
|
||||
status: true,
|
||||
removable: false,
|
||||
|
|
|
@ -13,7 +13,7 @@ const safe = require('../assets/safe.svg')
|
|||
const plus = require('../assets/new.svg')
|
||||
|
||||
type Props = {
|
||||
provider: string
|
||||
provider: string,
|
||||
}
|
||||
|
||||
type SafeProps = {
|
||||
|
@ -29,7 +29,7 @@ export const CreateSafe = ({ size, provider }: SafeProps) => (
|
|||
<Button
|
||||
component={Link}
|
||||
to={OPEN_ADDRESS}
|
||||
variant="raised"
|
||||
variant="contained"
|
||||
size={size || 'medium'}
|
||||
color="primary"
|
||||
disabled={!provider}
|
||||
|
@ -55,17 +55,20 @@ export const LoadSafe = ({ size, provider }: SafeProps) => (
|
|||
</Button>
|
||||
)
|
||||
|
||||
|
||||
const Welcome = ({ provider }: Props) => (
|
||||
<Block className={styles.safe}>
|
||||
<Heading tag="h1" align="center" margin="lg">
|
||||
Welcome to the Gnosis <br />
|
||||
Welcome to the Gnosis
|
||||
<br />
|
||||
Safe Team Edition
|
||||
</Heading>
|
||||
<Heading tag="h4" align="center" margin="xl">
|
||||
The Gnosis Safe Team Edition is geared towards teams managing <br />
|
||||
shared crypto funds. It is an improvement of the existing Gnosis <br />
|
||||
MultiSig wallet with redesigned smart contracts, cheaper setup and <br />
|
||||
The Gnosis Safe Team Edition is geared towards teams managing
|
||||
<br />
|
||||
shared crypto funds. It is an improvement of the existing Gnosis
|
||||
<br />
|
||||
MultiSig wallet with redesigned smart contracts, cheaper setup and
|
||||
<br />
|
||||
transaction costs as well as an enhanced user experience.
|
||||
</Heading>
|
||||
<Block className={styles.safeActions} margin="md">
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
// @flow
|
||||
import { createBrowserHistory } from 'history'
|
||||
import { routerMiddleware, routerReducer } from 'react-router-redux'
|
||||
import { connectRouter, routerMiddleware } from 'connected-react-router'
|
||||
import { combineReducers, createStore, applyMiddleware, compose, type Reducer, type Store } from 'redux'
|
||||
import thunk from 'redux-thunk'
|
||||
import provider, { PROVIDER_REDUCER_ID, type State as ProviderState } from '~/logic/wallets/store/reducer/provider'
|
||||
import safe, { SAFE_REDUCER_ID, type State as SafeState, safesInitialState } from '~/routes/safe/store/reducer/safe'
|
||||
import tokens, { TOKEN_REDUCER_ID, type State as TokensState } from '~/routes/tokens/store/reducer/tokens'
|
||||
import transactions, { type State as TransactionsState, TRANSACTIONS_REDUCER_ID } from '~/routes/safe/store/reducer/transactions'
|
||||
import transactions, {
|
||||
type State as TransactionsState,
|
||||
TRANSACTIONS_REDUCER_ID,
|
||||
} from '~/routes/safe/store/reducer/transactions'
|
||||
|
||||
export const history = createBrowserHistory()
|
||||
|
||||
// eslint-disable-next-line
|
||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
|
||||
const finalCreateStore = composeEnhancers(applyMiddleware(
|
||||
thunk,
|
||||
routerMiddleware(history),
|
||||
))
|
||||
const finalCreateStore = composeEnhancers(applyMiddleware(thunk, routerMiddleware(history)))
|
||||
|
||||
export type GlobalState = {
|
||||
providers: ProviderState,
|
||||
|
@ -27,7 +27,7 @@ export type GlobalState = {
|
|||
export type GetState = () => GlobalState
|
||||
|
||||
const reducers: Reducer<GlobalState> = combineReducers({
|
||||
routing: routerReducer,
|
||||
router: connectRouter(history),
|
||||
[PROVIDER_REDUCER_ID]: provider,
|
||||
[SAFE_REDUCER_ID]: safe,
|
||||
[TOKEN_REDUCER_ID]: tokens,
|
||||
|
|
|
@ -5,7 +5,6 @@ import SafeView from '~/routes/safe/component/Safe'
|
|||
import { aNewStore, type GlobalState } from '~/store'
|
||||
import { sleep } from '~/utils/timer'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
import { addEtherTo } from '~/test/utils/tokenMovements'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
import { travelToSafe } from '~/test/builder/safe.dom.utils'
|
||||
|
@ -19,19 +18,17 @@ export type DomSafe = {
|
|||
store: Store<GlobalState>,
|
||||
}
|
||||
|
||||
export const filterMoveButtonsFrom = (buttons: Element[]) =>
|
||||
buttons.filter(button => button.getElementsByTagName('span')[0].innerHTML !== MOVE_FUNDS_BUTTON_TEXT)
|
||||
export const filterMoveButtonsFrom = (buttons: Element[]) => buttons.filter(
|
||||
(button: Element): boolean => button.getElementsByTagName('span')[0].textContent !== MOVE_FUNDS_BUTTON_TEXT,
|
||||
)
|
||||
|
||||
export const renderSafeInDom = async (
|
||||
owners: number = 1,
|
||||
threshold: number = 1,
|
||||
): Promise<DomSafe> => {
|
||||
export const renderSafeInDom = async (owners: number = 1, threshold: number = 1): Promise<DomSafe> => {
|
||||
// create store
|
||||
const store = aNewStore()
|
||||
// deploy safe updating store
|
||||
const address = await aMinedSafe(store, owners, threshold)
|
||||
// have available accounts
|
||||
const accounts = await promisify(cb => getWeb3().eth.getAccounts(cb))
|
||||
const accounts = await getWeb3().eth.getAccounts()
|
||||
// navigate to SAFE route
|
||||
const SafeDom = travelToSafe(store, address)
|
||||
|
||||
|
@ -47,6 +44,10 @@ export const renderSafeInDom = async (
|
|||
const filteredButtons = filterMoveButtonsFrom(buttons)
|
||||
|
||||
return {
|
||||
address, safeButtons: filteredButtons, safe: SafeDom, accounts, store,
|
||||
address,
|
||||
safeButtons: filteredButtons,
|
||||
safe: SafeDom,
|
||||
accounts,
|
||||
store,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import { SEE_MULTISIG_BUTTON_TEXT } from '~/routes/safe/component/Safe/MultisigT
|
|||
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
|
||||
import { sleep } from '~/utils/timer'
|
||||
import { Provider } from 'react-redux'
|
||||
import { ConnectedRouter } from 'react-router-redux'
|
||||
import { ConnectedRouter } from 'connected-react-router'
|
||||
import AppRoutes from '~/routes'
|
||||
import { SAFELIST_ADDRESS, SETTINS_ADDRESS } from '~/routes/routes'
|
||||
import { history, type GlobalState } from '~/store'
|
||||
|
@ -125,8 +125,8 @@ export const whenSafeDeployed = (): Promise<string> => new Promise((resolve, rej
|
|||
clearInterval(interval)
|
||||
reject()
|
||||
}
|
||||
|
||||
const url = `${window.location}`
|
||||
console.log(url)
|
||||
const regex = /.*safes\/(0x[a-f0-9A-F]*)/
|
||||
const safeAddress = url.match(regex)
|
||||
if (safeAddress) {
|
||||
|
|
|
@ -3,7 +3,6 @@ import { makeSafe, type Safe } from '~/routes/safe/store/model/safe'
|
|||
import addSafe, { buildOwnersFrom } from '~/routes/safe/store/actions/addSafe'
|
||||
import { FIELD_NAME, FIELD_CONFIRMATIONS, FIELD_OWNERS, getOwnerNameBy, getOwnerAddressBy } from '~/routes/open/components/fields'
|
||||
import { getWeb3, getProviderInfo } from '~/logic/wallets/getWeb3'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
import { createSafe, type OpenState } from '~/routes/open/container/Open'
|
||||
import { type GlobalState } from '~/store/index'
|
||||
import { makeProvider } from '~/logic/wallets/store/model/provider'
|
||||
|
@ -72,7 +71,7 @@ export const aMinedSafe = async (
|
|||
const walletRecord = makeProvider(provider)
|
||||
store.dispatch(addProvider(walletRecord))
|
||||
|
||||
const accounts = await promisify(cb => getWeb3().eth.getAccounts(cb))
|
||||
const accounts = await getWeb3().eth.getAccounts()
|
||||
const form = {
|
||||
[FIELD_NAME]: 'Safe Name',
|
||||
[FIELD_CONFIRMATIONS]: `${threshold}`,
|
||||
|
|
|
@ -26,4 +26,3 @@ export const testToken = (token: Token | typeof undefined, symbol: string, statu
|
|||
expect(token.get('funds')).toBe(funds)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import { type Store } from 'redux'
|
|||
import TestUtils from 'react-dom/test-utils'
|
||||
import Select from '@material-ui/core/Select'
|
||||
import { Provider } from 'react-redux'
|
||||
import { ConnectedRouter } from 'react-router-redux'
|
||||
import { ConnectedRouter } from 'connected-react-router'
|
||||
import { ADD_OWNER_BUTTON } from '~/routes/open/components/SafeOwnersForm'
|
||||
import Open from '~/routes/open/container/Open'
|
||||
import { aNewStore, history, type GlobalState } from '~/store'
|
||||
|
@ -12,7 +12,6 @@ import { sleep } from '~/utils/timer'
|
|||
import { getProviderInfo, getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import addProvider from '~/logic/wallets/store/actions/addProvider'
|
||||
import { makeProvider } from '~/logic/wallets/store/model/provider'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
||||
import { whenSafeDeployed } from './builder/safe.dom.utils'
|
||||
|
||||
|
@ -34,7 +33,7 @@ const fillOpenSafeForm = async (localStore: Store<GlobalState>) => {
|
|||
|
||||
const deploySafe = async (safe: React$Component<{}>, threshold: number, numOwners: number) => {
|
||||
const web3 = getWeb3()
|
||||
const accounts = await promisify(cb => web3.eth.getAccounts(cb))
|
||||
const accounts = await web3.eth.getAccounts()
|
||||
|
||||
expect(threshold).toBeLessThanOrEqual(numOwners)
|
||||
const form = TestUtils.findRenderedDOMComponentWithTag(safe, 'form')
|
||||
|
@ -51,7 +50,7 @@ const deploySafe = async (safe: React$Component<{}>, threshold: number, numOwner
|
|||
const addedUpfront = 1
|
||||
const buttons = TestUtils.scryRenderedDOMComponentsWithTag(safe, 'button')
|
||||
const addOwnerButton = buttons[1]
|
||||
expect(addOwnerButton.getElementsByTagName('span')[0].innerHTML).toEqual(ADD_OWNER_BUTTON)
|
||||
expect(addOwnerButton.getElementsByTagName('span')[0].textContent).toEqual(ADD_OWNER_BUTTON)
|
||||
for (let i = addedUpfront; i < numOwners; i += 1) {
|
||||
TestUtils.Simulate.click(addOwnerButton)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import * as React from 'react'
|
|||
import { type Store } from 'redux'
|
||||
import TestUtils from 'react-dom/test-utils'
|
||||
import { Provider } from 'react-redux'
|
||||
import { ConnectedRouter } from 'react-router-redux'
|
||||
import { ConnectedRouter } from 'connected-react-router'
|
||||
import Load from '~/routes/load/container/Load'
|
||||
import { aNewStore, history, type GlobalState } from '~/store'
|
||||
import { sleep } from '~/utils/timer'
|
||||
|
|
|
@ -6,7 +6,6 @@ import { aNewStore } from '~/store'
|
|||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
import { addTknTo, getFirstTokenContract } from '~/test/utils/tokenMovements'
|
||||
import { EXPAND_BALANCE_INDEX, travelToSafe } from '~/test/builder/safe.dom.utils'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { sendMoveTokensForm, dispatchTknBalance } from '~/test/utils/transactions/moveTokens.helper'
|
||||
import { sleep } from '~/utils/timer'
|
||||
|
@ -18,12 +17,12 @@ describe('DOM > Feature > SAFE ERC20 TOKENS', () => {
|
|||
beforeEach(async () => {
|
||||
store = aNewStore()
|
||||
safeAddress = await aMinedSafe(store)
|
||||
accounts = await promisify(cb => getWeb3().eth.getAccounts(cb))
|
||||
accounts = await getWeb3().eth.getAccounts()
|
||||
})
|
||||
|
||||
it('sends ERC20 tokens', async () => {
|
||||
// GIVEN
|
||||
const numTokens = 100
|
||||
const numTokens = '100'
|
||||
const tokenAddress = await addTknTo(safeAddress, numTokens)
|
||||
|
||||
await dispatchTknBalance(store, tokenAddress, safeAddress)
|
||||
|
|
|
@ -18,18 +18,6 @@ describe('Safe - redux balance property', () => {
|
|||
})
|
||||
|
||||
it('reducer should return 0 to just deployed safe', async () => {
|
||||
// GIVEN
|
||||
const tokenList = [
|
||||
'0x975be7f72cea31fd83d0cb2a197f9136f38696b7', // WE
|
||||
'0xb3a4bc89d8517e0e2c9b66703d09d3029ffa1e6d', // <3
|
||||
'0x5f92161588c6178130ede8cbdc181acec66a9731', // GNO
|
||||
'0xb63d06025d580a94d59801f2513f5d309c079559', // OMG
|
||||
'0x3615757011112560521536258c1E7325Ae3b48AE', // RDN
|
||||
'0xc778417E063141139Fce010982780140Aa0cD5Ab', // Wrapped Ether
|
||||
'0x979861dF79C7408553aAF20c01Cfb3f81CCf9341', // OLY
|
||||
'0', // ETH
|
||||
]
|
||||
|
||||
// WHEN
|
||||
await store.dispatch(fetchTokensAction.fetchTokens(address))
|
||||
|
||||
|
@ -38,14 +26,16 @@ describe('Safe - redux balance property', () => {
|
|||
if (!tokens) throw new Error()
|
||||
|
||||
const safeBalances: Map<string, Token> | typeof undefined = tokens.get(address)
|
||||
if (!safeBalances) throw new Error()
|
||||
expect(safeBalances.size).toBe(8)
|
||||
if (!safeBalances) throw new Error('No tokens available, probably failed to fetch')
|
||||
expect(safeBalances.size).toBe(11)
|
||||
|
||||
tokenList.forEach((token: string) => {
|
||||
const record = safeBalances.get(token)
|
||||
if (!record) throw new Error()
|
||||
expect(record.get('funds')).toBe('0')
|
||||
})
|
||||
console.log(safeBalances.entries())
|
||||
|
||||
// safeBalances.forEach((token: string) => {
|
||||
// const record = safeBalances.get(token)
|
||||
// if (!record) throw new Error()
|
||||
// expect(record.get('funds')).toBe('0')
|
||||
// })
|
||||
})
|
||||
|
||||
it('reducer should return 0.03456 ETH as funds to safe with 0.03456 ETH', async () => {
|
||||
|
@ -59,7 +49,7 @@ describe('Safe - redux balance property', () => {
|
|||
|
||||
const safeBalances: Map<string, Token> | typeof undefined = tokens.get(address)
|
||||
if (!safeBalances) throw new Error()
|
||||
expect(safeBalances.size).toBe(8)
|
||||
expect(safeBalances.size).toBe(11)
|
||||
|
||||
const ethBalance = safeBalances.get(ETH_ADDRESS)
|
||||
if (!ethBalance) throw new Error()
|
||||
|
@ -68,7 +58,7 @@ describe('Safe - redux balance property', () => {
|
|||
|
||||
it('reducer should return 100 TKN when safe has 100 TKN', async () => {
|
||||
// GIVEN
|
||||
const numTokens = 100
|
||||
const numTokens = '100'
|
||||
const tokenAddress = await addTknTo(address, numTokens)
|
||||
|
||||
// WHEN
|
||||
|
|
|
@ -8,7 +8,6 @@ import { loadSafe } from '~/routes/load/container/Load'
|
|||
import { safesMapSelector } from '~/routes/safeList/store/selectors'
|
||||
import { makeOwner, type Owner } from '~/routes/safe/store/model/owner'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
import { safesInitialState } from '~/routes/safe/store/reducer/safe'
|
||||
import { setOwners, OWNERS_KEY } from '~/utils/localStorage'
|
||||
|
||||
|
@ -20,7 +19,7 @@ describe('Safe - redux load safe', () => {
|
|||
store = aNewStore()
|
||||
address = await aMinedSafe(store)
|
||||
localStorage.clear()
|
||||
accounts = await promisify(cb => getWeb3().eth.getAccounts(cb))
|
||||
accounts = await getWeb3().eth.getAccounts()
|
||||
})
|
||||
|
||||
it('if safe is not present, store and persist it with default names', async () => {
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import { List } from 'immutable'
|
||||
import { aNewStore } from '~/store'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
import { confirmationsTransactionSelector, safeTransactionsSelector } from '~/routes/safe/store/selectors'
|
||||
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
|
@ -103,7 +102,7 @@ describe('React DOM TESTS > Add and remove owners', () => {
|
|||
const threshold = 1
|
||||
const store = aNewStore()
|
||||
const address = await aMinedSafe(store, numOwners, threshold)
|
||||
const accounts = await promisify(cb => getWeb3().eth.getAccounts(cb))
|
||||
const accounts = await getWeb3().eth.getAccounts()
|
||||
const gnosisSafe = await getGnosisSafeInstanceAt(address)
|
||||
|
||||
const values = {
|
||||
|
@ -132,7 +131,7 @@ describe('React DOM TESTS > Add and remove owners', () => {
|
|||
const threshold = 1
|
||||
const store = aNewStore()
|
||||
const address = await aMinedSafe(store, numOwners, threshold)
|
||||
const accounts = await promisify(cb => getWeb3().eth.getAccounts(cb))
|
||||
const accounts = await getWeb3().eth.getAccounts()
|
||||
const gnosisSafe = await getGnosisSafeInstanceAt(address)
|
||||
|
||||
const values = {
|
||||
|
@ -164,7 +163,7 @@ describe('React DOM TESTS > Add and remove owners', () => {
|
|||
const threshold = 2
|
||||
const store = aNewStore()
|
||||
const address = await aMinedSafe(store, numOwners, threshold)
|
||||
const accounts = await promisify(cb => getWeb3().eth.getAccounts(cb))
|
||||
const accounts = await getWeb3().eth.getAccounts()
|
||||
const gnosisSafe = await getGnosisSafeInstanceAt(address)
|
||||
|
||||
const decrease = shouldDecrease(numOwners, threshold)
|
||||
|
@ -190,7 +189,7 @@ describe('React DOM TESTS > Add and remove owners', () => {
|
|||
const threshold = 2
|
||||
const store = aNewStore()
|
||||
const address = await aMinedSafe(store, numOwners, threshold)
|
||||
const accounts = await promisify(cb => getWeb3().eth.getAccounts(cb))
|
||||
const accounts = await getWeb3().eth.getAccounts()
|
||||
const gnosisSafe = await getGnosisSafeInstanceAt(address)
|
||||
|
||||
const decrease = true
|
||||
|
@ -215,7 +214,7 @@ describe('React DOM TESTS > Add and remove owners', () => {
|
|||
const threshold = 2
|
||||
const store = aNewStore()
|
||||
const address = await aMinedSafe(store, numOwners, threshold)
|
||||
const accounts = await promisify(cb => getWeb3().eth.getAccounts(cb))
|
||||
const accounts = await getWeb3().eth.getAccounts()
|
||||
const gnosisSafe = await getGnosisSafeInstanceAt(address)
|
||||
|
||||
const decrease = shouldDecrease(numOwners, threshold)
|
||||
|
|
|
@ -8,7 +8,6 @@ import { makeConfirmation } from '~/routes/safe/store/model/confirmation'
|
|||
import { aNewStore } from '~/store'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
import { getSafeFrom } from '~/test/utils/safeHelper'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { safeTransactionsSelector } from '~/routes/safe/store/selectors'
|
||||
import fetchSafe from '~/routes/safe/store/actions/fetchSafe'
|
||||
|
@ -20,7 +19,7 @@ describe('Transactions Suite', () => {
|
|||
let safeAddress: string
|
||||
let accounts: string[]
|
||||
beforeAll(async () => {
|
||||
accounts = await promisify(cb => getWeb3().eth.getAccounts(cb))
|
||||
accounts = await getWeb3().eth.getAccounts()
|
||||
})
|
||||
beforeEach(async () => {
|
||||
localStorage.clear()
|
||||
|
@ -32,15 +31,15 @@ describe('Transactions Suite', () => {
|
|||
it('retrieves tx info from service having subject available', async () => {
|
||||
let safe: Safe = getSafeFrom(store.getState(), safeAddress)
|
||||
const gnosisSafe = await getSafeEthereumInstance(safeAddress)
|
||||
const firstTxData = gnosisSafe.contract.addOwnerWithThreshold.getData(accounts[1], 2)
|
||||
const firstTxData = gnosisSafe.contract.methods.addOwnerWithThreshold(accounts[1], 2).encodeABI()
|
||||
const executor = accounts[0]
|
||||
const nonce = await gnosisSafe.nonce()
|
||||
const firstTxHash = await createTransaction(safe, 'Add Owner Second account', safeAddress, 0, nonce, executor, firstTxData)
|
||||
const firstTxHash = await createTransaction(safe, 'Add Owner Second account', safeAddress, '0', nonce, executor, firstTxData)
|
||||
await store.dispatch(fetchSafe(safe.get('address')))
|
||||
safe = getSafeFrom(store.getState(), safeAddress)
|
||||
|
||||
const secondTxData = gnosisSafe.contract.addOwnerWithThreshold.getData(accounts[2], 2)
|
||||
const secondTxHash = await createTransaction(safe, 'Add Owner Third account', safeAddress, 0, nonce + 100, executor, secondTxData)
|
||||
const secondTxData = gnosisSafe.contract.methods.addOwnerWithThreshold(accounts[2], 2).encodeABI()
|
||||
const secondTxHash = await createTransaction(safe, 'Add Owner Third account', safeAddress, '0', nonce + 100, executor, secondTxData)
|
||||
await store.dispatch(fetchSafe(safe.get('address')))
|
||||
safe = getSafeFrom(store.getState(), safeAddress)
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import * as TestUtils from 'react-dom/test-utils'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { type Match } from 'react-router-dom'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
import TokenComponent from '~/routes/tokens/component/Token'
|
||||
import { getFirstTokenContract, getSecondTokenContract } from '~/test/utils/tokenMovements'
|
||||
import { aNewStore } from '~/store'
|
||||
|
@ -24,21 +23,23 @@ describe('DOM > Feature > Add new ERC 20 Tokens', () => {
|
|||
|
||||
beforeAll(async () => {
|
||||
web3 = getWeb3()
|
||||
accounts = await promisify(cb => web3.eth.getAccounts(cb))
|
||||
accounts = await web3.eth.getAccounts()
|
||||
firstErc20Token = await getFirstTokenContract(web3, accounts[0])
|
||||
secondErc20Token = await getSecondTokenContract(web3, accounts[0])
|
||||
|
||||
// $FlowFixMe
|
||||
enhancedFetchModule.enhancedFetch = jest.fn()
|
||||
enhancedFetchModule.enhancedFetch.mockImplementation(() => Promise.resolve([
|
||||
{
|
||||
address: firstErc20Token.address,
|
||||
name: 'First Token Example',
|
||||
symbol: 'FTE',
|
||||
decimals: 18,
|
||||
logoUrl: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||
},
|
||||
]))
|
||||
enhancedFetchModule.enhancedFetch.mockImplementation(() => Promise.resolve({
|
||||
results: [
|
||||
{
|
||||
address: firstErc20Token.address,
|
||||
name: 'First Token Example',
|
||||
symbol: 'FTE',
|
||||
decimals: 18,
|
||||
logoUri: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||
},
|
||||
],
|
||||
}))
|
||||
})
|
||||
|
||||
it('adds a second erc 20 token filling the form', async () => {
|
||||
|
@ -46,6 +47,7 @@ describe('DOM > Feature > Add new ERC 20 Tokens', () => {
|
|||
const store = aNewStore()
|
||||
const safeAddress = await aMinedSafe(store)
|
||||
await store.dispatch(fetchTokensModule.fetchTokens(safeAddress))
|
||||
|
||||
const TokensDom = await travelToTokens(store, safeAddress)
|
||||
await sleep(400)
|
||||
const tokens = TestUtils.scryRenderedComponentsWithType(TokensDom, TokenComponent)
|
||||
|
|
|
@ -3,7 +3,6 @@ import * as TestUtils from 'react-dom/test-utils'
|
|||
import { List } from 'immutable'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { type Match } from 'react-router-dom'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
import TokenComponent from '~/routes/tokens/component/Token'
|
||||
import Checkbox from '@material-ui/core/Checkbox'
|
||||
import { getFirstTokenContract, getSecondTokenContract, addTknTo } from '~/test/utils/tokenMovements'
|
||||
|
@ -26,27 +25,29 @@ describe('DOM > Feature > Enable and disable default tokens', () => {
|
|||
|
||||
beforeAll(async () => {
|
||||
web3 = getWeb3()
|
||||
accounts = await promisify(cb => web3.eth.getAccounts(cb))
|
||||
accounts = await web3.eth.getAccounts()
|
||||
firstErc20Token = await getFirstTokenContract(web3, accounts[0])
|
||||
secondErc20Token = await getSecondTokenContract(web3, accounts[0])
|
||||
// $FlowFixMe
|
||||
enhancedFetchModule.enhancedFetch = jest.fn()
|
||||
enhancedFetchModule.enhancedFetch.mockImplementation(() => Promise.resolve([
|
||||
{
|
||||
address: firstErc20Token.address,
|
||||
name: 'First Token Example',
|
||||
symbol: 'FTE',
|
||||
decimals: 18,
|
||||
logoUrl: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||
},
|
||||
{
|
||||
address: secondErc20Token.address,
|
||||
name: 'Second Token Example',
|
||||
symbol: 'STE',
|
||||
decimals: 18,
|
||||
logoUrl: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||
},
|
||||
]))
|
||||
enhancedFetchModule.enhancedFetch.mockImplementation(() => Promise.resolve({
|
||||
results: [
|
||||
{
|
||||
address: firstErc20Token.address,
|
||||
name: 'First Token Example',
|
||||
symbol: 'FTE',
|
||||
decimals: 18,
|
||||
logoUri: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||
},
|
||||
{
|
||||
address: secondErc20Token.address,
|
||||
name: 'Second Token Example',
|
||||
symbol: 'STE',
|
||||
decimals: 18,
|
||||
logoUri: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||
},
|
||||
],
|
||||
}))
|
||||
})
|
||||
|
||||
it('retrieves only ether as active token in first moment', async () => {
|
||||
|
@ -76,8 +77,8 @@ describe('DOM > Feature > Enable and disable default tokens', () => {
|
|||
// GIVEN
|
||||
const store = aNewStore()
|
||||
const safeAddress = await aMinedSafe(store)
|
||||
await addTknTo(safeAddress, 50, firstErc20Token)
|
||||
await addTknTo(safeAddress, 50, secondErc20Token)
|
||||
await addTknTo(safeAddress, '50', firstErc20Token)
|
||||
await addTknTo(safeAddress, '50', secondErc20Token)
|
||||
await store.dispatch(fetchTokensModule.fetchTokens(safeAddress))
|
||||
|
||||
const match: Match = buildMathPropsFrom(safeAddress)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// @flow
|
||||
import * as TestUtils from 'react-dom/test-utils'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
import { getFirstTokenContract, getSecondTokenContract } from '~/test/utils/tokenMovements'
|
||||
import { aNewStore } from '~/store'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
|
@ -9,7 +8,12 @@ import { travelToTokens } from '~/test/builder/safe.dom.utils'
|
|||
import * as fetchTokensModule from '~/routes/tokens/store/actions/fetchTokens'
|
||||
import * as enhancedFetchModule from '~/utils/fetch'
|
||||
import { TOKEN_ADRESS_PARAM } from '~/routes/tokens/component/AddToken/FirstPage'
|
||||
import { TOKEN_NAME_PARAM, TOKEN_SYMBOL_PARAM, TOKEN_DECIMALS_PARAM, TOKEN_LOGO_URL_PARAM } from '~/routes/tokens/component/AddToken/SecondPage'
|
||||
import {
|
||||
TOKEN_NAME_PARAM,
|
||||
TOKEN_SYMBOL_PARAM,
|
||||
TOKEN_DECIMALS_PARAM,
|
||||
TOKEN_LOGO_URL_PARAM,
|
||||
} from '~/routes/tokens/component/AddToken/SecondPage'
|
||||
import addToken from '~/routes/tokens/store/actions/addToken'
|
||||
import { addTokenFnc } from '~/routes/tokens/component/AddToken'
|
||||
import { sleep } from '~/utils/timer'
|
||||
|
@ -24,21 +28,23 @@ describe('DOM > Feature > Add new ERC 20 Tokens', () => {
|
|||
|
||||
beforeAll(async () => {
|
||||
web3 = getWeb3()
|
||||
accounts = await promisify(cb => web3.eth.getAccounts(cb))
|
||||
accounts = await web3.eth.getAccounts()
|
||||
firstErc20Token = await getFirstTokenContract(web3, accounts[0])
|
||||
secondErc20Token = await getSecondTokenContract(web3, accounts[0])
|
||||
|
||||
// $FlowFixMe
|
||||
enhancedFetchModule.enhancedFetch = jest.fn()
|
||||
enhancedFetchModule.enhancedFetch.mockImplementation(() => Promise.resolve([
|
||||
{
|
||||
address: firstErc20Token.address,
|
||||
name: 'First Token Example',
|
||||
symbol: 'FTE',
|
||||
decimals: 18,
|
||||
logoUrl: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||
},
|
||||
]))
|
||||
enhancedFetchModule.enhancedFetch.mockImplementation(() => Promise.resolve({
|
||||
results: [
|
||||
{
|
||||
address: firstErc20Token.address,
|
||||
name: 'First Token Example',
|
||||
symbol: 'FTE',
|
||||
decimals: 18,
|
||||
logoUri: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||
},
|
||||
],
|
||||
}))
|
||||
})
|
||||
|
||||
it('remove custom ERC 20 tokens', async () => {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// @flow
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { type Match } from 'react-router-dom'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
import { getFirstTokenContract, getSecondTokenContract } from '~/test/utils/tokenMovements'
|
||||
import { aNewStore } from '~/store'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
|
@ -11,7 +10,12 @@ import { testToken } from '~/test/builder/tokens.dom.utils'
|
|||
import * as fetchTokensModule from '~/routes/tokens/store/actions/fetchTokens'
|
||||
import * as enhancedFetchModule from '~/utils/fetch'
|
||||
import { TOKEN_ADRESS_PARAM } from '~/routes/tokens/component/AddToken/FirstPage'
|
||||
import { TOKEN_NAME_PARAM, TOKEN_DECIMALS_PARAM, TOKEN_SYMBOL_PARAM, TOKEN_LOGO_URL_PARAM } from '~/routes/tokens/component/AddToken/SecondPage'
|
||||
import {
|
||||
TOKEN_NAME_PARAM,
|
||||
TOKEN_DECIMALS_PARAM,
|
||||
TOKEN_SYMBOL_PARAM,
|
||||
TOKEN_LOGO_URL_PARAM,
|
||||
} from '~/routes/tokens/component/AddToken/SecondPage'
|
||||
import addToken from '~/routes/tokens/store/actions/addToken'
|
||||
import { addTokenFnc } from '~/routes/tokens/component/AddToken'
|
||||
import { activeTokensSelector } from '~/routes/tokens/store/selectors'
|
||||
|
@ -24,21 +28,23 @@ describe('DOM > Feature > Add new ERC 20 Tokens', () => {
|
|||
|
||||
beforeAll(async () => {
|
||||
web3 = getWeb3()
|
||||
accounts = await promisify(cb => web3.eth.getAccounts(cb))
|
||||
accounts = await web3.eth.getAccounts()
|
||||
firstErc20Token = await getFirstTokenContract(web3, accounts[0])
|
||||
secondErc20Token = await getSecondTokenContract(web3, accounts[0])
|
||||
|
||||
// $FlowFixMe
|
||||
enhancedFetchModule.enhancedFetch = jest.fn()
|
||||
enhancedFetchModule.enhancedFetch.mockImplementation(() => Promise.resolve([
|
||||
{
|
||||
address: firstErc20Token.address,
|
||||
name: 'First Token Example',
|
||||
symbol: 'FTE',
|
||||
decimals: 18,
|
||||
logoUrl: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||
},
|
||||
]))
|
||||
enhancedFetchModule.enhancedFetch.mockImplementation(() => Promise.resolve({
|
||||
results: [
|
||||
{
|
||||
address: firstErc20Token.address,
|
||||
name: 'First Token Example',
|
||||
symbol: 'FTE',
|
||||
decimals: 18,
|
||||
logoUri: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||
},
|
||||
],
|
||||
}))
|
||||
})
|
||||
|
||||
it('persist added custom ERC 20 tokens as active when reloading the page', async () => {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// @flow
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { type Match } from 'react-router-dom'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
import { getFirstTokenContract, getSecondTokenContract } from '~/test/utils/tokenMovements'
|
||||
import { aNewStore } from '~/store'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
|
@ -11,7 +10,12 @@ import { testToken } from '~/test/builder/tokens.dom.utils'
|
|||
import * as fetchTokensModule from '~/routes/tokens/store/actions/fetchTokens'
|
||||
import * as enhancedFetchModule from '~/utils/fetch'
|
||||
import { TOKEN_ADRESS_PARAM } from '~/routes/tokens/component/AddToken/FirstPage'
|
||||
import { TOKEN_NAME_PARAM, TOKEN_DECIMALS_PARAM, TOKEN_SYMBOL_PARAM, TOKEN_LOGO_URL_PARAM } from '~/routes/tokens/component/AddToken/SecondPage'
|
||||
import {
|
||||
TOKEN_NAME_PARAM,
|
||||
TOKEN_DECIMALS_PARAM,
|
||||
TOKEN_SYMBOL_PARAM,
|
||||
TOKEN_LOGO_URL_PARAM,
|
||||
} from '~/routes/tokens/component/AddToken/SecondPage'
|
||||
import addToken from '~/routes/tokens/store/actions/addToken'
|
||||
import { addTokenFnc } from '~/routes/tokens/component/AddToken'
|
||||
import { activeTokensSelector, tokenListSelector } from '~/routes/tokens/store/selectors'
|
||||
|
@ -27,21 +31,23 @@ describe('DOM > Feature > Add new ERC 20 Tokens', () => {
|
|||
|
||||
beforeAll(async () => {
|
||||
web3 = getWeb3()
|
||||
accounts = await promisify(cb => web3.eth.getAccounts(cb))
|
||||
accounts = await web3.eth.getAccounts()
|
||||
firstErc20Token = await getFirstTokenContract(web3, accounts[0])
|
||||
secondErc20Token = await getSecondTokenContract(web3, accounts[0])
|
||||
|
||||
// $FlowFixMe
|
||||
enhancedFetchModule.enhancedFetch = jest.fn()
|
||||
enhancedFetchModule.enhancedFetch.mockImplementation(() => Promise.resolve([
|
||||
{
|
||||
address: firstErc20Token.address,
|
||||
name: 'First Token Example',
|
||||
symbol: 'FTE',
|
||||
decimals: 18,
|
||||
logoUrl: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||
},
|
||||
]))
|
||||
enhancedFetchModule.enhancedFetch.mockImplementation(() => Promise.resolve({
|
||||
results: [
|
||||
{
|
||||
address: firstErc20Token.address,
|
||||
name: 'First Token Example',
|
||||
symbol: 'FTE',
|
||||
decimals: 18,
|
||||
logoUri: 'https://upload.wikimedia.org/wikipedia/commons/c/c0/Earth_simple_icon.png',
|
||||
},
|
||||
],
|
||||
}))
|
||||
})
|
||||
|
||||
const checkTokensOf = (store: Store, safeAddress: string) => {
|
||||
|
@ -78,7 +84,7 @@ describe('DOM > Feature > Add new ERC 20 Tokens', () => {
|
|||
name: 'Custom ERC20 Token',
|
||||
symbol: 'CTS',
|
||||
decimals: 10,
|
||||
logoUrl: 'https://example.com',
|
||||
logoUri: 'https://example.com',
|
||||
status: true,
|
||||
removable: true,
|
||||
})
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// @flow
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import abi from 'ethereumjs-abi'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
|
||||
/*
|
||||
console.log(`to[${to}] \n\n valieInWei[${valueInWei}] \n\n
|
||||
|
@ -15,9 +14,12 @@ const err = await getErrorMessage(address, 0, txData, accounts[2])
|
|||
*/
|
||||
export const getErrorMessage = async (to: string, value: number, data: string, from: string) => {
|
||||
const web3 = getWeb3()
|
||||
const returnData = await promisify(cb => web3.eth.call({
|
||||
to, from, value, data,
|
||||
}, cb))
|
||||
const returnData = await web3.eth.call({
|
||||
to,
|
||||
from,
|
||||
value,
|
||||
data,
|
||||
})
|
||||
const returnBuffer = Buffer.from(returnData.slice(2), 'hex')
|
||||
|
||||
return abi.rawDecode(['string'], returnBuffer.slice(4))[0]
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
// @flow
|
||||
import contract from 'truffle-contract'
|
||||
import { getBalanceInEtherOf, getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { promisify } from '~/utils/promisify'
|
||||
import Token from '#/test/TestToken.json'
|
||||
import { ensureOnce } from '~/utils/singleton'
|
||||
import { toNative } from '~/logic/wallets/tokens'
|
||||
|
||||
export const addEtherTo = async (address: string, eth: string) => {
|
||||
const web3 = getWeb3()
|
||||
const accounts = await promisify(cb => web3.eth.getAccounts(cb))
|
||||
const txData = { from: accounts[0], to: address, value: web3.toWei(eth, 'ether') }
|
||||
return promisify(cb => web3.eth.sendTransaction(txData, cb))
|
||||
const accounts = await web3.eth.getAccounts()
|
||||
const txData = { from: accounts[0], to: address, value: web3.utils.toWei(eth, 'ether') }
|
||||
return web3.eth.sendTransaction(txData)
|
||||
}
|
||||
|
||||
export const checkBalanceOf = async (addressToTest: string, value: string) => {
|
||||
|
@ -28,12 +27,12 @@ const createTokenContract = async (web3: any, executor: string) => {
|
|||
export const getFirstTokenContract = ensureOnce(createTokenContract)
|
||||
export const getSecondTokenContract = ensureOnce(createTokenContract)
|
||||
|
||||
export const addTknTo = async (safe: string, value: number, tokenContract?: any) => {
|
||||
export const addTknTo = async (safe: string, value: string, tokenContract?: any) => {
|
||||
const web3 = getWeb3()
|
||||
const accounts = await promisify(cb => getWeb3().eth.getAccounts(cb))
|
||||
const accounts = await web3.eth.getAccounts()
|
||||
|
||||
const myToken = tokenContract || await getFirstTokenContract(web3, accounts[0])
|
||||
const nativeValue = await toNative(value, 18)
|
||||
const nativeValue = toNative(value, 18)
|
||||
await myToken.transfer(safe, nativeValue.valueOf(), { from: accounts[0], gas: '5000000' })
|
||||
|
||||
return myToken.address
|
||||
|
|
|
@ -51,7 +51,7 @@ export const dispatchTknBalance = async (store: Store, tokenAddress: string, add
|
|||
name: 'Token',
|
||||
symbol: 'TKN',
|
||||
decimals: 18,
|
||||
logoUrl: 'https://github.com/TrustWallet/tokens/blob/master/images/0x6810e776880c02933d47db1b9fc05908e5386b96.png?raw=true',
|
||||
logoUri: 'https://github.com/TrustWallet/tokens/blob/master/images/0x6810e776880c02933d47db1b9fc05908e5386b96.png?raw=true',
|
||||
funds,
|
||||
}))
|
||||
fetchBalancesMock.mockImplementation(() => store.dispatch(addTokens(address, balances)))
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
// @flow
|
||||
import { createMuiTheme } from '@material-ui/core/styles'
|
||||
import { largeFontSize, mediumFontSize, smallFontSize, disabled, primary, secondary, md, lg, bolderFont, boldFont, buttonLargeFontSize } from './variables'
|
||||
import {
|
||||
largeFontSize,
|
||||
mediumFontSize,
|
||||
smallFontSize,
|
||||
disabled,
|
||||
primary,
|
||||
secondary,
|
||||
md,
|
||||
lg,
|
||||
bolderFont,
|
||||
boldFont,
|
||||
buttonLargeFontSize,
|
||||
} from './variables'
|
||||
|
||||
export type WithStyles = {
|
||||
classes: Object,
|
||||
|
@ -25,20 +37,18 @@ const palette = {
|
|||
export default createMuiTheme({
|
||||
typography: {
|
||||
fontFamily: 'Montserrat,sans-serif',
|
||||
useNextVariants: true,
|
||||
},
|
||||
overrides: {
|
||||
MuiButton: {
|
||||
root: {
|
||||
fontFamily: 'Roboto Mono, monospace',
|
||||
letterSpacing: '0.9px',
|
||||
'&:disabled': {
|
||||
'&$disabled': {
|
||||
color: disabled,
|
||||
},
|
||||
color: disabled,
|
||||
},
|
||||
disabled: {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
contained: {
|
||||
boxShadow: 'none',
|
||||
},
|
||||
|
@ -159,9 +169,9 @@ export default createMuiTheme({
|
|||
root: {
|
||||
fontFamily: 'Roboto Mono, monospace',
|
||||
fontWeight: 'normal',
|
||||
},
|
||||
selected: {
|
||||
fontWeight: bolderFont,
|
||||
'&$selected': {
|
||||
fontWeight: bolderFont,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiTablePagination: {
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
// @flow
|
||||
|
||||
export const copyToClipboard = (text: string) => {
|
||||
if (!navigator.clipboard) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
navigator.clipboard.writeText(text)
|
||||
} catch (err) {
|
||||
console.error(err.message)
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ export const getSafeEthToken = async (safeAddress: string) => {
|
|||
name: 'Ether',
|
||||
symbol: 'ETH',
|
||||
decimals: 18,
|
||||
logoUrl: logo,
|
||||
logoUri: logo,
|
||||
funds: balance,
|
||||
})
|
||||
|
||||
|
|
Loading…
Reference in New Issue