Contract call interface using RPC for non-mutating messages (#1745)
* start * Droptdown functional * continue * initial working version
This commit is contained in:
parent
878f0f70e8
commit
1dc4e0e967
@ -3,311 +3,273 @@
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { ApiProps } from '@polkadot/react-api/types';
|
||||
import { BareProps, I18nProps } from '@polkadot/react-components/types';
|
||||
import { BareProps, CallContract, I18nProps, StringOrNull } from '@polkadot/react-components/types';
|
||||
import { QueueProps } from '@polkadot/react-components/Status/types';
|
||||
import { ContractExecResult } from '@polkadot/types/interfaces/contracts';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { Abi } from '@polkadot/api-contract';
|
||||
import { Button, Dropdown, InputAddress, InputBalance, InputNumber, Modal, TxButton, TxComponent } from '@polkadot/react-components';
|
||||
import { getContractAbi } from '@polkadot/react-components/util';
|
||||
import React, { useState } from 'react';
|
||||
import rpc from '@polkadot/jsonrpc';
|
||||
import { Button, Dropdown, InputAddress, InputBalance, InputNumber, Modal, Output, TxButton } from '@polkadot/react-components';
|
||||
import { QueueConsumer } from '@polkadot/react-components/Status/Context';
|
||||
import { withApi, withMulti } from '@polkadot/react-api';
|
||||
import { displayType } from '@polkadot/types';
|
||||
import { isNull, isUndefined } from '@polkadot/util';
|
||||
|
||||
import Params from '../Params';
|
||||
|
||||
import translate from '../translate';
|
||||
import Params from '../Params';
|
||||
import { GAS_LIMIT } from '../constants';
|
||||
import { findCallMethod, getContractForAddress, getCallMethodOptions, getContractMethodFn } from './util';
|
||||
|
||||
interface Props extends BareProps, I18nProps, ApiProps, RouteComponentProps<{}> {
|
||||
address: string | null;
|
||||
interface Props extends BareProps, I18nProps, ApiProps {
|
||||
callContract: CallContract | null;
|
||||
callMethodIndex: number | null;
|
||||
isOpen: boolean;
|
||||
method: string | null;
|
||||
onChangeCallContract: (callContract: CallContract) => void;
|
||||
onChangeCallMethodIndex: (callMethodIndex: number) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
accountId: string | null;
|
||||
address: string | null;
|
||||
contractAbi?: Abi | null;
|
||||
endowment: BN;
|
||||
gasLimit: BN;
|
||||
isAddressValid: boolean;
|
||||
isBusy: boolean;
|
||||
method: string | null;
|
||||
params: any[];
|
||||
}
|
||||
function Call (props: Props): React.ReactElement<Props> | null {
|
||||
const { isOpen, callContract, callMethodIndex, onChangeCallContract, onChangeCallMethodIndex, onClose, api, t } = props;
|
||||
|
||||
class Call extends TxComponent<Props, State> {
|
||||
public defaultState: State = {
|
||||
address: null,
|
||||
accountId: null,
|
||||
endowment: new BN(0),
|
||||
gasLimit: new BN(GAS_LIMIT),
|
||||
method: null,
|
||||
isAddressValid: false,
|
||||
isBusy: false,
|
||||
params: []
|
||||
if (isNull(callContract) || isNull(callMethodIndex)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const hasRpc = api.rpc.contracts && api.rpc.contracts.call;
|
||||
const callMethod = findCallMethod(callContract, callMethodIndex);
|
||||
const useRpc = hasRpc && callMethod && !callMethod.mutates;
|
||||
// const isRpc = false;
|
||||
|
||||
const [accountId, setAccountId] = useState<StringOrNull>(null);
|
||||
const [endowment, setEndowment] = useState<BN>(new BN(0));
|
||||
const [gasLimit, setGasLimit] = useState<BN>(new BN(GAS_LIMIT));
|
||||
const [isBusy, setIsBusy] = useState(false);
|
||||
const [params, setParams] = useState<any[]>([]);
|
||||
|
||||
const _onChangeAccountId = (accountId: StringOrNull): void => setAccountId(accountId);
|
||||
|
||||
const _onChangeCallAddress = (callAddress: StringOrNull): void => {
|
||||
const callContract = getContractForAddress(callAddress);
|
||||
|
||||
onChangeCallContract && callContract.abi && onChangeCallContract(callContract);
|
||||
};
|
||||
|
||||
public state: State = this.defaultState;
|
||||
const _onChangeCallMethodString = (callMethodString: string): void => {
|
||||
setParams([]);
|
||||
onChangeCallMethodIndex && onChangeCallMethodIndex(parseInt(callMethodString, 10) || 0);
|
||||
};
|
||||
|
||||
public static getDerivedStateFromProps ({ address: propsAddress, method: propsMethod, isOpen }: Props, { address, method }: State): Pick<State, never> | null {
|
||||
if (!isOpen) {
|
||||
return {
|
||||
address: null,
|
||||
method: null,
|
||||
contractAbi: null,
|
||||
isAddressValid: false
|
||||
};
|
||||
}
|
||||
const _onChangeEndowment = (endowment?: BN): void => endowment && setEndowment(endowment);
|
||||
const _onChangeGasLimit = (gasLimit?: BN): void => gasLimit && setGasLimit(gasLimit);
|
||||
|
||||
return {
|
||||
...(
|
||||
!address
|
||||
? {
|
||||
address: propsAddress,
|
||||
contractAbi: propsAddress ? getContractAbi(propsAddress) : null,
|
||||
isAddressValid: !!propsAddress
|
||||
}
|
||||
: {}
|
||||
),
|
||||
...(
|
||||
!method
|
||||
? { method: propsMethod }
|
||||
: {}
|
||||
)
|
||||
};
|
||||
}
|
||||
const _onChangeParams = (params: any[]): void => setParams(params);
|
||||
const _toggleBusy = (): void => setIsBusy(!isBusy);
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { isOpen, t } = this.props;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className='app--contracts-Modal'
|
||||
dimmer='inverted'
|
||||
onClose={this.onClose}
|
||||
open={isOpen}
|
||||
>
|
||||
<Modal.Header>
|
||||
{t('Call a contract')}
|
||||
</Modal.Header>
|
||||
<Modal.Content>
|
||||
{this.renderContent()}
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
{this.renderButtons()}
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
public renderContent (): React.ReactNode {
|
||||
const { t } = this.props;
|
||||
const { gasLimit } = this.state;
|
||||
|
||||
const [address, contractAbi, method] = this.getCallProps();
|
||||
const isEndowValid = true;
|
||||
const isGasValid = gasLimit.gtn(0);
|
||||
|
||||
if (!address || !contractAbi) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const methodOptions = contractAbi
|
||||
? Object.keys(contractAbi.messages).map((key): { key: string; text: string; value: string } => {
|
||||
const fn = contractAbi.messages[key];
|
||||
const type = fn.type ? `: ${displayType(fn.type)}` : '';
|
||||
const args = fn.args.map(({ name, type }): string => `${name}: ${displayType(type)}`);
|
||||
const text = `${key}(${args.join(', ')})${type}`;
|
||||
|
||||
return {
|
||||
key,
|
||||
text,
|
||||
value: key
|
||||
};
|
||||
})
|
||||
: [];
|
||||
|
||||
return (
|
||||
<div className='contracts--Call'>
|
||||
<InputAddress
|
||||
help={t('Specify the user account to use for this contract call. And fees will be deducted from this account.')}
|
||||
label={t('call from account')}
|
||||
onChange={this.onChangeAccount}
|
||||
type='account'
|
||||
/>
|
||||
<InputAddress
|
||||
help={t('A deployed contract that has either been deployed or attached. The address and ABI are used to construct the parameters.')}
|
||||
label={t('contract to use')}
|
||||
onChange={this.onChangeAddress}
|
||||
type='contract'
|
||||
value={address}
|
||||
/>
|
||||
<Dropdown
|
||||
help={t('The message to send to this contract. Parameters are adjusted based on the ABI provided.')}
|
||||
isError={!method}
|
||||
label={t('message to send')}
|
||||
onChange={this.onChangeMethod}
|
||||
options={methodOptions}
|
||||
style={{ fontFamily: 'monospace' }}
|
||||
value={method}
|
||||
/>
|
||||
<Params
|
||||
onChange={this.onChangeParams}
|
||||
onEnter={this.sendTx}
|
||||
params={
|
||||
method && contractAbi && contractAbi.messages[method]
|
||||
? contractAbi.messages[method].args
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<InputBalance
|
||||
help={t('The allotted value for this contract, i.e. the amount transferred to the contract as part of this call.')}
|
||||
isError={!isEndowValid}
|
||||
label={t('value')}
|
||||
onChange={this.onChangeEndowment}
|
||||
/>
|
||||
<InputNumber
|
||||
help={t('The maximum amount of gas that can be used by this deployment, if the code requires more, the deployment will fail.')}
|
||||
isError={!isGasValid}
|
||||
label={t('maximum gas allowed')}
|
||||
onChange={this.onChangeGas}
|
||||
value={gasLimit}
|
||||
onEnter={this.sendTx}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderButtons (): React.ReactNode {
|
||||
const { api, t } = this.props;
|
||||
const { accountId, gasLimit, isAddressValid } = this.state;
|
||||
const isEndowValid = true; // !endowment.isZero();
|
||||
const isGasValid = !gasLimit.isZero();
|
||||
const isValid = !!accountId && isEndowValid && isGasValid && isAddressValid;
|
||||
|
||||
return (
|
||||
<Button.Group>
|
||||
<Button
|
||||
icon='cancel'
|
||||
isNegative
|
||||
onClick={this.onClose}
|
||||
label={t('Cancel')}
|
||||
/>
|
||||
<Button.Or />
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
icon='sign-in'
|
||||
isDisabled={!isValid}
|
||||
isPrimary
|
||||
label={t('Call')}
|
||||
onClick={this.toggleBusy}
|
||||
onFailed={this.toggleBusy}
|
||||
onSuccess={this.toggleBusy}
|
||||
params={this.constructCall}
|
||||
tx={api.tx.contracts ? 'contracts.call' : 'contract.call'}
|
||||
ref={this.button}
|
||||
/>
|
||||
</Button.Group>
|
||||
);
|
||||
}
|
||||
|
||||
private getCallProps = (): [string | null, Abi | null, string | null] => {
|
||||
let address;
|
||||
let contractAbi;
|
||||
let method;
|
||||
|
||||
if (!this.state.address) {
|
||||
return [null, null, null];
|
||||
} else {
|
||||
address = this.state.address;
|
||||
contractAbi = this.state.contractAbi || getContractAbi(address);
|
||||
method = contractAbi && this.state.method && contractAbi.messages[this.state.method]
|
||||
? this.state.method
|
||||
: (
|
||||
contractAbi
|
||||
? Object.keys(contractAbi.messages)[0]
|
||||
: null
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
address || null,
|
||||
contractAbi || null,
|
||||
method || null
|
||||
];
|
||||
}
|
||||
|
||||
private constructCall = (): any[] => {
|
||||
const {
|
||||
endowment, gasLimit, params
|
||||
} = this.state;
|
||||
|
||||
const [address, contractAbi, method] = this.getCallProps();
|
||||
|
||||
if (!contractAbi || !method) {
|
||||
const _constructTx = (): any[] => {
|
||||
const fn = getContractMethodFn(callContract, callMethod);
|
||||
if (!fn || !callContract || !callContract.address) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [address, endowment, gasLimit, contractAbi.messages[method](...params)];
|
||||
}
|
||||
return [callContract.address, endowment, gasLimit, fn(...params)];
|
||||
};
|
||||
|
||||
private onChangeAccount = (accountId: string | null): void => {
|
||||
this.setState({ accountId });
|
||||
}
|
||||
|
||||
private onChangeAddress = (address: string | null): void => {
|
||||
const contractAbi = getContractAbi(address);
|
||||
|
||||
this.setState({ address, contractAbi, isAddressValid: !!contractAbi });
|
||||
}
|
||||
|
||||
private onChangeEndowment = (endowment?: BN | null): void => {
|
||||
this.setState({ endowment: endowment || new BN(0) });
|
||||
}
|
||||
|
||||
private onChangeGas = (gasLimit: BN | undefined): void => {
|
||||
this.setState({ gasLimit: gasLimit || new BN(GAS_LIMIT) });
|
||||
}
|
||||
|
||||
private onChangeMethod = (method: string | null): void => {
|
||||
this.setState({ method, params: [] });
|
||||
}
|
||||
|
||||
private onChangeParams = (params: any[]): void => {
|
||||
this.setState({ params });
|
||||
}
|
||||
|
||||
private toggleBusy = (): void => {
|
||||
this.setState(({ isBusy }): Pick<State, never> => ({
|
||||
isBusy: !isBusy
|
||||
}));
|
||||
}
|
||||
|
||||
private reset = (): void => {
|
||||
this.setState((state: State): Pick<State, never> => {
|
||||
if (!state.isBusy) {
|
||||
return {
|
||||
...state,
|
||||
...this.defaultState
|
||||
};
|
||||
const _constructRpc = (): [any] | null => {
|
||||
const fn = getContractMethodFn(callContract, callMethod);
|
||||
if (!fn || !accountId || !callContract || !callContract.address || !callContract.abi || !callMethod) {
|
||||
return null;
|
||||
}
|
||||
return [
|
||||
{
|
||||
origin: accountId,
|
||||
dest: callContract.address,
|
||||
value: endowment,
|
||||
gasLimit,
|
||||
inputData: fn(...params)
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
return {};
|
||||
});
|
||||
}
|
||||
const isEndowmentValid = true;
|
||||
const isGasValid = !gasLimit.isZero();
|
||||
const isValid = !!accountId && isEndowmentValid && isGasValid && callContract && callContract.address && callContract.abi;
|
||||
|
||||
private onClose = (): void => {
|
||||
const { onClose } = this.props;
|
||||
return (
|
||||
<Modal
|
||||
className='app--contracts-Modal'
|
||||
dimmer='inverted'
|
||||
onClose={onClose}
|
||||
open={isOpen}
|
||||
>
|
||||
<Modal.Header>
|
||||
{t('Call a contract')}
|
||||
</Modal.Header>
|
||||
<Modal.Content>
|
||||
{callContract && (
|
||||
<div className='contracts--CallControls'>
|
||||
<InputAddress
|
||||
defaultValue={accountId}
|
||||
help={t('Specify the user account to use for this contract call. And fees will be deducted from this account.')}
|
||||
isDisabled={isBusy}
|
||||
label={t('call from account')}
|
||||
onChange={_onChangeAccountId}
|
||||
type='account'
|
||||
value={accountId}
|
||||
/>
|
||||
<InputAddress
|
||||
help={t('A deployed contract that has either been deployed or attached. The address and ABI are used to construct the parameters.')}
|
||||
isDisabled={isBusy}
|
||||
label={t('contract to use')}
|
||||
onChange={_onChangeCallAddress}
|
||||
type='contract'
|
||||
value={callContract.address}
|
||||
/>
|
||||
{callMethodIndex !== null && (
|
||||
<>
|
||||
<Dropdown
|
||||
help={t('The message to send to this contract. Parameters are adjusted based on the ABI provided.')}
|
||||
isDisabled={isBusy}
|
||||
isError={callMethod === null}
|
||||
label={t('message to send')}
|
||||
onChange={_onChangeCallMethodString}
|
||||
options={getCallMethodOptions(callContract)}
|
||||
value={`${callMethodIndex}`}
|
||||
/>
|
||||
<Params
|
||||
isDisabled={isBusy}
|
||||
onChange={_onChangeParams}
|
||||
params={
|
||||
callMethod
|
||||
? callMethod.args
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<InputBalance
|
||||
help={t('The allotted value for this contract, i.e. the amount transferred to the contract as part of this call.')}
|
||||
isDisabled={isBusy}
|
||||
isError={!isEndowmentValid}
|
||||
label={t('value')}
|
||||
onChange={_onChangeEndowment}
|
||||
value={endowment}
|
||||
/>
|
||||
<InputNumber
|
||||
defaultValue={gasLimit}
|
||||
help={t('The maximum amount of gas that can be used by this call. If the code requires more, the call will fail.')}
|
||||
isDisabled={isBusy}
|
||||
isError={!isGasValid}
|
||||
label={t('maximum gas allowed')}
|
||||
onChange={_onChangeGasLimit}
|
||||
value={gasLimit}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<QueueConsumer>
|
||||
{
|
||||
({ queueRpc, txqueue }: QueueProps): React.ReactNode => {
|
||||
const _onSubmitRpc = (): void => {
|
||||
const values = _constructRpc();
|
||||
|
||||
this.reset();
|
||||
onClose && onClose();
|
||||
}
|
||||
if (values) {
|
||||
queueRpc({
|
||||
accountId,
|
||||
rpc: rpc.contracts.methods.call,
|
||||
values
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const results = txqueue
|
||||
.filter(({ error, result, rpc, values }): boolean =>
|
||||
((!isUndefined(error) || !isUndefined(result)) &&
|
||||
rpc.section === 'contracts' && rpc.method === 'call' && !!values && values[0].dest === callContract.address)
|
||||
)
|
||||
.reverse();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button.Group>
|
||||
<Button
|
||||
icon='cancel'
|
||||
isNegative
|
||||
onClick={onClose}
|
||||
label={t('Cancel')}
|
||||
/>
|
||||
<Button.Or />
|
||||
{useRpc
|
||||
? (
|
||||
<Button
|
||||
icon='sign-in'
|
||||
isDisabled={!isValid}
|
||||
isPrimary
|
||||
label={t('Call')}
|
||||
onClick={_onSubmitRpc}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
icon='sign-in'
|
||||
isDisabled={!isValid}
|
||||
isPrimary
|
||||
label={t('Call')}
|
||||
onClick={_toggleBusy}
|
||||
onFailed={_toggleBusy}
|
||||
onSuccess={_toggleBusy}
|
||||
params={_constructTx}
|
||||
tx={api.tx.contracts ? 'contracts.call' : 'contract.call'}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Button.Group>
|
||||
{results.length > 0 && (
|
||||
<>
|
||||
<h3>{t('Call results')}</h3>
|
||||
<div>
|
||||
{
|
||||
results.map(
|
||||
(tx, index): React.ReactNode => {
|
||||
let output: string;
|
||||
const contractExecResult = tx.result as ContractExecResult;
|
||||
if (contractExecResult.isSuccess) {
|
||||
const { data } = contractExecResult.asSuccess;
|
||||
output = data.toHex();
|
||||
} else {
|
||||
output = 'Error';
|
||||
}
|
||||
|
||||
return (
|
||||
<Output
|
||||
isError={contractExecResult.isError}
|
||||
key={`result-${tx.id}`}
|
||||
label={t(`#${results.length - 1 - index}`)}
|
||||
style={{ fontFamily: 'monospace' }}
|
||||
value={output}
|
||||
withCopy
|
||||
withLabel
|
||||
/>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
</QueueConsumer>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default withMulti(
|
||||
translate(withRouter(Call)),
|
||||
Call,
|
||||
translate,
|
||||
withApi
|
||||
);
|
||||
|
||||
@ -3,28 +3,21 @@
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { ActionStatus } from '@polkadot/react-components/Status/types';
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { CallContract, I18nProps } from '@polkadot/react-components/types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
import { AddressRow, Button, Card, Forget, Messages } from '@polkadot/react-components';
|
||||
import { getContractAbi } from '@polkadot/react-components/util';
|
||||
|
||||
import translate from '../translate';
|
||||
|
||||
interface Props extends I18nProps, RouteComponentProps {
|
||||
basePath: string;
|
||||
address: string;
|
||||
onCall: (callAddress?: string, callMethod?: string) => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
isBackupOpen: boolean;
|
||||
isForgetOpen: boolean;
|
||||
isPasswordOpen: boolean;
|
||||
contract: CallContract;
|
||||
onCall: (_: CallContract) => (_?: number) => () => void;
|
||||
}
|
||||
|
||||
const ContractCard = styled(Card)`
|
||||
@ -34,85 +27,17 @@ const ContractCard = styled(Card)`
|
||||
}
|
||||
`;
|
||||
|
||||
class Contract extends React.PureComponent<Props, State> {
|
||||
public state: State = {
|
||||
isBackupOpen: false,
|
||||
isForgetOpen: false,
|
||||
isPasswordOpen: false
|
||||
};
|
||||
function Contract (props: Props): React.ReactElement<Props> | null {
|
||||
const { contract, contract: { abi, address }, onCall, t } = props;
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { address, onCall, t } = this.props;
|
||||
|
||||
const contractAbi = getContractAbi(address);
|
||||
|
||||
if (!contractAbi) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ContractCard>
|
||||
{this.renderModals()}
|
||||
<AddressRow
|
||||
buttons={this.renderButtons()}
|
||||
isContract
|
||||
isEditable
|
||||
type='contract'
|
||||
value={address}
|
||||
withBalance={false}
|
||||
withNonce={false}
|
||||
withTags
|
||||
>
|
||||
<details>
|
||||
<summary>{t('Messages')}</summary>
|
||||
<Messages
|
||||
address={address}
|
||||
contractAbi={contractAbi}
|
||||
isRemovable={false}
|
||||
onSelect={onCall}
|
||||
/>
|
||||
</details>
|
||||
</AddressRow>
|
||||
</ContractCard>
|
||||
);
|
||||
if (!address || !abi) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private renderModals (): React.ReactNode {
|
||||
const { address } = this.props;
|
||||
const { isForgetOpen } = this.state;
|
||||
|
||||
if (!address) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const modals = [];
|
||||
|
||||
if (isForgetOpen) {
|
||||
modals.push(
|
||||
<Forget
|
||||
address={address}
|
||||
mode='contract'
|
||||
onForget={this.onForget}
|
||||
key='modal-forget-contract'
|
||||
onClose={this.toggleForget}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return modals;
|
||||
}
|
||||
|
||||
private toggleForget = (): void => {
|
||||
const { isForgetOpen } = this.state;
|
||||
|
||||
this.setState({
|
||||
isForgetOpen: !isForgetOpen
|
||||
});
|
||||
}
|
||||
|
||||
private onForget = (): void => {
|
||||
const { address, t } = this.props;
|
||||
const [isForgetOpen, setIsForgetOpen] = useState(false);
|
||||
|
||||
const _toggleForget = (): void => setIsForgetOpen(!isForgetOpen);
|
||||
const _onForget = (): void => {
|
||||
if (!address) {
|
||||
return;
|
||||
}
|
||||
@ -130,32 +55,65 @@ class Contract extends React.PureComponent<Props, State> {
|
||||
status.status = 'error';
|
||||
status.message = error.message;
|
||||
}
|
||||
this.toggleForget();
|
||||
}
|
||||
_toggleForget();
|
||||
};
|
||||
|
||||
private renderButtons (): React.ReactNode {
|
||||
const { address, onCall, t } = this.props;
|
||||
const _onCallMessage = onCall(contract);
|
||||
const _onCall = _onCallMessage();
|
||||
|
||||
return (
|
||||
<div className='contracts--Contract-buttons'>
|
||||
<Button
|
||||
icon='trash'
|
||||
isNegative
|
||||
onClick={this.toggleForget}
|
||||
size='small'
|
||||
tooltip={t('Forget this contract')}
|
||||
/>
|
||||
<Button
|
||||
icon='play'
|
||||
isPrimary
|
||||
label={t('execute')}
|
||||
onClick={(): void => onCall(address)}
|
||||
size='small'
|
||||
tooltip={t('Call a method on this contract')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ContractCard>
|
||||
{
|
||||
isForgetOpen && (
|
||||
<Forget
|
||||
address={address}
|
||||
mode='contract'
|
||||
onForget={_onForget}
|
||||
key='modal-forget-contract'
|
||||
onClose={_toggleForget}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<AddressRow
|
||||
buttons={
|
||||
<div className='contracts--Contract-buttons'>
|
||||
<Button
|
||||
icon='trash'
|
||||
isNegative
|
||||
onClick={_toggleForget}
|
||||
size='small'
|
||||
tooltip={t('Forget this contract')}
|
||||
/>
|
||||
<Button
|
||||
icon='play'
|
||||
isPrimary
|
||||
label={t('execute')}
|
||||
onClick={_onCall}
|
||||
size='small'
|
||||
tooltip={t('Call a method on this contract')}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
isContract
|
||||
isEditable
|
||||
type='contract'
|
||||
value={address}
|
||||
withBalance={false}
|
||||
withNonce={false}
|
||||
withTags
|
||||
>
|
||||
<details>
|
||||
<summary>{t('Messages')}</summary>
|
||||
<Messages
|
||||
address={address}
|
||||
contractAbi={abi}
|
||||
isRemovable={false}
|
||||
onSelect={_onCallMessage}
|
||||
/>
|
||||
</details>
|
||||
</AddressRow>
|
||||
</ContractCard>
|
||||
);
|
||||
}
|
||||
|
||||
export default translate(withRouter(Contract));
|
||||
|
||||
@ -2,120 +2,108 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { CallContract, NullContract, I18nProps } from '@polkadot/react-components/types';
|
||||
import { ComponentProps } from '../types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { Button, CardGrid } from '@polkadot/react-components';
|
||||
import { stringCamelCase } from '@polkadot/util';
|
||||
|
||||
import translate from '../translate';
|
||||
import Add from './Add';
|
||||
import Contract from './Contract';
|
||||
import Call from './Call';
|
||||
import { getContractForAddress } from './util';
|
||||
|
||||
interface Props extends ComponentProps, I18nProps, RouteComponentProps {}
|
||||
|
||||
interface State {
|
||||
isAddOpen: boolean;
|
||||
isCallOpen: boolean;
|
||||
callAddress: string | null;
|
||||
callMethod: string | null;
|
||||
}
|
||||
function Contracts (props: Props): React.ReactElement<Props> {
|
||||
const { accounts, basePath, contracts, hasCode, showDeploy, t } = props;
|
||||
// const { callAddress, callMethod, isAddOpen, isCallOpen } = this.state;
|
||||
|
||||
class Contracts extends React.PureComponent<Props, State> {
|
||||
public state: State = {
|
||||
callAddress: null,
|
||||
callMethod: null,
|
||||
isAddOpen: false,
|
||||
isCallOpen: false
|
||||
const [callContract, setCallContract] = useState<CallContract | null>(null);
|
||||
const [callMethodIndex, setCallMethodIndex] = useState<number | null>(null);
|
||||
const [isAddOpen, setIsAddOpen] = useState(false);
|
||||
const [isCallOpen, setIsCallOpen] = useState(false);
|
||||
|
||||
const _toggleAdd = (): void => setIsAddOpen(!isAddOpen);
|
||||
const _toggleCall = (): void => setIsCallOpen(!isCallOpen);
|
||||
|
||||
const _onChangeCallContract = (newCallContract: CallContract): void => {
|
||||
if (callContract && newCallContract.address !== callContract.address) {
|
||||
setCallMethodIndex(0);
|
||||
}
|
||||
setCallContract(callContract);
|
||||
};
|
||||
const _onChangeCallMethodIndex = (callMethodIndex: number): void => {
|
||||
!!callContract && setCallMethodIndex(callMethodIndex);
|
||||
};
|
||||
const _onCall = (callContract: CallContract): (_?: number) => () => void => {
|
||||
return function (callMethodIndex?: number): () => void {
|
||||
return function (): void {
|
||||
setCallContract(callContract);
|
||||
setCallMethodIndex(callMethodIndex || 0);
|
||||
setIsCallOpen(true);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { accounts, basePath, contracts, hasCode, showDeploy, t } = this.props;
|
||||
const { callAddress, callMethod, isAddOpen, isCallOpen } = this.state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<CardGrid
|
||||
emptyText={t('No contracts available')}
|
||||
buttons={
|
||||
<Button.Group>
|
||||
{hasCode && (
|
||||
<>
|
||||
<Button
|
||||
icon='cloud upload'
|
||||
isPrimary
|
||||
label={t('Deploy a code hash')}
|
||||
onClick={showDeploy()}
|
||||
/>
|
||||
<Button.Or />
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
icon='add'
|
||||
isPrimary
|
||||
label={t('Add an existing contract')}
|
||||
onClick={this.showAdd}
|
||||
/>
|
||||
</Button.Group>
|
||||
}
|
||||
>
|
||||
{accounts && contracts && Object.keys(contracts).map((address): React.ReactNode => {
|
||||
return (
|
||||
<>
|
||||
<CardGrid
|
||||
emptyText={t('No contracts available')}
|
||||
buttons={
|
||||
<Button.Group>
|
||||
{hasCode && (
|
||||
<>
|
||||
<Button
|
||||
icon='cloud upload'
|
||||
isPrimary
|
||||
label={t('Deploy a code hash')}
|
||||
onClick={showDeploy()}
|
||||
/>
|
||||
<Button.Or />
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
icon='add'
|
||||
isPrimary
|
||||
label={t('Add an existing contract')}
|
||||
onClick={_toggleAdd}
|
||||
/>
|
||||
</Button.Group>
|
||||
}
|
||||
>
|
||||
{(accounts && contracts && Object.keys(contracts)
|
||||
.map((address): CallContract | NullContract => getContractForAddress(address))
|
||||
.filter(({ abi, address }: CallContract | NullContract): boolean => !!address && !!abi) as CallContract[])
|
||||
.map((contract: CallContract): React.ReactNode => {
|
||||
return (
|
||||
<Contract
|
||||
basePath={basePath}
|
||||
address={address}
|
||||
key={address}
|
||||
onCall={this.showCall}
|
||||
contract={contract}
|
||||
key={contract.address}
|
||||
onCall={_onCall}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</CardGrid>
|
||||
<Add
|
||||
basePath={basePath}
|
||||
isOpen={isAddOpen}
|
||||
onClose={this.hideAdd}
|
||||
/>
|
||||
<Call
|
||||
address={callAddress}
|
||||
isOpen={isCallOpen}
|
||||
method={callMethod}
|
||||
onClose={this.hideCall}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private showAdd = (): void => {
|
||||
this.setState({
|
||||
isAddOpen: true
|
||||
});
|
||||
}
|
||||
|
||||
private hideAdd = (): void => {
|
||||
this.setState({
|
||||
isAddOpen: false
|
||||
});
|
||||
}
|
||||
|
||||
private showCall = (callAddress?: string, callMethod?: string): void => {
|
||||
this.setState({
|
||||
isCallOpen: true,
|
||||
callAddress: callAddress || null,
|
||||
callMethod: callMethod ? stringCamelCase(callMethod) : null
|
||||
});
|
||||
}
|
||||
|
||||
private hideCall = (): void => {
|
||||
this.setState({
|
||||
isCallOpen: false,
|
||||
callAddress: null,
|
||||
callMethod: null
|
||||
});
|
||||
}
|
||||
</CardGrid>
|
||||
<Add
|
||||
basePath={basePath}
|
||||
isOpen={isAddOpen}
|
||||
onClose={_toggleAdd}
|
||||
/>
|
||||
<Call
|
||||
callContract={callContract}
|
||||
callMethodIndex={callMethodIndex}
|
||||
isOpen={isCallOpen}
|
||||
onChangeCallContract={_onChangeCallContract}
|
||||
onChangeCallMethodIndex={_onChangeCallMethodIndex}
|
||||
onClose={_toggleCall}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default translate(withRouter(Contracts));
|
||||
|
||||
54
packages/app-contracts/src/Contracts/util.tsx
Normal file
54
packages/app-contracts/src/Contracts/util.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright 2017-2019 @polkadot/app-contracts authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { ContractABIFn, ContractABIMethod } from '@polkadot/api-contract/types';
|
||||
import { CallContract, NullContract, StringOrNull } from '@polkadot/react-components/types';
|
||||
import { CONTRACT_NULL } from '../constants';
|
||||
|
||||
import React from 'react';
|
||||
import { MessageSignature } from '@polkadot/react-components';
|
||||
import { getContractAbi } from '@polkadot/react-components/util';
|
||||
import { stringCamelCase } from '@polkadot/util';
|
||||
|
||||
export function findCallMethod (callContract: CallContract | null, callMethodIndex = 0): ContractABIMethod | null {
|
||||
const message = callContract && callContract.abi.abi.contract.messages[callMethodIndex];
|
||||
|
||||
return message || null;
|
||||
}
|
||||
|
||||
export function getContractMethodFn (callContract: CallContract | null, callMethod: ContractABIMethod | null): ContractABIFn | null {
|
||||
const fn = callContract && callContract.abi && callMethod && callContract.abi.messages[stringCamelCase(callMethod.name)];
|
||||
|
||||
return fn || null;
|
||||
}
|
||||
|
||||
export function getContractForAddress (address: StringOrNull): CallContract | NullContract {
|
||||
if (!address) {
|
||||
return CONTRACT_NULL;
|
||||
} else {
|
||||
const abi = getContractAbi(address);
|
||||
return abi
|
||||
? {
|
||||
address,
|
||||
abi
|
||||
}
|
||||
: CONTRACT_NULL;
|
||||
}
|
||||
}
|
||||
|
||||
export function getCallMethodOptions (callContract: CallContract | null): any[] {
|
||||
return callContract && callContract.abi
|
||||
? callContract.abi.abi.contract.messages.map((message, messageIndex): { key: string; text: React.ReactNode; value: string } => {
|
||||
const key = message.name;
|
||||
|
||||
return {
|
||||
key,
|
||||
text: (
|
||||
<MessageSignature message={message} />
|
||||
),
|
||||
value: `${messageIndex}`
|
||||
};
|
||||
})
|
||||
: [];
|
||||
}
|
||||
@ -14,17 +14,16 @@ import { SubmittableResult } from '@polkadot/api';
|
||||
import { Abi } from '@polkadot/api-contract';
|
||||
import { withApi, withMulti } from '@polkadot/react-api';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
import { Button, Dropdown, InputBalance, TxButton } from '@polkadot/react-components';
|
||||
import { Button, Dropdown, InputBalance, MessageSignature, TxButton } from '@polkadot/react-components';
|
||||
import createValues from '@polkadot/react-params/values';
|
||||
import { displayType } from '@polkadot/types';
|
||||
|
||||
import ContractModal, { ContractModalProps, ContractModalState } from './Modal';
|
||||
import Params from './Params';
|
||||
import store from './store';
|
||||
import translate from './translate';
|
||||
import { GAS_LIMIT, ENDOWMENT } from './constants';
|
||||
import { GAS_LIMIT } from './constants';
|
||||
|
||||
type ConstructOptions = { key: string; text: string; value: string }[];
|
||||
type ConstructOptions = { key: string; text: React.ReactNode; value: string }[];
|
||||
|
||||
interface Props extends ContractModalProps, ApiProps, I18nProps, RouteComponentProps {
|
||||
codeHash?: string;
|
||||
@ -52,7 +51,7 @@ class Deploy extends ContractModal<Props, State> {
|
||||
...this.defaultState,
|
||||
constructorIndex: -1,
|
||||
constructOptions: [],
|
||||
endowment: new BN(ENDOWMENT),
|
||||
endowment: new BN(0),
|
||||
gasLimit: new BN(GAS_LIMIT),
|
||||
isHashValid: false,
|
||||
params: [],
|
||||
@ -126,12 +125,14 @@ class Deploy extends ContractModal<Props, State> {
|
||||
const constructor = constructors[constructorIndex];
|
||||
const constructOptions: ConstructOptions = constructors.map(
|
||||
(constr) => {
|
||||
const { name, args } = constr;
|
||||
const textArgs = args.map(({ name, type }): string => `${name}: ${displayType(type)}`);
|
||||
|
||||
return {
|
||||
key: `${constructorIndex}`,
|
||||
text: `${name}(${textArgs.join(', ')})`,
|
||||
text: (
|
||||
<MessageSignature
|
||||
asConstructor
|
||||
message={constr}
|
||||
/>
|
||||
),
|
||||
value: `${constructorIndex}`
|
||||
};
|
||||
});
|
||||
@ -147,7 +148,6 @@ class Deploy extends ContractModal<Props, State> {
|
||||
const { t } = this.props;
|
||||
const { codeHash, constructorIndex, constructOptions, contractAbi, endowment, isAbiSupplied, isBusy, isHashValid } = this.state;
|
||||
|
||||
const isEndowValid = !endowment.isZero();
|
||||
const codeOptions = store.getAllCode().map(({ json: { codeHash, name } }): { text: string; value: string } => ({
|
||||
text: `${name} (${codeHash})`,
|
||||
value: codeHash
|
||||
@ -182,11 +182,12 @@ class Deploy extends ContractModal<Props, State> {
|
||||
<Dropdown
|
||||
help={t('The deployment constructor information for this contract, as provided by the ABI.')}
|
||||
isDisabled={contractAbi.abi.contract.constructors.length <= 1}
|
||||
label={t('constructor')}
|
||||
label={t('constructor ')}
|
||||
onChange={this.onChangeConstructorIndex}
|
||||
options={constructOptions}
|
||||
style={{ fontFamily: 'monospace' }}
|
||||
value={`${constructorIndex}`}
|
||||
withLabel
|
||||
/>
|
||||
)
|
||||
: null
|
||||
@ -204,7 +205,7 @@ class Deploy extends ContractModal<Props, State> {
|
||||
<InputBalance
|
||||
help={t('The allotted endowment for this contract, i.e. the amount transferred to the contract upon instantiation.')}
|
||||
isDisabled={isBusy}
|
||||
isError={!isEndowValid}
|
||||
isError={endowment.isZero()}
|
||||
label={t('endowment')}
|
||||
onChange={this.onChangeEndowment}
|
||||
onEnter={this.sendTx}
|
||||
|
||||
@ -4,3 +4,7 @@
|
||||
|
||||
export const ENDOWMENT = 1000;
|
||||
export const GAS_LIMIT = 500000;
|
||||
export const CONTRACT_NULL = {
|
||||
abi: null,
|
||||
address: null
|
||||
};
|
||||
|
||||
95
packages/react-components/src/MessageSignature.tsx
Normal file
95
packages/react-components/src/MessageSignature.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
// Copyright 2017-2019 @polkadot/react-components authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { ContractABIMethod } from '@polkadot/api-contract/types';
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { displayType } from '@polkadot/types';
|
||||
|
||||
import Icon from './Icon';
|
||||
import Tooltip from './Tooltip';
|
||||
import translate from './translate';
|
||||
|
||||
export interface Props extends I18nProps {
|
||||
asConstructor?: boolean;
|
||||
message: ContractABIMethod;
|
||||
withTooltip?: boolean;
|
||||
}
|
||||
|
||||
const Signature = styled.div`
|
||||
font-family: monospace;
|
||||
font-weight: normal;
|
||||
|
||||
.mutates {
|
||||
color: #ff8600;
|
||||
margin-left: 0.5rem;
|
||||
opacity: 0.6;
|
||||
}
|
||||
`;
|
||||
|
||||
const Name = styled.span`
|
||||
color: #2f8ddb;
|
||||
font-weight: bold;
|
||||
`;
|
||||
|
||||
const Type = styled.span`
|
||||
color: #21a2b2;
|
||||
`;
|
||||
|
||||
const ReturnType = styled.span`
|
||||
color: #ff8600;
|
||||
`;
|
||||
|
||||
function MessageSignature ({ message: { args, mutates, name, returnType }, asConstructor = false, withTooltip = false, t }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<Signature>
|
||||
<Name>
|
||||
{name}
|
||||
</Name>
|
||||
(
|
||||
{args.map(({ name, type }, index): React.ReactNode => {
|
||||
return (
|
||||
<React.Fragment key={`${name}-args-${index}`}>
|
||||
{name}:
|
||||
{' '}
|
||||
<Type>
|
||||
{displayType(type)}
|
||||
</Type>
|
||||
{index < args.length - 1 && ', '}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
)
|
||||
{(!asConstructor && returnType) && (
|
||||
<>
|
||||
:
|
||||
{' '}
|
||||
<ReturnType>
|
||||
{displayType(returnType)}
|
||||
</ReturnType>
|
||||
</>
|
||||
)}
|
||||
{mutates && (
|
||||
<>
|
||||
<Icon
|
||||
data-tip
|
||||
data-for={`mutates-${name}`}
|
||||
name="database"
|
||||
className="mutates"
|
||||
/>
|
||||
{withTooltip && (
|
||||
<Tooltip
|
||||
trigger={`mutates-${name}`}
|
||||
text={t('Mutates contract state')}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Signature>
|
||||
);
|
||||
}
|
||||
|
||||
export default translate(MessageSignature);
|
||||
@ -9,10 +9,9 @@ import { Button } from '@polkadot/react-components';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Abi } from '@polkadot/api-contract';
|
||||
import { displayType } from '@polkadot/types';
|
||||
|
||||
import Icon from './Icon';
|
||||
import Tooltip from './Tooltip';
|
||||
import MessageSignature from './MessageSignature';
|
||||
import translate from './translate';
|
||||
import { classes } from './util';
|
||||
|
||||
@ -22,24 +21,22 @@ export interface Props extends I18nProps {
|
||||
isLabelled?: boolean;
|
||||
isRemovable: boolean;
|
||||
onRemove?: () => void;
|
||||
onSelect?: (callAddress?: string, callMethod?: string) => void;
|
||||
onSelectConstructor?: (index: number) => void;
|
||||
onSelect?: (messageIndex: number) => () => void;
|
||||
onSelectConstructor?: (constructorIndex: number) => void;
|
||||
withConstructors?: boolean;
|
||||
}
|
||||
|
||||
const NOOP = (): void => {};
|
||||
|
||||
function onSelect (props: Props, index: number): () => void {
|
||||
function onSelect (props: Props, messageIndex: number): () => void {
|
||||
return function (): void {
|
||||
const { address: callAddress, contractAbi: { abi: { contract: { messages } } }, onSelect } = props;
|
||||
|
||||
if (!callAddress || !messages || !messages[index]) {
|
||||
if (!callAddress || !messages || !messages[messageIndex]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { name } = messages[index];
|
||||
|
||||
onSelect && onSelect(callAddress, name);
|
||||
onSelect && onSelect(messageIndex)();
|
||||
};
|
||||
}
|
||||
|
||||
@ -50,60 +47,25 @@ function onSelectConstructor (props: Props, index: number): () => void {
|
||||
if (!constructors || !constructors[index]) {
|
||||
return;
|
||||
}
|
||||
console.log(onSelectConstructor);
|
||||
|
||||
onSelectConstructor && onSelectConstructor(index);
|
||||
};
|
||||
}
|
||||
|
||||
function renderItem (props: Props, { args, docs = [], mutates, name, returnType }: ContractABIMethod, index: number, asConstructor = false): React.ReactNode {
|
||||
function renderItem (props: Props, message: ContractABIMethod, index: number, asConstructor = false): React.ReactNode {
|
||||
const { docs = [], name } = message;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={name}
|
||||
className={classes('message', !onSelect && 'exempt-hover', asConstructor && 'constructor')}
|
||||
>
|
||||
<div className="info">
|
||||
<div className="signature">
|
||||
<span className="name">
|
||||
{name}
|
||||
</span>
|
||||
(
|
||||
{args.map(({ name, type }, index): React.ReactNode => {
|
||||
return (
|
||||
<React.Fragment key={`${name}-args-${index}`}>
|
||||
{name}:
|
||||
{' '}
|
||||
<span className='type'>
|
||||
{displayType(type)}
|
||||
</span>
|
||||
{index < args.length - 1 && ', '}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
)
|
||||
{(!asConstructor && returnType) && (
|
||||
<>
|
||||
:
|
||||
{' '}
|
||||
<span className="return-type">
|
||||
{displayType(returnType)}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{mutates && (
|
||||
<>
|
||||
<Icon
|
||||
data-tip
|
||||
data-for={`mutates-${name}`}
|
||||
name="database"
|
||||
className="mutates"
|
||||
/>
|
||||
<Tooltip
|
||||
trigger={`mutates-${name}`}
|
||||
text={props.t('Mutates contract state')}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<MessageSignature
|
||||
asConstructor={asConstructor}
|
||||
message={message}
|
||||
withTooltip
|
||||
/>
|
||||
<details className="docs">
|
||||
<summary>
|
||||
{
|
||||
@ -229,34 +191,10 @@ export default translate(styled(Messages)`
|
||||
.info {
|
||||
flex: 1 1;
|
||||
|
||||
.signature {
|
||||
font-family: monospace;
|
||||
font-weight: normal;
|
||||
|
||||
.name {
|
||||
color: #2f8ddb;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.type {
|
||||
color: #21a2b2;
|
||||
}
|
||||
|
||||
.return-type {
|
||||
color: #ff8600;
|
||||
}
|
||||
}
|
||||
|
||||
.docs {
|
||||
font-size: 0.8rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.mutates {
|
||||
color: #ff8600;
|
||||
margin-left: 0.5rem;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.accessory {
|
||||
|
||||
@ -54,6 +54,7 @@ export { default as Labelled } from './Labelled';
|
||||
export { default as LinkPolkascan } from './LinkPolkascan';
|
||||
export { default as Menu } from './Menu';
|
||||
export { default as Messages } from './Messages';
|
||||
export { default as MessageSignature } from './MessageSignature';
|
||||
export { default as Modal } from './Modal';
|
||||
export { default as Nonce } from './Nonce';
|
||||
export { default as Output } from './Output';
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { Abi } from '@polkadot/api-contract';
|
||||
import { ActionStatus } from '@polkadot/react-components/Status/types';
|
||||
|
||||
export interface BareProps {
|
||||
@ -18,3 +19,24 @@ export interface AppProps {
|
||||
export type I18nProps = BareProps & WithTranslation;
|
||||
|
||||
export type BitLength = 8 | 16 | 32 | 64 | 128 | 256;
|
||||
|
||||
export type StringOrNull = string | null;
|
||||
|
||||
interface ContractBase {
|
||||
abi: Abi;
|
||||
}
|
||||
|
||||
export interface Contract extends ContractBase {
|
||||
address: null;
|
||||
}
|
||||
|
||||
export interface ContractDeployed extends ContractBase {
|
||||
address: string;
|
||||
}
|
||||
|
||||
export type CallContract = ContractDeployed;
|
||||
|
||||
export interface NullContract {
|
||||
abi: null;
|
||||
address: null;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user