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:
Jaco Greeff 2018-08-02 17:15:07 +02:00 committed by GitHub
parent b3e6bd113f
commit cf8a24a8da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 689 additions and 18 deletions

View File

@ -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)

View File

@ -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'

View File

@ -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);

View 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.

View File

@ -0,0 +1 @@
# @polkadot/app-staking

View 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"
}
}

View 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);

View 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);

View 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' }
)
);

View 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;
}

View 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
}
)
);

View 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']);

View File

@ -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",

View File

@ -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);

View 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);

View File

@ -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'));

View 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);
}

View File

@ -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
};
}

View File

@ -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> =>

View File

@ -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,

View 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);
}

View File

@ -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);

View File

@ -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
};

View File

@ -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/*" ],

View File

@ -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: