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