Staking overview & actions (#171)
* Staking overview & actions * Show session countdown * Accounts with stake/Unstake buttons * Small layout tweaks * Cleanup multi-params from storage * Submission/click works... * Cleanups * Render split * Case sensitivity * Doc links * Icon to indicate validating
This commit is contained in:
parent
b3e6bd113f
commit
cf8a24a8da
@ -23,6 +23,7 @@ The repo is split into a number of packages, each representing an application. T
|
||||
- [app-explorer](packages/app-explorer/) A simple block explorer. It only shows the most recent blocks, updating as they become available.
|
||||
- [app-extrinsics](packages/app-extrinsics/) Submission of extrinsics to a node.
|
||||
- [app-rpc](packages/app-rpc/) Sumission of raw data to RPC endpoints.
|
||||
- [app-staking](packages/app-staking/) A basic staking management app.
|
||||
- [app-storage](packages/app-storage/) A simple node storage query application. Multiple queries can be queued and updates as new values become available.
|
||||
- [app-toolbox](packages/app-toolbox/) Utilities to manage data.
|
||||
- [app-vanitygen](packages/app-vanitygen/) A toy that allows you to generate vanity addresses. Running `yarn run vanitygen --match <string>` runs the generator as a Node CLI app. (Orders of a magnitude faster due to the use of libsoldium bindings)
|
||||
|
||||
@ -2,7 +2,7 @@ const config = require('@polkadot/dev-react/config/jest');
|
||||
|
||||
module.exports = Object.assign({}, config, {
|
||||
moduleNameMapper: {
|
||||
'@polkadot/app-(accounts|addresses|example|explorer|extrinsics|rpc|storage|toolbox|vanitygen)(.*)$': '<rootDir>/packages/ui-$1/src/$2',
|
||||
'@polkadot/app-(accounts|addresses|example|explorer|extrinsics|rpc|staking|storage|toolbox|vanitygen)(.*)$': '<rootDir>/packages/ui-$1/src/$2',
|
||||
'@polkadot/ui-(app|identicon|keyring|react-rx|react|signer)(.*)$': '<rootDir>/packages/ui-$1/src/$2',
|
||||
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 'empty/object',
|
||||
'\\.(css|less)$': 'empty/object'
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { BareProps } from '@polkadot/ui-app/types';
|
||||
import { I18nProps } from '@polkadot/ui-app/types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
@ -14,7 +14,9 @@ import Nonce from '@polkadot/ui-react-rx/Nonce';
|
||||
import addressDecode from '@polkadot/util-keyring/address/decode';
|
||||
import addressEncode from '@polkadot/util-keyring/address/encode';
|
||||
|
||||
type Props = BareProps & {
|
||||
import translate from './translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
value: string;
|
||||
};
|
||||
|
||||
@ -28,7 +30,7 @@ type State = {
|
||||
const DEFAULT_ADDR = '5'.padEnd(16, 'x');
|
||||
const DEFAULT_SHORT = `${DEFAULT_ADDR.slice(0, 7)}…${DEFAULT_ADDR.slice(-7)}`;
|
||||
|
||||
export default class Address extends React.PureComponent<Props, State> {
|
||||
class Address extends React.PureComponent<Props, State> {
|
||||
state: State = {} as State;
|
||||
|
||||
static getDerivedStateFromProps ({ value }: Props, { address, publicKey, shortValue }: State): State {
|
||||
@ -76,6 +78,7 @@ export default class Address extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
renderBalance () {
|
||||
const { t } = this.props;
|
||||
const { isValid, publicKey } = this.state;
|
||||
|
||||
if (!isValid) {
|
||||
@ -86,7 +89,9 @@ export default class Address extends React.PureComponent<Props, State> {
|
||||
<Balance
|
||||
className='accounts--Address-balance'
|
||||
key='balance'
|
||||
label='balance '
|
||||
label={t('address.balance', {
|
||||
defaultValue: 'balance '
|
||||
})}
|
||||
params={publicKey}
|
||||
/>,
|
||||
<Nonce
|
||||
@ -94,8 +99,12 @@ export default class Address extends React.PureComponent<Props, State> {
|
||||
key='nonce'
|
||||
params={publicKey}
|
||||
>
|
||||
{' transactions'}
|
||||
{t('address.transactions', {
|
||||
defaultValue: ' transactions'
|
||||
})}
|
||||
</Nonce>
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(Address);
|
||||
|
||||
15
packages/app-staking/LICENSE
Normal file
15
packages/app-staking/LICENSE
Normal file
@ -0,0 +1,15 @@
|
||||
ISC License (ISC)
|
||||
|
||||
Copyright 2017-2018 @polkadot/app-staking authors & contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
1
packages/app-staking/README.md
Normal file
1
packages/app-staking/README.md
Normal file
@ -0,0 +1 @@
|
||||
# @polkadot/app-staking
|
||||
21
packages/app-staking/package.json
Normal file
21
packages/app-staking/package.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@polkadot/app-staking",
|
||||
"version": "0.19.11",
|
||||
"description": "A basic staking app",
|
||||
"main": "index.js",
|
||||
"scripts": {},
|
||||
"author": "Jaco Greeff <jacogr@gmail.com>",
|
||||
"maintainers": [
|
||||
"Jaco Greeff <jacogr@gmail.com>"
|
||||
],
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.0.0-beta.51",
|
||||
"@polkadot/storage": "^0.26.33",
|
||||
"@polkadot/ui-app": "^0.19.9",
|
||||
"@polkadot/ui-keyring": "^0.19.9",
|
||||
"@polkadot/ui-react": "^0.19.9",
|
||||
"@polkadot/ui-react-rx": "^0.19.9",
|
||||
"@polkadot/util-keyring": "^0.26.33"
|
||||
}
|
||||
}
|
||||
145
packages/app-staking/src/Account.tsx
Normal file
145
packages/app-staking/src/Account.tsx
Normal file
@ -0,0 +1,145 @@
|
||||
// Copyright 2017-2018 @polkadot/app-staking authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { I18nProps } from '@polkadot/ui-app/types';
|
||||
import { Extrinsics } from '@polkadot/extrinsics/types';
|
||||
import { SectionItem } from '@polkadot/params/types';
|
||||
import { RawParam$Value } from '@polkadot/ui-app/Params/types';
|
||||
import { QueueTx$ExtrinsicAdd } from '@polkadot/ui-signer/types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
import extrinsics from '@polkadot/extrinsics';
|
||||
import Button from '@polkadot/ui-app/Button';
|
||||
import Icon from '@polkadot/ui-app/Icon';
|
||||
import classes from '@polkadot/ui-app/util/classes';
|
||||
import IdentityIcon from '@polkadot/ui-react/IdentityIcon';
|
||||
import RxBalance from '@polkadot/ui-react-rx/Balance';
|
||||
import RxNonce from '@polkadot/ui-react-rx/Nonce';
|
||||
import decodeAddress from '@polkadot/util-keyring/address/decode';
|
||||
|
||||
import translate from './translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
address: string,
|
||||
name: string,
|
||||
nonce?: BN,
|
||||
intentionPosition: number,
|
||||
isIntending: boolean,
|
||||
isValidator: boolean,
|
||||
queueExtrinsic: QueueTx$ExtrinsicAdd
|
||||
};
|
||||
|
||||
type State = {
|
||||
nonce: BN;
|
||||
};
|
||||
|
||||
class Account extends React.PureComponent<Props, State> {
|
||||
constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
nonce: new BN(0)
|
||||
};
|
||||
}
|
||||
render () {
|
||||
const { className, isIntending, style, t } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes('staking--Account', className)}
|
||||
style={style}
|
||||
>
|
||||
{this.renderAccount()}
|
||||
<Button
|
||||
isDisabled={isIntending}
|
||||
isPrimary
|
||||
onClick={this.stake}
|
||||
text={t('account.stake', {
|
||||
defaultValue: 'stake'
|
||||
})}
|
||||
/>
|
||||
<Button
|
||||
isDisabled={!isIntending}
|
||||
isNegative
|
||||
onClick={this.unstake}
|
||||
text={t('account.unstake', {
|
||||
defaultValue: 'unstake'
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderAccount () {
|
||||
const { address, isValidator, name, t } = this.props;
|
||||
const addrShort = `${address.slice(0, 7)}…${address.slice(-7)}`;
|
||||
|
||||
return (
|
||||
<div className='staking--Account-details'>
|
||||
<div>
|
||||
<Icon
|
||||
className={classes('staking--Account-validating', isValidator ? 'isValidator' : '')}
|
||||
name='certificate'
|
||||
size='large'
|
||||
/>
|
||||
<IdentityIcon
|
||||
className='staking--Account-icon'
|
||||
size={32}
|
||||
value={address}
|
||||
/>
|
||||
<div className='staking--Account-info'>
|
||||
<div className='staking--Account-name'>{name}</div>
|
||||
<div className='staking--Account-address'>{addrShort}</div>
|
||||
</div>
|
||||
</div>
|
||||
<RxBalance
|
||||
className='staking--Account-balance'
|
||||
label={t('account.balance', {
|
||||
defaultValue: 'balance '
|
||||
})}
|
||||
params={address}
|
||||
/>
|
||||
<RxNonce
|
||||
className='staking--Account-nonce'
|
||||
onChange={this.onChangeNonce}
|
||||
params={address}
|
||||
>
|
||||
{t('account.transactions', {
|
||||
defaultValue: ' transactions'
|
||||
})}
|
||||
</RxNonce>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private send (extrinsic: SectionItem<Extrinsics>, values: Array<RawParam$Value>) {
|
||||
const { address, queueExtrinsic } = this.props;
|
||||
const { nonce } = this.state;
|
||||
const publicKey = decodeAddress(address);
|
||||
|
||||
queueExtrinsic({
|
||||
extrinsic,
|
||||
nonce,
|
||||
publicKey,
|
||||
values
|
||||
});
|
||||
}
|
||||
|
||||
private stake = () => {
|
||||
this.send(extrinsics.staking.public.stake, []);
|
||||
}
|
||||
|
||||
private unstake = () => {
|
||||
const { intentionPosition } = this.props;
|
||||
|
||||
this.send(extrinsics.staking.public.unstake, [intentionPosition]);
|
||||
}
|
||||
|
||||
private onChangeNonce = (nonce: BN) => {
|
||||
this.setState({ nonce });
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(Account);
|
||||
55
packages/app-staking/src/StakeList.tsx
Normal file
55
packages/app-staking/src/StakeList.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright 2017-2018 @polkadot/app-staking authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { I18nProps } from '@polkadot/ui-app/types';
|
||||
import { QueueProps } from '@polkadot/ui-signer/types';
|
||||
|
||||
import React from 'react';
|
||||
import classes from '@polkadot/ui-app/util/classes';
|
||||
import keyring from '@polkadot/ui-keyring/index';
|
||||
import { QueueConsumer } from '@polkadot/ui-signer/Context';
|
||||
|
||||
import Account from './Account';
|
||||
import translate from './translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
intentions: Array<string>,
|
||||
validators: Array<string>
|
||||
};
|
||||
|
||||
class StakeList extends React.PureComponent<Props> {
|
||||
render () {
|
||||
const { className, intentions, style, validators } = this.props;
|
||||
|
||||
return (
|
||||
<QueueConsumer>
|
||||
{({ queueExtrinsic }: QueueProps) => (
|
||||
<div
|
||||
className={classes('staking--StakeList', className)}
|
||||
style={style}
|
||||
>
|
||||
{keyring.getAccounts().map((account) => {
|
||||
const address = account.address();
|
||||
const name = account.getMeta().name || '';
|
||||
|
||||
return (
|
||||
<Account
|
||||
address={address}
|
||||
intentionPosition={intentions.indexOf(address)}
|
||||
isIntending={intentions.includes(address)}
|
||||
isValidator={validators.includes(address)}
|
||||
key={address}
|
||||
name={name}
|
||||
queueExtrinsic={queueExtrinsic}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</QueueConsumer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(StakeList);
|
||||
194
packages/app-staking/src/Summary.tsx
Normal file
194
packages/app-staking/src/Summary.tsx
Normal file
@ -0,0 +1,194 @@
|
||||
// Copyright 2017-2018 @polkadot/app-staking authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { Header } from '@polkadot/primitives/header';
|
||||
import { I18nProps } from '@polkadot/ui-app/types';
|
||||
import { ApiProps } from '@polkadot/ui-react-rx/types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
import apimethods from '@polkadot/jsonrpc';
|
||||
import storage from '@polkadot/storage';
|
||||
import classes from '@polkadot/ui-app/util/classes';
|
||||
import withApiCall from '@polkadot/ui-react-rx/with/apiCall';
|
||||
import withMulti from '@polkadot/ui-react-rx/with/multi';
|
||||
import withStorage from '@polkadot/ui-react-rx/with/storage';
|
||||
|
||||
import translate from './translate';
|
||||
|
||||
type Props = ApiProps & I18nProps & {
|
||||
intentions: Array<string>,
|
||||
lastBlockHeader?: Header,
|
||||
lastSessionChange?: BN,
|
||||
sessionLength?: BN,
|
||||
validators: Array<string>
|
||||
};
|
||||
|
||||
type StateBalances = {
|
||||
[index: string]: BN
|
||||
};
|
||||
|
||||
type State = {
|
||||
balances: StateBalances,
|
||||
subscriptions: Array<any>
|
||||
};
|
||||
|
||||
const DEFAULT_BALANCE = new BN(0);
|
||||
const DEFAULT_BLOCKNUMBER = new BN(0);
|
||||
const DEFAULT_SESSION_CHANGE = new BN(0);
|
||||
const DEFAULT_SESSION_LENGTH = new BN(60);
|
||||
|
||||
class Summary extends React.PureComponent<Props, State> {
|
||||
constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
balances: {},
|
||||
subscriptions: []
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps: Props) {
|
||||
const { intentions } = this.props;
|
||||
|
||||
if (intentions !== prevProps.intentions) {
|
||||
this.subscribeBalances(intentions);
|
||||
}
|
||||
}
|
||||
|
||||
private subscribeBalances (accounts: string[]) {
|
||||
const { api } = this.props;
|
||||
const { balances, subscriptions } = this.state;
|
||||
const newBalances = { ...balances };
|
||||
|
||||
accounts.forEach((account) => {
|
||||
if (newBalances[account]) {
|
||||
return;
|
||||
} else {
|
||||
newBalances[account] = DEFAULT_BALANCE;
|
||||
}
|
||||
|
||||
subscriptions.push(
|
||||
api.state
|
||||
// Here we pass a parameter to the key generator, so it points to the correct storage entry
|
||||
.getStorage(storage.staking.public.freeBalanceOf, account)
|
||||
.subscribe((balance: BN) => {
|
||||
this.setState(({ balances }: State) => {
|
||||
const newBalances = { ...balances };
|
||||
|
||||
newBalances[account] = balance;
|
||||
|
||||
return {
|
||||
balances: newBalances
|
||||
} as State;
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
this.setState({
|
||||
balances: newBalances,
|
||||
subscriptions
|
||||
} as State);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
const { subscriptions } = this.state;
|
||||
|
||||
subscriptions.forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className, intentions, lastBlockHeader, lastSessionChange = DEFAULT_SESSION_CHANGE, style, sessionLength = DEFAULT_SESSION_LENGTH, t, validators } = this.props;
|
||||
const blockNumber = lastBlockHeader
|
||||
? lastBlockHeader.number
|
||||
: DEFAULT_BLOCKNUMBER;
|
||||
const intentionHigh = this.calcIntentionsHigh();
|
||||
const validatorLow = this.calcValidatorLow();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes('staking--Summary', className)}
|
||||
style={style}
|
||||
>
|
||||
<div>{t('summary.headline', {
|
||||
defaultValue: '{{validatorCount}} validators, {{intentionCount}} accounts with intentions',
|
||||
replace: {
|
||||
intentionCount: intentions.length,
|
||||
validatorCount: validators.length
|
||||
}
|
||||
})}</div>
|
||||
<div>{t('summary.balance.validator', {
|
||||
defaultValue: 'lowest validator balance is {{validatorLow}}',
|
||||
replace: {
|
||||
validatorLow: validatorLow ? validatorLow.toString() : 'unknown'
|
||||
}
|
||||
})}</div>
|
||||
<div>{t('summary.balance.stake', {
|
||||
defaultValue: ' highest balance intending to stake is {{intentionHigh}}',
|
||||
replace: {
|
||||
intentionHigh: intentionHigh ? intentionHigh.toString() : 'unknown'
|
||||
}
|
||||
})}</div>
|
||||
<div>{t('summary.countdown', {
|
||||
defaultValue: 'session block {{remainder}} / {{length}} at #{{blockNumber}}',
|
||||
replace: {
|
||||
blockNumber: blockNumber.toString(),
|
||||
remainder: Math.max(1, blockNumber.sub(lastSessionChange).mod(sessionLength).addn(1).toNumber()).toString(),
|
||||
length: sessionLength.toString()
|
||||
}
|
||||
})}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private calcIntentionsHigh (): BN | null {
|
||||
const { intentions, validators } = this.props;
|
||||
const { balances } = this.state;
|
||||
|
||||
return intentions.reduce((high: BN | null, addr) => {
|
||||
const balance = validators.includes(addr)
|
||||
? null
|
||||
: balances[addr] || null;
|
||||
|
||||
if (high === null || (balance && high.lt(balance))) {
|
||||
return balance;
|
||||
}
|
||||
|
||||
return high;
|
||||
}, null);
|
||||
}
|
||||
|
||||
private calcValidatorLow (): BN | null {
|
||||
const { validators } = this.props;
|
||||
const { balances } = this.state;
|
||||
|
||||
return validators.reduce((low: BN | null, addr) => {
|
||||
const balance = balances[addr] || null;
|
||||
|
||||
if (low === null || (balance && low.gt(balance))) {
|
||||
return balance;
|
||||
}
|
||||
|
||||
return low;
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
|
||||
export default withMulti(
|
||||
Summary,
|
||||
translate,
|
||||
withApiCall(
|
||||
apimethods.chain.public.newHead,
|
||||
{ propName: 'lastBlockHeader' }
|
||||
),
|
||||
withStorage(
|
||||
storage.session.public.length,
|
||||
{ propName: 'sessionLength' }
|
||||
),
|
||||
withStorage(
|
||||
storage.session.public.lastSessionChange,
|
||||
{ propName: 'lastSessionChange' }
|
||||
)
|
||||
);
|
||||
58
packages/app-staking/src/index.css
Normal file
58
packages/app-staking/src/index.css
Normal file
@ -0,0 +1,58 @@
|
||||
/* Copyright 2017-2018 @polkadot/app-staking authors & contributors
|
||||
/* This software may be modified and distributed under the terms
|
||||
/* of the ISC license. See the LICENSE file for details. */
|
||||
|
||||
.staking--App {
|
||||
padding: 1em 2rem;
|
||||
}
|
||||
|
||||
.staking--Summary {
|
||||
margin-bottom: 2rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.staking--Account {
|
||||
text-align: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.staking--Account > .button {
|
||||
margin-top: 1.25rem;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.staking--Account-details {
|
||||
display: inline-block;
|
||||
margin-right: 1rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.staking--Account-info {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.staking--Account-address {
|
||||
font-family: monospace;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.staking--Account-icon {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.staking--Account-validating {
|
||||
opacity: 0.25 !important;
|
||||
padding-top: 8px;
|
||||
vertical-align: top !important;
|
||||
}
|
||||
|
||||
.staking--Account-validating.isValidator {
|
||||
color: red;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.staking--Account-balance,
|
||||
.staking--Account-nonce {
|
||||
opacity: 0.45;
|
||||
text-align: right;
|
||||
}
|
||||
65
packages/app-staking/src/index.tsx
Normal file
65
packages/app-staking/src/index.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright 2017-2018 @polkadot/app-staking authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { BareProps } from '@polkadot/ui-app/types';
|
||||
|
||||
import React from 'react';
|
||||
import storage from '@polkadot/storage';
|
||||
import encodeAddress from '@polkadot/util-keyring/address/encode';
|
||||
import classes from '@polkadot/ui-app/util/classes';
|
||||
import withStorage from '@polkadot/ui-react-rx/with/storage';
|
||||
import withMulti from '@polkadot/ui-react-rx/with/multi';
|
||||
|
||||
import './index.css';
|
||||
|
||||
import StakeList from './StakeList';
|
||||
import Summary from './Summary';
|
||||
|
||||
type Props = BareProps & {
|
||||
intentions?: Array<string>,
|
||||
validators?: Array<string>
|
||||
};
|
||||
|
||||
const transformAddress = (publicKeys: Array<Uint8Array>) =>
|
||||
publicKeys.map(encodeAddress);
|
||||
|
||||
class App extends React.PureComponent<Props> {
|
||||
render () {
|
||||
const { className, intentions = [], style, validators = [] } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes('staking--App', className)}
|
||||
style={style}
|
||||
>
|
||||
<Summary
|
||||
intentions={intentions}
|
||||
validators={validators}
|
||||
/>
|
||||
<StakeList
|
||||
intentions={intentions}
|
||||
validators={validators}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withMulti(
|
||||
App,
|
||||
withStorage(
|
||||
storage.staking.public.intentions,
|
||||
{
|
||||
propName: 'intentions',
|
||||
transform: transformAddress
|
||||
}
|
||||
),
|
||||
withStorage(
|
||||
storage.session.public.validators,
|
||||
{
|
||||
propName: 'validators',
|
||||
transform: transformAddress
|
||||
}
|
||||
)
|
||||
);
|
||||
7
packages/app-staking/src/translate.ts
Normal file
7
packages/app-staking/src/translate.ts
Normal file
@ -0,0 +1,7 @@
|
||||
// Copyright 2017-2018 @polkadot/app-staking authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { translate } from 'react-i18next';
|
||||
|
||||
export default translate(['staking', 'ui']);
|
||||
@ -23,6 +23,7 @@
|
||||
"@polkadot/app-explorer": "^0.19.11",
|
||||
"@polkadot/app-extrinsics": "^0.19.11",
|
||||
"@polkadot/app-rpc": "^0.19.11",
|
||||
"@polkadot/app-staking": "^0.19.11",
|
||||
"@polkadot/app-storage": "^0.19.11",
|
||||
"@polkadot/app-toolbox": "^0.19.11",
|
||||
"@polkadot/app-vanitygen": "^0.19.11",
|
||||
|
||||
@ -12,6 +12,7 @@ import extrinsics from './extrinsics';
|
||||
// import home from './home';
|
||||
import rpc from './rpc';
|
||||
// import settings from './settings';
|
||||
import staking from './staking';
|
||||
import storage from './storage';
|
||||
import toolbox from './toolbox';
|
||||
import unknown from './unknown';
|
||||
@ -20,7 +21,7 @@ import vanitygen from './vanitygen';
|
||||
export default ({
|
||||
default: 'explorer',
|
||||
routes: ([] as Routes).concat(
|
||||
example, explorer, extrinsics, storage, null, accounts, addresses, vanitygen, null, rpc, toolbox
|
||||
example, explorer, staking, extrinsics, storage, null, accounts, addresses, vanitygen, null, rpc, toolbox
|
||||
),
|
||||
unknown
|
||||
} as Routing);
|
||||
|
||||
20
packages/apps/src/routing/staking.ts
Normal file
20
packages/apps/src/routing/staking.ts
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2017-2018 @polkadot/apps authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { Routes } from '../types';
|
||||
|
||||
import Staking from '@polkadot/app-staking/index';
|
||||
|
||||
export default ([
|
||||
{
|
||||
Component: Staking,
|
||||
i18n: {
|
||||
defaultValue: 'Staking'
|
||||
},
|
||||
icon: 'certificate',
|
||||
isExact: false,
|
||||
isHidden: false,
|
||||
name: 'staking'
|
||||
}
|
||||
] as Routes);
|
||||
@ -10,7 +10,7 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
|
||||
const packages = ['app-accounts', 'app-addresses', 'app-example', 'app-explorer', 'app-extrinsics', 'app-rpc', 'app-storage', 'app-toolbox', 'app-vanitygen', 'ui-app', 'ui-identicon', 'ui-keyring', 'ui-react-rx', 'ui-react', 'ui-signer'];
|
||||
const packages = ['app-accounts', 'app-addresses', 'app-example', 'app-explorer', 'app-extrinsics', 'app-rpc', 'app-staking', 'app-storage', 'app-toolbox', 'app-vanitygen', 'ui-app', 'ui-identicon', 'ui-keyring', 'ui-react-rx', 'ui-react', 'ui-signer'];
|
||||
|
||||
function createWebpack ({ alias = {}, context, name = 'index' }) {
|
||||
const pkgJson = require(path.join(context, 'package.json'));
|
||||
|
||||
16
packages/ui-keyring/src/account/all.ts
Normal file
16
packages/ui-keyring/src/account/all.ts
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright 2017-2018 @polkadot/ui-keyring authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { KeyringAddress, State } from '../types';
|
||||
|
||||
import get from '../address/get';
|
||||
|
||||
export default function all (state: State): Array<KeyringAddress> {
|
||||
return Object
|
||||
.keys(state.available.account)
|
||||
.map((address) =>
|
||||
get(state, address, 'account')
|
||||
)
|
||||
.filter((account) => !account.getMeta().isTesting);
|
||||
}
|
||||
@ -8,7 +8,7 @@ import isString from '@polkadot/util/is/string';
|
||||
import addressDecode from '@polkadot/util-keyring/address/decode';
|
||||
import addressEncode from '@polkadot/util-keyring/address/encode';
|
||||
|
||||
export default function get (state: State, _address: string | Uint8Array): KeyringAddress {
|
||||
export default function get (state: State, _address: string | Uint8Array, type: 'account' | 'address' = 'address'): KeyringAddress {
|
||||
const address = isString(_address)
|
||||
? _address
|
||||
: addressEncode(_address);
|
||||
@ -18,10 +18,10 @@ export default function get (state: State, _address: string | Uint8Array): Keyri
|
||||
address: (): string =>
|
||||
address,
|
||||
isValid: (): boolean =>
|
||||
!!state.available.address[address],
|
||||
!!state.available[type][address],
|
||||
publicKey: (): Uint8Array =>
|
||||
publicKey,
|
||||
getMeta: (): KeyringJson$Meta =>
|
||||
state.available.address[address].meta
|
||||
state.available[type][address].meta
|
||||
};
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ import isAvailable from './isAvailable';
|
||||
import saveAccount from './account/save';
|
||||
import saveAccountMeta from './account/meta';
|
||||
import forgetAddress from './address/forget';
|
||||
import getAccounts from './account/all';
|
||||
import getAddress from './address/get';
|
||||
import getAddresses from './address/all';
|
||||
import saveAddress from './address/meta';
|
||||
@ -41,6 +42,8 @@ export default ({
|
||||
forgetAddress(state, address),
|
||||
isAvailable: (address: string | Uint8Array): boolean =>
|
||||
isAvailable(state, address),
|
||||
getAccounts: (): Array<KeyringAddress> =>
|
||||
getAccounts(state),
|
||||
getAddress: (address: string | Uint8Array): KeyringAddress =>
|
||||
getAddress(state, address),
|
||||
getAddresses: (): Array<KeyringAddress> =>
|
||||
|
||||
1
packages/ui-keyring/src/types.d.ts
vendored
1
packages/ui-keyring/src/types.d.ts
vendored
@ -60,6 +60,7 @@ export type KeyringInstance = {
|
||||
createAccount: (seed: Uint8Array, password?: string, meta?: KeyringPair$Meta) => KeyringPair,
|
||||
forgetAccount: (address: string) => void,
|
||||
forgetAddress: (address: string) => void,
|
||||
getAccounts: () => Array<KeyringAddress>,
|
||||
getAddress: (address: string | Uint8Array) => KeyringAddress,
|
||||
getAddresses: () => Array<KeyringAddress>,
|
||||
getOptions: (type: KeyringOption$Type) => KeyringOptions,
|
||||
|
||||
13
packages/ui-react-rx/src/with/multi.ts
Normal file
13
packages/ui-react-rx/src/with/multi.ts
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright 2017-2018 @polkadot/ui-react-rx authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
type HOC = (Component: React.ComponentType<any>) => React.ComponentType<any>;
|
||||
|
||||
export default function withMulti<T> (Component: React.ComponentType<any>, ...hocs: Array<HOC>): React.ComponentType<any> {
|
||||
return hocs.reduce((Component, hoc) => {
|
||||
return hoc(Component);
|
||||
}, Component);
|
||||
}
|
||||
@ -3,13 +3,20 @@
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { BareProps } from '@polkadot/ui-app/types';
|
||||
import { QueueProps, QueueTx, QueueTx$Base, QueueTx$Id, QueueTx$Status } from './types';
|
||||
import { ApiProps } from '@polkadot/ui-react-rx/types';
|
||||
import { QueueProps, QueueTx, QueueTx$Extrinsic, QueueTx$Base, QueueTx$Id, QueueTx$Status } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import rpcs from '@polkadot/jsonrpc';
|
||||
import withApi from '@polkadot/ui-react-rx/with/api';
|
||||
import encode from '@polkadot/extrinsics/codec/encode/extrinsic';
|
||||
import isUndefined from '@polkadot/util/is/undefined';
|
||||
|
||||
import { QueueProvider } from './Context';
|
||||
|
||||
export type Props = BareProps & {
|
||||
const rpc = rpcs.author.public.submitExtrinsic;
|
||||
|
||||
export type Props = BareProps & ApiProps & {
|
||||
children: any // node?
|
||||
};
|
||||
|
||||
@ -21,7 +28,7 @@ const defaultState = {
|
||||
|
||||
let nextId: QueueTx$Id = 0;
|
||||
|
||||
export default class Queue extends React.Component<Props, State> {
|
||||
class Queue extends React.Component<Props, State> {
|
||||
state: State = defaultState;
|
||||
|
||||
constructor (props: Props) {
|
||||
@ -30,6 +37,7 @@ export default class Queue extends React.Component<Props, State> {
|
||||
this.state = {
|
||||
queue: [],
|
||||
queueAdd: this.queueAdd,
|
||||
queueExtrinsic: this.queueExtrinsic,
|
||||
queueSetStatus: this.queueSetStatus
|
||||
};
|
||||
}
|
||||
@ -84,4 +92,27 @@ export default class Queue extends React.Component<Props, State> {
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
queueExtrinsic = ({ extrinsic, nonce, publicKey, values }: QueueTx$Extrinsic): QueueTx$Id => {
|
||||
const { apiSupport } = this.props;
|
||||
const params = Object.values(extrinsic.params);
|
||||
const isValid = values.length === params.length &&
|
||||
params.reduce((isValid, param, index) =>
|
||||
isValid && !isUndefined(values[index]),
|
||||
true
|
||||
);
|
||||
const encoded = isValid && extrinsic.params
|
||||
? encode(extrinsic, values, apiSupport)
|
||||
: new Uint8Array([]);
|
||||
|
||||
return this.queueAdd({
|
||||
isValid,
|
||||
nonce,
|
||||
publicKey,
|
||||
rpc,
|
||||
values: [encoded]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default withApi(Queue);
|
||||
|
||||
17
packages/ui-signer/src/types.d.ts
vendored
17
packages/ui-signer/src/types.d.ts
vendored
@ -3,9 +3,11 @@
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import BN from 'bn.js';
|
||||
import { Extrinsics } from '@polkadot/extrinsics/types';
|
||||
import { SectionItem } from '@polkadot/params/types';
|
||||
import { Interfaces } from '@polkadot/jsonrpc/types';
|
||||
import { Param$Values } from '@polkadot/params/types';
|
||||
import { RawParam$Value } from '@polkadot/ui-app/Params/types';
|
||||
|
||||
export type EncodedMessage = {
|
||||
isValid: boolean,
|
||||
@ -22,12 +24,20 @@ export type QueueTx$Result = {
|
||||
status: QueueTx$Status
|
||||
}
|
||||
|
||||
export type QueueTx$Base = EncodedMessage & {
|
||||
rpc: SectionItem<Interfaces>,
|
||||
export type AccountInfo = {
|
||||
nonce: BN,
|
||||
publicKey?: Uint8Array | null
|
||||
};
|
||||
|
||||
export type QueueTx$Base = EncodedMessage & AccountInfo & {
|
||||
rpc: SectionItem<Interfaces>
|
||||
};
|
||||
|
||||
export type QueueTx$Extrinsic = AccountInfo & {
|
||||
extrinsic: SectionItem<Extrinsics>,
|
||||
values: Array<RawParam$Value>
|
||||
}
|
||||
|
||||
export type QueueTx = QueueTx$Base & {
|
||||
error?: Error,
|
||||
id: QueueTx$Id,
|
||||
@ -37,11 +47,14 @@ export type QueueTx = QueueTx$Base & {
|
||||
|
||||
export type QueueTx$MessageAdd = (value: QueueTx$Base) => QueueTx$Id;
|
||||
|
||||
export type QueueTx$ExtrinsicAdd = (value: QueueTx$Extrinsic) => QueueTx$Id;
|
||||
|
||||
export type QueueTx$MessageSetStatus = (id: number, status: QueueTx$Status, result?: any, error?: Error) => void;
|
||||
|
||||
export type QueueProps = {
|
||||
queue: Array<QueueTx>,
|
||||
queueAdd: QueueTx$MessageAdd,
|
||||
queueExtrinsic: QueueTx$ExtrinsicAdd,
|
||||
queueSetStatus: QueueTx$MessageSetStatus
|
||||
};
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
"@polkadot/app-example/*": [ "packages/app-example/src/*" ],
|
||||
"@polkadot/app-extrinsics/*": [ "packages/app-extrinsics/src/*" ],
|
||||
"@polkadot/app-rpc/*": [ "packages/app-rpc/src/*" ],
|
||||
"@polkadot/app-staking/*": [ "packages/app-staking/src/*" ],
|
||||
"@polkadot/app-storage/*": [ "packages/app-storage/src/*" ],
|
||||
"@polkadot/app-toolbox/*": [ "packages/app-toolbox/src/*" ],
|
||||
"@polkadot/app-vanitygen/*": [ "packages/app-vanitygen/src/*" ],
|
||||
|
||||
@ -910,7 +910,7 @@
|
||||
"@polkadot/trie-hash" "^0.26.35"
|
||||
"@polkadot/util" "^0.26.35"
|
||||
|
||||
"@polkadot/storage@^0.26.35":
|
||||
"@polkadot/storage@^0.26.33", "@polkadot/storage@^0.26.35":
|
||||
version "0.26.35"
|
||||
resolved "https://registry.yarnpkg.com/@polkadot/storage/-/storage-0.26.35.tgz#2d4e3aac47612b0319c0c69f369974266d1c1d04"
|
||||
dependencies:
|
||||
@ -945,7 +945,7 @@
|
||||
tweetnacl "^1.0.0"
|
||||
xxhashjs "^0.2.2"
|
||||
|
||||
"@polkadot/util-keyring@^0.26.35":
|
||||
"@polkadot/util-keyring@^0.26.33", "@polkadot/util-keyring@^0.26.35":
|
||||
version "0.26.35"
|
||||
resolved "https://registry.yarnpkg.com/@polkadot/util-keyring/-/util-keyring-0.26.35.tgz#266002c6591665cf8a7df6c5ad3ed840f8b400a5"
|
||||
dependencies:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user