Make Wallet first-class citizens (#3990)
* Fixed hint in Address Select + Wallet as first-class-citizen * Separate Owned and not Owned Wallets * Fix balance not updating * Fix MethodDecoding for Contract Deployment * Fix TypedInput params * Fix Token Transfer for Wallet * Small change to contracts * Fix wallets shown twice * Fix separation of accounts and wallets in Accounts * Fix linting * Execute contract methods from Wallet ✓ * Fixing linting * Wallet as first-class citizen: Part 1 (Manual) #3784 * Lower level wallet transaction convertion * Fix linting * Proper autoFocus on right Signer input * PR Grumble: don't show Wallets in dApps Permissions * Add postTransaction and gasEstimate wrapper methods * Extract Wallet postTx and gasEstimate to utils + PATCH api * Remove invalid test It's totally valid for input's length not to be a multiple of 32 bytes. EG. for Wallet Contracts * Merge master * Fix linting * Fix merge issue * Rename Portal * Rename Protal => Portal (typo)
This commit is contained in:
parent
88c0329a31
commit
fd41a10319
@ -25,7 +25,7 @@ export default class Encoder {
|
||||
throw new Error('tokens should be array of Token');
|
||||
}
|
||||
|
||||
const mediates = tokens.map((token) => Encoder.encodeToken(token));
|
||||
const mediates = tokens.map((token, index) => Encoder.encodeToken(token, index));
|
||||
const inits = mediates
|
||||
.map((mediate, idx) => mediate.init(Mediate.offsetFor(mediates, idx)))
|
||||
.join('');
|
||||
@ -36,37 +36,40 @@ export default class Encoder {
|
||||
return `${inits}${closings}`;
|
||||
}
|
||||
|
||||
static encodeToken (token) {
|
||||
static encodeToken (token, index = 0) {
|
||||
if (!isInstanceOf(token, Token)) {
|
||||
throw new Error('token should be instanceof Token');
|
||||
}
|
||||
|
||||
switch (token.type) {
|
||||
case 'address':
|
||||
return new Mediate('raw', padAddress(token.value));
|
||||
try {
|
||||
switch (token.type) {
|
||||
case 'address':
|
||||
return new Mediate('raw', padAddress(token.value));
|
||||
|
||||
case 'int':
|
||||
case 'uint':
|
||||
return new Mediate('raw', padU32(token.value));
|
||||
case 'int':
|
||||
case 'uint':
|
||||
return new Mediate('raw', padU32(token.value));
|
||||
|
||||
case 'bool':
|
||||
return new Mediate('raw', padBool(token.value));
|
||||
case 'bool':
|
||||
return new Mediate('raw', padBool(token.value));
|
||||
|
||||
case 'fixedBytes':
|
||||
return new Mediate('raw', padFixedBytes(token.value));
|
||||
case 'fixedBytes':
|
||||
return new Mediate('raw', padFixedBytes(token.value));
|
||||
|
||||
case 'bytes':
|
||||
return new Mediate('prefixed', padBytes(token.value));
|
||||
case 'bytes':
|
||||
return new Mediate('prefixed', padBytes(token.value));
|
||||
|
||||
case 'string':
|
||||
return new Mediate('prefixed', padString(token.value));
|
||||
case 'string':
|
||||
return new Mediate('prefixed', padString(token.value));
|
||||
|
||||
case 'fixedArray':
|
||||
case 'array':
|
||||
return new Mediate(token.type, token.value.map((token) => Encoder.encodeToken(token)));
|
||||
|
||||
default:
|
||||
throw new Error(`Invalid token type ${token.type} in encodeToken`);
|
||||
case 'fixedArray':
|
||||
case 'array':
|
||||
return new Mediate(token.type, token.value.map((token) => Encoder.encodeToken(token)));
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(`Cannot encode token #${index} [${token.type}: ${token.value}]. ${e.message}`);
|
||||
}
|
||||
|
||||
throw new Error(`Invalid token type ${token.type} in encodeToken`);
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,10 @@ export default class Interface {
|
||||
}
|
||||
|
||||
encodeTokens (paramTypes, values) {
|
||||
return Interface.encodeTokens(paramTypes, values);
|
||||
}
|
||||
|
||||
static encodeTokens (paramTypes, values) {
|
||||
const createToken = function (paramType, value) {
|
||||
if (paramType.subtype) {
|
||||
return new Token(paramType.type, value.map((entry) => createToken(paramType.subtype, entry)));
|
||||
|
@ -114,7 +114,11 @@ export default class Api {
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('pollMethod', error);
|
||||
// Don't print if the request is rejected: that's ok
|
||||
if (error.type !== 'REQUEST_REJECTED') {
|
||||
console.error('pollMethod', error);
|
||||
}
|
||||
|
||||
reject(error);
|
||||
});
|
||||
};
|
||||
|
@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import Abi from '../../abi';
|
||||
import Abi from '~/abi';
|
||||
|
||||
let nextSubscriptionId = 0;
|
||||
|
||||
@ -53,6 +53,10 @@ export default class Contract {
|
||||
|
||||
this._subscribedToBlock = false;
|
||||
this._blockSubscriptionId = null;
|
||||
|
||||
if (api && api.patch && api.patch.contract) {
|
||||
api.patch.contract(this);
|
||||
}
|
||||
}
|
||||
|
||||
get address () {
|
||||
@ -90,8 +94,10 @@ export default class Contract {
|
||||
}
|
||||
|
||||
deployEstimateGas (options, values) {
|
||||
const _options = this._encodeOptions(this.constructors[0], options, values);
|
||||
|
||||
return this._api.eth
|
||||
.estimateGas(this._encodeOptions(this.constructors[0], options, values))
|
||||
.estimateGas(_options)
|
||||
.then((gasEst) => {
|
||||
return [gasEst, gasEst.mul(1.2)];
|
||||
});
|
||||
@ -115,8 +121,10 @@ export default class Contract {
|
||||
|
||||
setState({ state: 'postTransaction', gas });
|
||||
|
||||
const _options = this._encodeOptions(this.constructors[0], options, values);
|
||||
|
||||
return this._api.parity
|
||||
.postTransaction(this._encodeOptions(this.constructors[0], options, values))
|
||||
.postTransaction(_options)
|
||||
.then((requestId) => {
|
||||
setState({ state: 'checkRequest', requestId });
|
||||
return this._pollCheckRequest(requestId);
|
||||
@ -199,7 +207,7 @@ export default class Contract {
|
||||
getCallData = (func, options, values) => {
|
||||
let data = options.data;
|
||||
|
||||
const tokens = func ? this._abi.encodeTokens(func.inputParamTypes(), values) : null;
|
||||
const tokens = func ? Abi.encodeTokens(func.inputParamTypes(), values) : null;
|
||||
const call = tokens ? func.encodeCall(tokens) : null;
|
||||
|
||||
if (data && data.substr(0, 2) === '0x') {
|
||||
@ -221,6 +229,8 @@ export default class Contract {
|
||||
}
|
||||
|
||||
_bindFunction = (func) => {
|
||||
func.contract = this;
|
||||
|
||||
func.call = (options, values = []) => {
|
||||
const callParams = this._encodeOptions(func, this._addOptionsTo(options), values);
|
||||
|
||||
@ -233,13 +243,13 @@ export default class Contract {
|
||||
|
||||
if (!func.constant) {
|
||||
func.postTransaction = (options, values = []) => {
|
||||
return this._api.parity
|
||||
.postTransaction(this._encodeOptions(func, this._addOptionsTo(options), values));
|
||||
const _options = this._encodeOptions(func, this._addOptionsTo(options), values);
|
||||
return this._api.parity.postTransaction(_options);
|
||||
};
|
||||
|
||||
func.estimateGas = (options, values = []) => {
|
||||
return this._api.eth
|
||||
.estimateGas(this._encodeOptions(func, this._addOptionsTo(options), values));
|
||||
const _options = this._encodeOptions(func, this._addOptionsTo(options), values);
|
||||
return this._api.eth.estimateGas(_options);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -209,7 +209,10 @@ export default class Ws extends JsonRpcBase {
|
||||
if (result.error) {
|
||||
this.error(event.data);
|
||||
|
||||
console.error(`${method}(${JSON.stringify(params)}): ${result.error.code}: ${result.error.message}`);
|
||||
// Don't print error if request rejected...
|
||||
if (!/rejected/.test(result.error.message)) {
|
||||
console.error(`${method}(${JSON.stringify(params)}): ${result.error.code}: ${result.error.message}`);
|
||||
}
|
||||
|
||||
const error = new TransportError(method, result.error.code, result.error.message);
|
||||
reject(error);
|
||||
|
@ -47,8 +47,6 @@ export function decodeMethodInput (methodAbi, paramdata) {
|
||||
throw new Error('Input to decodeMethodInput should be a hex value');
|
||||
} else if (paramdata.substr(0, 2) === '0x') {
|
||||
return decodeMethodInput(methodAbi, paramdata.slice(2));
|
||||
} else if (paramdata.length % 64 !== 0) {
|
||||
throw new Error('Parameter length in decodeMethodInput not a multiple of 64 characters');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,10 +48,6 @@ describe('api/util/decode', () => {
|
||||
expect(() => decodeMethodInput({}, 'invalid')).to.throw(/should be a hex value/);
|
||||
});
|
||||
|
||||
it('throws on invalid lengths', () => {
|
||||
expect(() => decodeMethodInput({}, DATA.slice(-32))).to.throw(/not a multiple of/);
|
||||
});
|
||||
|
||||
it('correctly decodes valid inputs', () => {
|
||||
expect(decodeMethodInput({
|
||||
type: 'function',
|
||||
|
@ -36,6 +36,7 @@ import ContextProvider from '~/ui/ContextProvider';
|
||||
import muiTheme from '~/ui/Theme';
|
||||
import MainApplication from './main';
|
||||
|
||||
import { patchApi } from '~/util/tx';
|
||||
import { setApi } from '~/redux/providers/apiActions';
|
||||
|
||||
import './environment';
|
||||
@ -60,6 +61,7 @@ if (window.location.hash && window.location.hash.indexOf(AUTH_HASH) === 0) {
|
||||
}
|
||||
|
||||
const api = new SecureApi(`ws://${parityUrl}`, token);
|
||||
patchApi(api);
|
||||
ContractInstances.create(api);
|
||||
|
||||
const store = initStore(api, hashHistory);
|
||||
|
@ -15,6 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { omitBy } from 'lodash';
|
||||
|
||||
import { Form, TypedInput, Input, AddressSelect, InputAddress } from '~/ui';
|
||||
|
||||
@ -73,6 +74,9 @@ export default class WalletDetails extends Component {
|
||||
renderMultisigDetails () {
|
||||
const { accounts, wallet, errors } = this.props;
|
||||
|
||||
// Wallets cannot create contracts
|
||||
const _accounts = omitBy(accounts, (a) => a.wallet);
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<AddressSelect
|
||||
@ -81,7 +85,7 @@ export default class WalletDetails extends Component {
|
||||
value={ wallet.account }
|
||||
error={ errors.account }
|
||||
onChange={ this.onAccoutChange }
|
||||
accounts={ accounts }
|
||||
accounts={ _accounts }
|
||||
/>
|
||||
|
||||
<Input
|
||||
|
@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { pick } from 'lodash';
|
||||
import { pick, omitBy } from 'lodash';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
@ -561,13 +561,19 @@ class DeployContract extends Component {
|
||||
}
|
||||
|
||||
function mapStateToProps (initState, initProps) {
|
||||
const fromAddresses = Object.keys(initProps.accounts);
|
||||
const { accounts } = initProps;
|
||||
|
||||
// Skip Wallet accounts : they can't create Contracts
|
||||
const _accounts = omitBy(accounts, (a) => a.wallet);
|
||||
|
||||
const fromAddresses = Object.keys(_accounts);
|
||||
|
||||
return (state) => {
|
||||
const balances = pick(state.balances.balances, fromAddresses);
|
||||
const { gasLimit } = state.nodeStatus;
|
||||
|
||||
return {
|
||||
accounts: _accounts,
|
||||
balances,
|
||||
gasLimit
|
||||
};
|
||||
|
@ -389,6 +389,7 @@ class ExecuteContract extends Component {
|
||||
const { advancedOptions, amount, func, minBlock, values } = this.state;
|
||||
const steps = advancedOptions ? STAGES_ADVANCED : STAGES_BASIC;
|
||||
const finalstep = steps.length - 1;
|
||||
|
||||
const options = {
|
||||
gas: this.gasStore.gas,
|
||||
gasPrice: this.gasStore.price,
|
||||
|
@ -383,9 +383,7 @@ export default class TransferStore {
|
||||
|
||||
const senderBalance = this.balance.tokens.find((b) => tag === b.token.tag);
|
||||
const format = new BigNumber(senderBalance.token.format || 1);
|
||||
const available = isWallet
|
||||
? this.api.util.fromWei(new BigNumber(senderBalance.value))
|
||||
: (new BigNumber(senderBalance.value)).div(format);
|
||||
const available = new BigNumber(senderBalance.value).div(format);
|
||||
|
||||
let { value, valueError } = this;
|
||||
let totalEth = gasTotal;
|
||||
@ -428,7 +426,6 @@ export default class TransferStore {
|
||||
|
||||
send () {
|
||||
const { options, values } = this._getTransferParams();
|
||||
|
||||
options.minBlock = new BigNumber(this.minBlock || 0).gt(0) ? this.minBlock : null;
|
||||
|
||||
return this._getTransferMethod().postTransaction(options, values);
|
||||
@ -440,16 +437,7 @@ export default class TransferStore {
|
||||
}
|
||||
|
||||
estimateGas () {
|
||||
if (this.isEth || !this.isWallet) {
|
||||
return this._estimateGas();
|
||||
}
|
||||
|
||||
return Promise
|
||||
.all([
|
||||
this._estimateGas(true),
|
||||
this._estimateGas()
|
||||
])
|
||||
.then((results) => results[0].plus(results[1]));
|
||||
return this._estimateGas();
|
||||
}
|
||||
|
||||
_getTransferMethod (gas = false, forceToken = false) {
|
||||
|
@ -36,7 +36,7 @@ class WalletSettings extends Component {
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
accounts: PropTypes.object.isRequired,
|
||||
accountsInfo: PropTypes.object.isRequired,
|
||||
wallet: PropTypes.object.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
senders: PropTypes.object.isRequired
|
||||
@ -113,7 +113,7 @@ class WalletSettings extends Component {
|
||||
default:
|
||||
case 'EDIT':
|
||||
const { wallet, errors } = this.store;
|
||||
const { accounts, senders } = this.props;
|
||||
const { accountsInfo, senders } = this.props;
|
||||
|
||||
return (
|
||||
<Form>
|
||||
@ -137,7 +137,7 @@ class WalletSettings extends Component {
|
||||
label='other wallet owners'
|
||||
value={ wallet.owners.slice() }
|
||||
onChange={ this.store.onOwnersChange }
|
||||
accounts={ accounts }
|
||||
accounts={ accountsInfo }
|
||||
param='address[]'
|
||||
/>
|
||||
|
||||
@ -190,7 +190,7 @@ class WalletSettings extends Component {
|
||||
}
|
||||
|
||||
renderChange (change) {
|
||||
const { accounts } = this.props;
|
||||
const { accountsInfo } = this.props;
|
||||
|
||||
switch (change.type) {
|
||||
case 'dailylimit':
|
||||
@ -229,7 +229,7 @@ class WalletSettings extends Component {
|
||||
<InputAddress
|
||||
disabled
|
||||
value={ change.value }
|
||||
accounts={ accounts }
|
||||
accounts={ accountsInfo }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -243,7 +243,7 @@ class WalletSettings extends Component {
|
||||
<InputAddress
|
||||
disabled
|
||||
value={ change.value }
|
||||
accounts={ accounts }
|
||||
accounts={ accountsInfo }
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -329,7 +329,7 @@ function mapStateToProps (initState, initProps) {
|
||||
const senders = pick(accounts, owners);
|
||||
|
||||
return () => {
|
||||
return { accounts: accountsInfo, senders };
|
||||
return { accountsInfo, senders };
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,8 @@ const STEPS = {
|
||||
};
|
||||
|
||||
export default class WalletSettingsStore {
|
||||
accounts = {};
|
||||
|
||||
@observable step = null;
|
||||
@observable requests = [];
|
||||
@observable deployState = '';
|
||||
|
@ -175,7 +175,7 @@ export function fetchBalances (_addresses) {
|
||||
const { api, personal } = getState();
|
||||
const { visibleAccounts, accounts } = personal;
|
||||
|
||||
const addresses = uniq(_addresses || visibleAccounts || []);
|
||||
const addresses = uniq((_addresses || visibleAccounts || []).concat(Object.keys(accounts)));
|
||||
|
||||
if (addresses.length === 0) {
|
||||
return Promise.resolve();
|
||||
@ -183,7 +183,7 @@ export function fetchBalances (_addresses) {
|
||||
|
||||
const fullFetch = addresses.length === 1;
|
||||
|
||||
const addressesToFetch = uniq(addresses.concat(Object.keys(accounts)));
|
||||
const addressesToFetch = uniq(addresses);
|
||||
|
||||
return Promise
|
||||
.all(addressesToFetch.map((addr) => fetchAccount(addr, api, fullFetch)))
|
||||
|
@ -14,14 +14,18 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { isEqual } from 'lodash';
|
||||
import { isEqual, intersection } from 'lodash';
|
||||
|
||||
import { fetchBalances } from './balancesActions';
|
||||
import { attachWallets } from './walletActions';
|
||||
|
||||
import Contract from '~/api/contract';
|
||||
import MethodDecodingStore from '~/ui/MethodDecoding/methodDecodingStore';
|
||||
import WalletsUtils from '~/util/wallets';
|
||||
import { wallet as WalletAbi } from '~/contracts/abi';
|
||||
|
||||
export function personalAccountsInfo (accountsInfo) {
|
||||
const addresses = [];
|
||||
const accounts = {};
|
||||
const contacts = {};
|
||||
const contracts = {};
|
||||
@ -32,6 +36,7 @@ export function personalAccountsInfo (accountsInfo) {
|
||||
.filter((account) => account.uuid || !account.meta.deleted)
|
||||
.forEach((account) => {
|
||||
if (account.uuid) {
|
||||
addresses.push(account.address);
|
||||
accounts[account.address] = account;
|
||||
} else if (account.meta.wallet) {
|
||||
account.wallet = true;
|
||||
@ -46,14 +51,52 @@ export function personalAccountsInfo (accountsInfo) {
|
||||
// Load user contracts for Method Decoding
|
||||
MethodDecodingStore.loadContracts(contracts);
|
||||
|
||||
return (dispatch) => {
|
||||
const data = {
|
||||
accountsInfo,
|
||||
accounts, contacts, contracts, wallets
|
||||
};
|
||||
return (dispatch, getState) => {
|
||||
const { api } = getState();
|
||||
|
||||
dispatch(_personalAccountsInfo(data));
|
||||
dispatch(attachWallets(wallets));
|
||||
const _fetchOwners = Object
|
||||
.values(wallets)
|
||||
.map((wallet) => {
|
||||
const walletContract = new Contract(api, WalletAbi);
|
||||
return WalletsUtils.fetchOwners(walletContract.at(wallet.address));
|
||||
});
|
||||
|
||||
Promise
|
||||
.all(_fetchOwners)
|
||||
.then((walletsOwners) => {
|
||||
return Object
|
||||
.values(wallets)
|
||||
.map((wallet, index) => {
|
||||
wallet.owners = walletsOwners[index].map((owner) => ({
|
||||
address: owner,
|
||||
name: accountsInfo[owner] && accountsInfo[owner].name || owner
|
||||
}));
|
||||
|
||||
return wallet;
|
||||
});
|
||||
})
|
||||
.then((_wallets) => {
|
||||
_wallets.forEach((wallet) => {
|
||||
const owners = wallet.owners.map((o) => o.address);
|
||||
|
||||
// Owners ∩ Addresses not null : Wallet is owned
|
||||
// by one of the accounts
|
||||
if (intersection(owners, addresses).length > 0) {
|
||||
accounts[wallet.address] = wallet;
|
||||
} else {
|
||||
contacts[wallet.address] = wallet;
|
||||
}
|
||||
});
|
||||
|
||||
const data = {
|
||||
accountsInfo,
|
||||
accounts, contacts, contracts
|
||||
};
|
||||
|
||||
dispatch(_personalAccountsInfo(data));
|
||||
dispatch(attachWallets(wallets));
|
||||
dispatch(fetchBalances());
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -25,14 +25,13 @@ const initialState = {
|
||||
hasContacts: false,
|
||||
contracts: {},
|
||||
hasContracts: false,
|
||||
wallet: {},
|
||||
hasWallets: false,
|
||||
visibleAccounts: []
|
||||
};
|
||||
|
||||
export default handleActions({
|
||||
personalAccountsInfo (state, action) {
|
||||
const { accountsInfo, accounts, contacts, contracts, wallets } = action;
|
||||
const accountsInfo = action.accountsInfo || state.accountsInfo;
|
||||
const { accounts, contacts, contracts } = action;
|
||||
|
||||
return Object.assign({}, state, {
|
||||
accountsInfo,
|
||||
@ -41,9 +40,7 @@ export default handleActions({
|
||||
contacts,
|
||||
hasContacts: Object.keys(contacts).length !== 0,
|
||||
contracts,
|
||||
hasContracts: Object.keys(contracts).length !== 0,
|
||||
wallets,
|
||||
hasWallets: Object.keys(wallets).length !== 0
|
||||
hasContracts: Object.keys(contracts).length !== 0
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -90,7 +90,7 @@ export default handleActions({
|
||||
signerSuccessRejectRequest (state, action) {
|
||||
const { id } = action.payload;
|
||||
const rejected = Object.assign(
|
||||
state.pending.find(p => p.id === id),
|
||||
state.pending.find(p => p.id === id) || { id },
|
||||
{ status: 'rejected' }
|
||||
);
|
||||
return {
|
||||
|
@ -26,8 +26,8 @@ import TextFieldUnderline from 'material-ui/TextField/TextFieldUnderline';
|
||||
import AccountCard from '~/ui/AccountCard';
|
||||
import InputAddress from '~/ui/Form/InputAddress';
|
||||
import Portal from '~/ui/Portal';
|
||||
import { validateAddress } from '~/util/validation';
|
||||
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||
import { validateAddress } from '~/util/validation';
|
||||
|
||||
import AddressSelectStore from './addressSelectStore';
|
||||
import styles from './addressSelect.css';
|
||||
@ -40,6 +40,7 @@ let currentId = 1;
|
||||
@observer
|
||||
class AddressSelect extends Component {
|
||||
static contextTypes = {
|
||||
intl: React.PropTypes.object.isRequired,
|
||||
api: PropTypes.object.isRequired,
|
||||
muiTheme: PropTypes.object.isRequired
|
||||
};
|
||||
@ -55,7 +56,6 @@ class AddressSelect extends Component {
|
||||
contacts: PropTypes.object,
|
||||
contracts: PropTypes.object,
|
||||
tokens: PropTypes.object,
|
||||
wallets: PropTypes.object,
|
||||
|
||||
// Optional props
|
||||
allowInput: PropTypes.bool,
|
||||
@ -160,6 +160,12 @@ class AddressSelect extends Component {
|
||||
}
|
||||
|
||||
const id = `addressSelect_${++currentId}`;
|
||||
const ilHint = typeof hint === 'string' || !(hint && hint.props)
|
||||
? (hint || '')
|
||||
: this.context.intl.formatMessage(
|
||||
hint.props,
|
||||
hint.props.values || {}
|
||||
);
|
||||
|
||||
return (
|
||||
<Portal
|
||||
@ -174,7 +180,7 @@ class AddressSelect extends Component {
|
||||
<input
|
||||
id={ id }
|
||||
className={ styles.input }
|
||||
placeholder={ hint }
|
||||
placeholder={ ilHint }
|
||||
|
||||
onBlur={ this.handleInputBlur }
|
||||
onFocus={ this.handleInputFocus }
|
||||
|
@ -78,14 +78,13 @@ export default class AddressSelectStore {
|
||||
}
|
||||
|
||||
@action setValues (props) {
|
||||
const { accounts = {}, contracts = {}, contacts = {}, wallets = {} } = props;
|
||||
const { accounts = {}, contracts = {}, contacts = {} } = props;
|
||||
|
||||
const accountsN = Object.keys(accounts).length;
|
||||
const contractsN = Object.keys(contracts).length;
|
||||
const contactsN = Object.keys(contacts).length;
|
||||
const walletsN = Object.keys(wallets).length;
|
||||
|
||||
if (accountsN + contractsN + contactsN + walletsN === 0) {
|
||||
if (accountsN + contractsN + contactsN === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -98,10 +97,7 @@ export default class AddressSelectStore {
|
||||
defaultMessage='accounts'
|
||||
/>
|
||||
),
|
||||
values: [].concat(
|
||||
Object.values(wallets),
|
||||
Object.values(accounts)
|
||||
)
|
||||
values: Object.values(accounts)
|
||||
},
|
||||
{
|
||||
key: 'contacts',
|
||||
|
@ -51,6 +51,7 @@ export default class Input extends Component {
|
||||
PropTypes.string,
|
||||
PropTypes.bool
|
||||
]),
|
||||
autoFocus: PropTypes.bool,
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
@ -112,7 +113,7 @@ export default class Input extends Component {
|
||||
|
||||
render () {
|
||||
const { value } = this.state;
|
||||
const { children, className, hideUnderline, disabled, error, focused, label } = this.props;
|
||||
const { autoFocus, children, className, hideUnderline, disabled, error, focused, label } = this.props;
|
||||
const { hint, onClick, onFocus, multiLine, rows, type, min, max, style, tabIndex } = this.props;
|
||||
|
||||
const readOnly = this.props.readOnly || disabled;
|
||||
@ -138,6 +139,7 @@ export default class Input extends Component {
|
||||
{ this.renderCopyButton() }
|
||||
<TextField
|
||||
autoComplete='off'
|
||||
autoFocus={ autoFocus }
|
||||
className={ className }
|
||||
errorText={ error }
|
||||
floatingLabelFixed
|
||||
|
@ -25,7 +25,6 @@ class InputAddressSelect extends Component {
|
||||
accounts: PropTypes.object.isRequired,
|
||||
contacts: PropTypes.object.isRequired,
|
||||
contracts: PropTypes.object.isRequired,
|
||||
wallets: PropTypes.object.isRequired,
|
||||
error: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
hint: PropTypes.string,
|
||||
@ -34,7 +33,7 @@ class InputAddressSelect extends Component {
|
||||
};
|
||||
|
||||
render () {
|
||||
const { accounts, contacts, contracts, wallets, label, hint, error, value, onChange } = this.props;
|
||||
const { accounts, contacts, contracts, label, hint, error, value, onChange } = this.props;
|
||||
|
||||
return (
|
||||
<AddressSelect
|
||||
@ -42,7 +41,6 @@ class InputAddressSelect extends Component {
|
||||
accounts={ accounts }
|
||||
contacts={ contacts }
|
||||
contracts={ contracts }
|
||||
wallets={ wallets }
|
||||
error={ error }
|
||||
label={ label }
|
||||
hint={ hint }
|
||||
@ -53,13 +51,12 @@ class InputAddressSelect extends Component {
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { accounts, contacts, contracts, wallets } = state.personal;
|
||||
const { accounts, contacts, contracts } = state.personal;
|
||||
|
||||
return {
|
||||
accounts,
|
||||
contacts,
|
||||
contracts,
|
||||
wallets
|
||||
contracts
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -67,15 +67,7 @@ export default class TypedInput extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { param } = this.props;
|
||||
|
||||
if (typeof param === 'string') {
|
||||
const parsedParam = parseAbiType(param);
|
||||
|
||||
if (parsedParam) {
|
||||
return this.renderParam(parsedParam);
|
||||
}
|
||||
}
|
||||
const param = this.getParam();
|
||||
|
||||
if (param) {
|
||||
return this.renderParam(param);
|
||||
@ -234,7 +226,8 @@ export default class TypedInput extends Component {
|
||||
}
|
||||
|
||||
renderInteger (value = this.props.value, onChange = this.onChange) {
|
||||
const { label, error, param, hint, min, max } = this.props;
|
||||
const { label, error, hint, min, max } = this.props;
|
||||
const param = this.getParam();
|
||||
|
||||
const realValue = value && typeof value.toNumber === 'function'
|
||||
? value.toNumber()
|
||||
@ -263,7 +256,8 @@ export default class TypedInput extends Component {
|
||||
* @see https://github.com/facebook/react/issues/1549
|
||||
*/
|
||||
renderFloat (value = this.props.value, onChange = this.onChange) {
|
||||
const { label, error, param, hint, min, max } = this.props;
|
||||
const { label, error, hint, min, max } = this.props;
|
||||
const param = this.getParam();
|
||||
|
||||
const realValue = value && typeof value.toNumber === 'function'
|
||||
? value.toNumber()
|
||||
@ -379,7 +373,9 @@ export default class TypedInput extends Component {
|
||||
}
|
||||
|
||||
onAddField = () => {
|
||||
const { value, onChange, param } = this.props;
|
||||
const { value, onChange } = this.props;
|
||||
const param = this.getParam();
|
||||
|
||||
const newValues = [].concat(value, param.subtype.default);
|
||||
|
||||
onChange(newValues);
|
||||
@ -392,4 +388,14 @@ export default class TypedInput extends Component {
|
||||
onChange(newValues);
|
||||
}
|
||||
|
||||
getParam = () => {
|
||||
const { param } = this.props;
|
||||
|
||||
if (typeof param === 'string') {
|
||||
return parseAbiType(param);
|
||||
}
|
||||
|
||||
return param;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -118,6 +118,15 @@ export default class MethodDecodingStore {
|
||||
return Promise.resolve(result);
|
||||
}
|
||||
|
||||
try {
|
||||
const { signature } = this.api.util.decodeCallData(input);
|
||||
|
||||
if (signature === CONTRACT_CREATE || transaction.creates) {
|
||||
result.contract = true;
|
||||
return Promise.resolve({ ...result, deploy: true });
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
return this
|
||||
.isContract(contractAddress || transaction.creates)
|
||||
.then((isContract) => {
|
||||
@ -132,7 +141,7 @@ export default class MethodDecodingStore {
|
||||
result.params = paramdata;
|
||||
|
||||
// Contract deployment
|
||||
if (!signature || signature === CONTRACT_CREATE || transaction.creates) {
|
||||
if (!signature) {
|
||||
return Promise.resolve({ ...result, deploy: true });
|
||||
}
|
||||
|
||||
@ -192,7 +201,7 @@ export default class MethodDecodingStore {
|
||||
*/
|
||||
isContract (contractAddress) {
|
||||
// If zero address, it isn't a contract
|
||||
if (/^(0x)?0*$/.test(contractAddress)) {
|
||||
if (!contractAddress || /^(0x)?0*$/.test(contractAddress)) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Portal from 'react-portal';
|
||||
import ReactPortal from 'react-portal';
|
||||
import keycode from 'keycode';
|
||||
|
||||
import { CloseIcon } from '~/ui/Icons';
|
||||
@ -24,7 +24,7 @@ import ParityBackground from '~/ui/ParityBackground';
|
||||
|
||||
import styles from './portal.css';
|
||||
|
||||
export default class Protal extends Component {
|
||||
export default class Portal extends Component {
|
||||
|
||||
static propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
@ -65,7 +65,7 @@ export default class Protal extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<Portal isOpened onClose={ this.handleClose }>
|
||||
<ReactPortal isOpened onClose={ this.handleClose }>
|
||||
<div
|
||||
className={ classes.join(' ') }
|
||||
onKeyDown={ this.handleKeyDown }
|
||||
@ -75,7 +75,7 @@ export default class Protal extends Component {
|
||||
{ this.renderCloseIcon() }
|
||||
{ children }
|
||||
</div>
|
||||
</Portal>
|
||||
</ReactPortal>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -134,7 +134,7 @@ class TxHash extends Component {
|
||||
const { api } = this.context;
|
||||
const { hash } = this.props;
|
||||
|
||||
if (error) {
|
||||
if (error || !hash || /^(0x)?0*$/.test(hash)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -14,10 +14,93 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import WalletsUtils from '~/util/wallets';
|
||||
|
||||
const isValidReceipt = (receipt) => {
|
||||
return receipt && receipt.blockNumber && receipt.blockNumber.gt(0);
|
||||
};
|
||||
|
||||
function getTxArgs (func, options, values = []) {
|
||||
const { contract } = func;
|
||||
const { api } = contract;
|
||||
const address = options.from;
|
||||
|
||||
if (!address) {
|
||||
return Promise.resolve({ func, options, values });
|
||||
}
|
||||
|
||||
return WalletsUtils
|
||||
.isWallet(api, address)
|
||||
.then((isWallet) => {
|
||||
if (!isWallet) {
|
||||
return { func, options, values };
|
||||
}
|
||||
|
||||
options.data = contract.getCallData(func, options, values);
|
||||
options.to = options.to || contract.address;
|
||||
|
||||
if (!options.to) {
|
||||
return { func, options, values };
|
||||
}
|
||||
|
||||
return WalletsUtils
|
||||
.getCallArgs(api, options, values)
|
||||
.then((callArgs) => {
|
||||
if (!callArgs) {
|
||||
return { func, options, values };
|
||||
}
|
||||
|
||||
return callArgs;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function estimateGas (_func, _options, _values = []) {
|
||||
return getTxArgs(_func, _options, _values)
|
||||
.then((callArgs) => {
|
||||
const { func, options, values } = callArgs;
|
||||
return func._estimateGas(options, values);
|
||||
})
|
||||
.then((gas) => {
|
||||
return WalletsUtils
|
||||
.isWallet(_func.contract.api, _options.from)
|
||||
.then((isWallet) => {
|
||||
if (isWallet) {
|
||||
return gas.mul(1.5);
|
||||
}
|
||||
|
||||
return gas;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function postTransaction (_func, _options, _values = []) {
|
||||
return getTxArgs(_func, _options, _values)
|
||||
.then((callArgs) => {
|
||||
const { func, options, values } = callArgs;
|
||||
return func._postTransaction(options, values);
|
||||
});
|
||||
}
|
||||
|
||||
export function patchApi (api) {
|
||||
api.patch = {
|
||||
...api.patch,
|
||||
contract: patchContract
|
||||
};
|
||||
}
|
||||
|
||||
export function patchContract (contract) {
|
||||
contract._functions.forEach((func) => {
|
||||
if (!func.constant) {
|
||||
func._postTransaction = func.postTransaction;
|
||||
func._estimateGas = func.estimateGas;
|
||||
|
||||
func.postTransaction = postTransaction.bind(contract, func);
|
||||
func.estimateGas = estimateGas.bind(contract, func);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function checkIfTxFailed (api, tx, gasSent) {
|
||||
return api.pollMethod('eth_getTransactionReceipt', tx)
|
||||
.then((receipt) => {
|
||||
|
@ -14,13 +14,92 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { range, uniq } from 'lodash';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { intersection, range, uniq } from 'lodash';
|
||||
|
||||
import Contract from '~/api/contract';
|
||||
import { bytesToHex, toHex } from '~/api/util/format';
|
||||
import { validateAddress } from '~/util/validation';
|
||||
import WalletAbi from '~/contracts/abi/wallet.json';
|
||||
|
||||
const _cachedWalletLookup = {};
|
||||
|
||||
export default class WalletsUtils {
|
||||
|
||||
static getCallArgs (api, options, values = []) {
|
||||
const walletContract = new Contract(api, WalletAbi);
|
||||
|
||||
const promises = [
|
||||
api.parity.accountsInfo(),
|
||||
WalletsUtils.fetchOwners(walletContract.at(options.from))
|
||||
];
|
||||
|
||||
return Promise
|
||||
.all(promises)
|
||||
.then(([ accounts, owners ]) => {
|
||||
const addresses = Object.keys(accounts);
|
||||
const owner = intersection(addresses, owners).pop();
|
||||
|
||||
if (!owner) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return owner;
|
||||
})
|
||||
.then((owner) => {
|
||||
if (!owner) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const _options = Object.assign({}, options);
|
||||
const { from, to, value = new BigNumber(0), data } = options;
|
||||
|
||||
delete _options.data;
|
||||
|
||||
const nextValues = [ to, value, data ];
|
||||
const nextOptions = {
|
||||
..._options,
|
||||
from: owner,
|
||||
to: from,
|
||||
value: new BigNumber(0)
|
||||
};
|
||||
|
||||
const execFunc = walletContract.instance.execute;
|
||||
|
||||
return { func: execFunc, options: nextOptions, values: nextValues };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given address could be
|
||||
* a Wallet. The result is cached in order not
|
||||
* to make unnecessary calls on non-wallet accounts
|
||||
*/
|
||||
static isWallet (api, address) {
|
||||
if (!_cachedWalletLookup[address]) {
|
||||
const walletContract = new Contract(api, WalletAbi);
|
||||
|
||||
_cachedWalletLookup[address] = walletContract
|
||||
.at(address)
|
||||
.instance
|
||||
.m_numOwners
|
||||
.call()
|
||||
.then((result) => {
|
||||
if (!result || result.equals(0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.then((bool) => {
|
||||
_cachedWalletLookup[address] = Promise.resolve(bool);
|
||||
return bool;
|
||||
});
|
||||
}
|
||||
|
||||
return _cachedWalletLookup[address];
|
||||
}
|
||||
|
||||
static fetchRequire (walletContract) {
|
||||
return walletContract.instance.m_required.call();
|
||||
}
|
||||
|
@ -42,7 +42,6 @@ export default class Header extends Component {
|
||||
render () {
|
||||
const { account, balance, className, children, hideName } = this.props;
|
||||
const { address, meta, uuid } = account;
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
@ -34,7 +34,6 @@ class List extends Component {
|
||||
order: PropTypes.string,
|
||||
orderFallback: PropTypes.string,
|
||||
search: PropTypes.array,
|
||||
walletsOwners: PropTypes.object,
|
||||
|
||||
fetchCertifiers: PropTypes.func.isRequired,
|
||||
fetchCertifications: PropTypes.func.isRequired,
|
||||
@ -58,7 +57,7 @@ class List extends Component {
|
||||
}
|
||||
|
||||
renderAccounts () {
|
||||
const { accounts, balances, empty, link, walletsOwners, handleAddSearchToken } = this.props;
|
||||
const { accounts, balances, empty, link, handleAddSearchToken } = this.props;
|
||||
|
||||
if (empty) {
|
||||
return (
|
||||
@ -76,7 +75,7 @@ class List extends Component {
|
||||
const account = accounts[address] || {};
|
||||
const balance = balances[address] || {};
|
||||
|
||||
const owners = walletsOwners && walletsOwners[address] || null;
|
||||
const owners = account.owners || null;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -157,7 +157,11 @@ export default class Summary extends Component {
|
||||
const { link, noLink, account, name } = this.props;
|
||||
|
||||
const { address } = account;
|
||||
const viewLink = `/${link || 'accounts'}/${address}`;
|
||||
const baseLink = account.wallet
|
||||
? 'wallet'
|
||||
: link || 'accounts';
|
||||
|
||||
const viewLink = `/${baseLink}/${address}`;
|
||||
|
||||
const content = (
|
||||
<IdentityName address={ address } name={ name } unknown />
|
||||
|
@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import ContentAdd from 'material-ui/svg-icons/content/add';
|
||||
import { uniq, isEqual } from 'lodash';
|
||||
import { uniq, isEqual, pickBy, omitBy } from 'lodash';
|
||||
|
||||
import List from './List';
|
||||
import { CreateAccount, CreateWallet } from '~/modals';
|
||||
@ -36,9 +36,6 @@ class Accounts extends Component {
|
||||
setVisibleAccounts: PropTypes.func.isRequired,
|
||||
accounts: PropTypes.object.isRequired,
|
||||
hasAccounts: PropTypes.bool.isRequired,
|
||||
wallets: PropTypes.object.isRequired,
|
||||
walletsOwners: PropTypes.object.isRequired,
|
||||
hasWallets: PropTypes.bool.isRequired,
|
||||
|
||||
balances: PropTypes.object
|
||||
}
|
||||
@ -62,8 +59,8 @@ class Accounts extends Component {
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
const prevAddresses = Object.keys({ ...this.props.accounts, ...this.props.wallets });
|
||||
const nextAddresses = Object.keys({ ...nextProps.accounts, ...nextProps.wallets });
|
||||
const prevAddresses = Object.keys(this.props.accounts);
|
||||
const nextAddresses = Object.keys(nextProps.accounts);
|
||||
|
||||
if (prevAddresses.length !== nextAddresses.length || !isEqual(prevAddresses.sort(), nextAddresses.sort())) {
|
||||
this.setVisibleAccounts(nextProps);
|
||||
@ -75,8 +72,8 @@ class Accounts extends Component {
|
||||
}
|
||||
|
||||
setVisibleAccounts (props = this.props) {
|
||||
const { accounts, wallets, setVisibleAccounts } = props;
|
||||
const addresses = Object.keys({ ...accounts, ...wallets });
|
||||
const { accounts, setVisibleAccounts } = props;
|
||||
const addresses = Object.keys(accounts);
|
||||
setVisibleAccounts(addresses);
|
||||
}
|
||||
|
||||
@ -115,30 +112,38 @@ class Accounts extends Component {
|
||||
}
|
||||
|
||||
renderAccounts () {
|
||||
const { accounts, balances } = this.props;
|
||||
|
||||
const _accounts = omitBy(accounts, (a) => a.wallet);
|
||||
const _hasAccounts = Object.keys(_accounts).length > 0;
|
||||
|
||||
if (!this.state.show) {
|
||||
return this.renderLoading(this.props.accounts);
|
||||
return this.renderLoading(_accounts);
|
||||
}
|
||||
|
||||
const { accounts, hasAccounts, balances } = this.props;
|
||||
const { searchValues, sortOrder } = this.state;
|
||||
|
||||
return (
|
||||
<List
|
||||
search={ searchValues }
|
||||
accounts={ accounts }
|
||||
accounts={ _accounts }
|
||||
balances={ balances }
|
||||
empty={ !hasAccounts }
|
||||
empty={ !_hasAccounts }
|
||||
order={ sortOrder }
|
||||
handleAddSearchToken={ this.onAddSearchToken } />
|
||||
);
|
||||
}
|
||||
|
||||
renderWallets () {
|
||||
const { accounts, balances } = this.props;
|
||||
|
||||
const wallets = pickBy(accounts, (a) => a.wallet);
|
||||
const hasWallets = Object.keys(wallets).length > 0;
|
||||
|
||||
if (!this.state.show) {
|
||||
return this.renderLoading(this.props.wallets);
|
||||
return this.renderLoading(wallets);
|
||||
}
|
||||
|
||||
const { wallets, hasWallets, balances, walletsOwners } = this.props;
|
||||
const { searchValues, sortOrder } = this.state;
|
||||
|
||||
if (!wallets || Object.keys(wallets).length === 0) {
|
||||
@ -154,7 +159,6 @@ class Accounts extends Component {
|
||||
empty={ !hasWallets }
|
||||
order={ sortOrder }
|
||||
handleAddSearchToken={ this.onAddSearchToken }
|
||||
walletsOwners={ walletsOwners }
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -287,34 +291,12 @@ class Accounts extends Component {
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { accounts, hasAccounts, wallets, hasWallets, accountsInfo } = state.personal;
|
||||
const { accounts, hasAccounts } = state.personal;
|
||||
const { balances } = state.balances;
|
||||
const walletsInfo = state.wallet.wallets;
|
||||
|
||||
const walletsOwners = Object
|
||||
.keys(walletsInfo)
|
||||
.map((wallet) => {
|
||||
const owners = walletsInfo[wallet].owners || [];
|
||||
|
||||
return {
|
||||
owners: owners.map((owner) => ({
|
||||
address: owner,
|
||||
name: accountsInfo[owner] && accountsInfo[owner].name || owner
|
||||
})),
|
||||
address: wallet
|
||||
};
|
||||
})
|
||||
.reduce((walletsOwners, wallet) => {
|
||||
walletsOwners[wallet.address] = wallet.owners;
|
||||
return walletsOwners;
|
||||
}, {});
|
||||
|
||||
return {
|
||||
accounts,
|
||||
hasAccounts,
|
||||
wallets,
|
||||
walletsOwners,
|
||||
hasWallets,
|
||||
accounts: accounts,
|
||||
hasAccounts: hasAccounts,
|
||||
balances
|
||||
};
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { omitBy } from 'lodash';
|
||||
|
||||
import { AddDapps, DappPermissions } from '~/modals';
|
||||
import PermissionStore from '~/modals/DappPermissions/store';
|
||||
@ -150,8 +151,15 @@ class Dapps extends Component {
|
||||
function mapStateToProps (state) {
|
||||
const { accounts } = state.personal;
|
||||
|
||||
/**
|
||||
* Do not show the Wallet Accounts in the Dapps
|
||||
* Permissions Modal. This will come in v1.6, but
|
||||
* for now it would break dApps using Web3...
|
||||
*/
|
||||
const _accounts = omitBy(accounts, (account) => account.wallet);
|
||||
|
||||
return {
|
||||
accounts
|
||||
accounts: _accounts
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@ export default class RequestPending extends Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
date: PropTypes.instanceOf(Date).isRequired,
|
||||
focus: PropTypes.bool,
|
||||
gasLimit: PropTypes.object.isRequired,
|
||||
id: PropTypes.object.isRequired,
|
||||
isSending: PropTypes.bool.isRequired,
|
||||
@ -38,6 +39,7 @@ export default class RequestPending extends Component {
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
focus: false,
|
||||
isSending: false
|
||||
};
|
||||
|
||||
@ -49,7 +51,7 @@ export default class RequestPending extends Component {
|
||||
};
|
||||
|
||||
render () {
|
||||
const { className, date, gasLimit, id, isSending, isTest, onReject, payload, store } = this.props;
|
||||
const { className, date, focus, gasLimit, id, isSending, isTest, onReject, payload, store } = this.props;
|
||||
|
||||
if (payload.sign) {
|
||||
const { sign } = payload;
|
||||
@ -58,6 +60,7 @@ export default class RequestPending extends Component {
|
||||
<SignRequest
|
||||
address={ sign.address }
|
||||
className={ className }
|
||||
focus={ focus }
|
||||
hash={ sign.hash }
|
||||
id={ id }
|
||||
isFinished={ false }
|
||||
@ -75,6 +78,7 @@ export default class RequestPending extends Component {
|
||||
<TransactionPending
|
||||
className={ className }
|
||||
date={ date }
|
||||
focus={ focus }
|
||||
gasLimit={ gasLimit }
|
||||
id={ id }
|
||||
isSending={ isSending }
|
||||
|
@ -30,13 +30,19 @@ export default class SignRequest extends Component {
|
||||
address: PropTypes.string.isRequired,
|
||||
hash: PropTypes.string.isRequired,
|
||||
isFinished: PropTypes.bool.isRequired,
|
||||
isTest: PropTypes.bool.isRequired,
|
||||
store: PropTypes.object.isRequired,
|
||||
|
||||
className: PropTypes.string,
|
||||
focus: PropTypes.bool,
|
||||
isSending: PropTypes.bool,
|
||||
onConfirm: PropTypes.func,
|
||||
onReject: PropTypes.func,
|
||||
status: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
isTest: PropTypes.bool.isRequired,
|
||||
store: PropTypes.object.isRequired
|
||||
status: PropTypes.string
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
focus: false
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
@ -81,7 +87,7 @@ export default class SignRequest extends Component {
|
||||
}
|
||||
|
||||
renderActions () {
|
||||
const { address, isFinished, status } = this.props;
|
||||
const { address, focus, isFinished, status } = this.props;
|
||||
|
||||
if (isFinished) {
|
||||
if (status === 'confirmed') {
|
||||
@ -111,6 +117,7 @@ export default class SignRequest extends Component {
|
||||
return (
|
||||
<TransactionPendingForm
|
||||
address={ address }
|
||||
focus={ focus }
|
||||
isSending={ this.props.isSending }
|
||||
onConfirm={ this.onConfirm }
|
||||
onReject={ this.onReject }
|
||||
|
@ -35,6 +35,7 @@ export default class TransactionPending extends Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
date: PropTypes.instanceOf(Date).isRequired,
|
||||
focus: PropTypes.bool,
|
||||
gasLimit: PropTypes.object,
|
||||
id: PropTypes.object.isRequired,
|
||||
isSending: PropTypes.bool.isRequired,
|
||||
@ -53,6 +54,10 @@ export default class TransactionPending extends Component {
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
focus: false
|
||||
};
|
||||
|
||||
gasStore = new GasPriceEditor.Store(this.context.api, {
|
||||
gas: this.props.transaction.gas.toFixed(),
|
||||
gasLimit: this.props.gasLimit,
|
||||
@ -80,7 +85,7 @@ export default class TransactionPending extends Component {
|
||||
}
|
||||
|
||||
renderTransaction () {
|
||||
const { className, id, isSending, isTest, store, transaction } = this.props;
|
||||
const { className, focus, id, isSending, isTest, store, transaction } = this.props;
|
||||
const { totalValue } = this.state;
|
||||
const { from, value } = transaction;
|
||||
|
||||
@ -100,6 +105,7 @@ export default class TransactionPending extends Component {
|
||||
value={ value } />
|
||||
<TransactionPendingForm
|
||||
address={ from }
|
||||
focus={ focus }
|
||||
isSending={ isSending }
|
||||
onConfirm={ this.onConfirm }
|
||||
onReject={ this.onReject } />
|
||||
|
@ -15,6 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import RaisedButton from 'material-ui/RaisedButton';
|
||||
@ -26,11 +27,16 @@ import styles from './transactionPendingFormConfirm.css';
|
||||
|
||||
class TransactionPendingFormConfirm extends Component {
|
||||
static propTypes = {
|
||||
accounts: PropTypes.object.isRequired,
|
||||
account: PropTypes.object.isRequired,
|
||||
address: PropTypes.string.isRequired,
|
||||
isSending: PropTypes.bool.isRequired,
|
||||
onConfirm: PropTypes.func.isRequired
|
||||
}
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
focus: PropTypes.bool
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
focus: false
|
||||
};
|
||||
|
||||
id = Math.random(); // for tooltip
|
||||
|
||||
@ -40,10 +46,39 @@ class TransactionPendingFormConfirm extends Component {
|
||||
walletError: null
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.focus();
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (!this.props.focus && nextProps.focus) {
|
||||
this.focus(nextProps);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Properly focus on the input element when needed.
|
||||
* This might be fixed some day in MaterialUI with
|
||||
* an autoFocus prop.
|
||||
*
|
||||
* @see https://github.com/callemall/material-ui/issues/5632
|
||||
*/
|
||||
focus (props = this.props) {
|
||||
if (props.focus) {
|
||||
const textNode = ReactDOM.findDOMNode(this.refs.input);
|
||||
|
||||
if (!textNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inputNode = textNode.querySelector('input');
|
||||
inputNode && inputNode.focus();
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { accounts, address, isSending } = this.props;
|
||||
const { account, address, isSending } = this.props;
|
||||
const { password, wallet, walletError } = this.state;
|
||||
const account = accounts[address] || {};
|
||||
const isExternal = !account.uuid;
|
||||
|
||||
const passwordHint = account.meta && account.meta.passwordHint
|
||||
@ -72,8 +107,10 @@ class TransactionPendingFormConfirm extends Component {
|
||||
}
|
||||
onChange={ this.onModifyPassword }
|
||||
onKeyDown={ this.onKeyDown }
|
||||
ref='input'
|
||||
type='password'
|
||||
value={ password } />
|
||||
value={ password }
|
||||
/>
|
||||
<div className={ styles.passwordHint }>
|
||||
{ passwordHint }
|
||||
</div>
|
||||
@ -178,11 +215,14 @@ class TransactionPendingFormConfirm extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { accounts } = state.personal;
|
||||
function mapStateToProps (initState, initProps) {
|
||||
const { accounts } = initState.personal;
|
||||
const { address } = initProps;
|
||||
|
||||
return {
|
||||
accounts
|
||||
const account = accounts[address] || {};
|
||||
|
||||
return () => {
|
||||
return { account };
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,12 @@ export default class TransactionPendingForm extends Component {
|
||||
isSending: PropTypes.bool.isRequired,
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
onReject: PropTypes.func.isRequired,
|
||||
className: PropTypes.string
|
||||
className: PropTypes.string,
|
||||
focus: PropTypes.bool
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
focus: false
|
||||
};
|
||||
|
||||
state = {
|
||||
@ -47,7 +52,7 @@ export default class TransactionPendingForm extends Component {
|
||||
}
|
||||
|
||||
renderForm () {
|
||||
const { address, isSending, onConfirm, onReject } = this.props;
|
||||
const { address, focus, isSending, onConfirm, onReject } = this.props;
|
||||
|
||||
if (this.state.isRejectOpen) {
|
||||
return (
|
||||
@ -59,8 +64,10 @@ export default class TransactionPendingForm extends Component {
|
||||
return (
|
||||
<TransactionPendingFormConfirm
|
||||
address={ address }
|
||||
focus={ focus }
|
||||
isSending={ isSending }
|
||||
onConfirm={ onConfirm } />
|
||||
onConfirm={ onConfirm }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ class Embedded extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderPending = (data) => {
|
||||
renderPending = (data, index) => {
|
||||
const { actions, gasLimit, isTest } = this.props;
|
||||
const { date, id, isSending, payload } = data;
|
||||
|
||||
@ -86,6 +86,7 @@ class Embedded extends Component {
|
||||
<RequestPending
|
||||
className={ styles.request }
|
||||
date={ date }
|
||||
focus={ index === 0 }
|
||||
gasLimit={ gasLimit }
|
||||
id={ id }
|
||||
isSending={ isSending }
|
||||
|
@ -104,7 +104,7 @@ class RequestsPage extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderPending = (data) => {
|
||||
renderPending = (data, index) => {
|
||||
const { actions, gasLimit, isTest } = this.props;
|
||||
const { date, id, isSending, payload } = data;
|
||||
|
||||
@ -112,6 +112,7 @@ class RequestsPage extends Component {
|
||||
<RequestPending
|
||||
className={ styles.request }
|
||||
date={ date }
|
||||
focus={ index === 0 }
|
||||
gasLimit={ gasLimit }
|
||||
id={ id }
|
||||
isSending={ isSending }
|
||||
|
@ -55,14 +55,20 @@ export default class WalletDetails extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ownersList = owners.map((address, idx) => (
|
||||
<InputAddress
|
||||
key={ `${idx}_${address}` }
|
||||
value={ address }
|
||||
disabled
|
||||
text
|
||||
/>
|
||||
));
|
||||
const ownersList = owners.map((owner, idx) => {
|
||||
const address = typeof owner === 'object'
|
||||
? owner.address
|
||||
: owner;
|
||||
|
||||
return (
|
||||
<InputAddress
|
||||
key={ `${idx}_${address}` }
|
||||
value={ address }
|
||||
disabled
|
||||
text
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -57,12 +57,12 @@ export default class WalletTransactions extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
const txRows = transactions.map((transaction) => {
|
||||
const txRows = transactions.slice(0, 15).map((transaction, index) => {
|
||||
const { transactionHash, blockNumber, from, to, value, data } = transaction;
|
||||
|
||||
return (
|
||||
<TxRow
|
||||
key={ transactionHash }
|
||||
key={ `${transactionHash}_${index}` }
|
||||
tx={ {
|
||||
hash: transactionHash,
|
||||
input: data && bytesToHex(data) || '',
|
||||
|
@ -64,13 +64,14 @@ class Wallet extends Component {
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
setVisibleAccounts: PropTypes.func.isRequired,
|
||||
address: PropTypes.string.isRequired,
|
||||
balance: nullableProptype(PropTypes.object.isRequired),
|
||||
images: PropTypes.object.isRequired,
|
||||
address: PropTypes.string.isRequired,
|
||||
wallets: PropTypes.object.isRequired,
|
||||
isTest: PropTypes.bool.isRequired,
|
||||
owned: PropTypes.bool.isRequired,
|
||||
setVisibleAccounts: PropTypes.func.isRequired,
|
||||
wallet: PropTypes.object.isRequired,
|
||||
isTest: PropTypes.bool.isRequired
|
||||
walletAccount: nullableProptype(PropTypes.object).isRequired
|
||||
};
|
||||
|
||||
state = {
|
||||
@ -104,28 +105,26 @@ class Wallet extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { wallets, balance, address } = this.props;
|
||||
const { walletAccount, balance, wallet } = this.props;
|
||||
|
||||
const wallet = (wallets || {})[address];
|
||||
|
||||
if (!wallet) {
|
||||
if (!walletAccount) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { owners, require, dailylimit } = this.props.wallet;
|
||||
const { owners, require, dailylimit } = wallet;
|
||||
|
||||
return (
|
||||
<div className={ styles.wallet }>
|
||||
{ this.renderEditDialog(wallet) }
|
||||
{ this.renderEditDialog(walletAccount) }
|
||||
{ this.renderSettingsDialog() }
|
||||
{ this.renderTransferDialog() }
|
||||
{ this.renderDeleteDialog(wallet) }
|
||||
{ this.renderDeleteDialog(walletAccount) }
|
||||
{ this.renderActionbar() }
|
||||
<Page>
|
||||
<div className={ styles.info }>
|
||||
<Header
|
||||
className={ styles.header }
|
||||
account={ wallet }
|
||||
account={ walletAccount }
|
||||
balance={ balance }
|
||||
isContract
|
||||
>
|
||||
@ -209,32 +208,47 @@ class Wallet extends Component {
|
||||
}
|
||||
|
||||
renderActionbar () {
|
||||
const { balance } = this.props;
|
||||
const { balance, owned } = this.props;
|
||||
const showTransferButton = !!(balance && balance.tokens);
|
||||
|
||||
const buttons = [
|
||||
<Button
|
||||
key='transferFunds'
|
||||
icon={ <ContentSend /> }
|
||||
label='transfer'
|
||||
disabled={ !showTransferButton }
|
||||
onClick={ this.onTransferClick } />,
|
||||
const buttons = [];
|
||||
|
||||
if (owned) {
|
||||
buttons.push(
|
||||
<Button
|
||||
key='transferFunds'
|
||||
icon={ <ContentSend /> }
|
||||
label='transfer'
|
||||
disabled={ !showTransferButton }
|
||||
onClick={ this.onTransferClick } />
|
||||
);
|
||||
}
|
||||
|
||||
buttons.push(
|
||||
<Button
|
||||
key='delete'
|
||||
icon={ <ActionDelete /> }
|
||||
label='delete'
|
||||
onClick={ this.showDeleteDialog } />,
|
||||
onClick={ this.showDeleteDialog } />
|
||||
);
|
||||
|
||||
buttons.push(
|
||||
<Button
|
||||
key='editmeta'
|
||||
icon={ <ContentCreate /> }
|
||||
label='edit'
|
||||
onClick={ this.onEditClick } />,
|
||||
<Button
|
||||
key='settings'
|
||||
icon={ <SettingsIcon /> }
|
||||
label='settings'
|
||||
onClick={ this.onSettingsClick } />
|
||||
];
|
||||
onClick={ this.onEditClick } />
|
||||
);
|
||||
|
||||
if (owned) {
|
||||
buttons.push(
|
||||
<Button
|
||||
key='settings'
|
||||
icon={ <SettingsIcon /> }
|
||||
label='settings'
|
||||
onClick={ this.onSettingsClick } />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Actionbar
|
||||
@ -293,12 +307,11 @@ class Wallet extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { wallets, balance, images, address } = this.props;
|
||||
const wallet = wallets[address];
|
||||
const { walletAccount, balance, images } = this.props;
|
||||
|
||||
return (
|
||||
<Transfer
|
||||
account={ wallet }
|
||||
account={ walletAccount }
|
||||
balance={ balance }
|
||||
images={ images }
|
||||
onClose={ this.onTransferClose }
|
||||
@ -342,20 +355,27 @@ function mapStateToProps (_, initProps) {
|
||||
|
||||
return (state) => {
|
||||
const { isTest } = state.nodeStatus;
|
||||
const { wallets } = state.personal;
|
||||
const { accountsInfo = {}, accounts = {} } = state.personal;
|
||||
const { balances } = state.balances;
|
||||
const { images } = state;
|
||||
const walletAccount = accounts[address] || accountsInfo[address] || null;
|
||||
|
||||
if (walletAccount) {
|
||||
walletAccount.address = address;
|
||||
}
|
||||
|
||||
const wallet = state.wallet.wallets[address] || {};
|
||||
const balance = balances[address] || null;
|
||||
const owned = !!accounts[address];
|
||||
|
||||
return {
|
||||
isTest,
|
||||
wallets,
|
||||
address,
|
||||
balance,
|
||||
images,
|
||||
address,
|
||||
wallet
|
||||
isTest,
|
||||
owned,
|
||||
wallet,
|
||||
walletAccount
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -38,6 +38,13 @@ module.exports = {
|
||||
library: '[name].js',
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
'~': path.resolve(__dirname, '../src')
|
||||
}
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
|
@ -69,6 +69,9 @@ module.exports = {
|
||||
},
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
'~': path.resolve(__dirname, '../src')
|
||||
},
|
||||
modules: [
|
||||
path.resolve('./src'),
|
||||
path.join(__dirname, '../node_modules')
|
||||
|
@ -64,6 +64,13 @@ module.exports = {
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
'~': path.resolve(__dirname, '../src')
|
||||
}
|
||||
},
|
||||
|
||||
output: {
|
||||
filename: '[name].js',
|
||||
path: path.resolve(__dirname, '../', `${DEST}/`),
|
||||
|
Loading…
Reference in New Issue
Block a user