Support for ink! v3 (#3828)

* Support for ink! v3

* Update packages/page-contracts/src/shared/Messages.tsx

* Cleanup displays

* Adjust

* Deploy & RPC call

* s/call/read/

* Adapt layout

* Swap to tables

* Update

* Updates to read/exec

* Adjust

* Updates

* Adjust with old code removal

* Update banner

* Adjust modals for dark

* Additional

* Bump API

* typo, case-sensitive

* Remove old types
This commit is contained in:
Jaco Greeff 2020-10-09 19:41:10 +02:00 committed by GitHub
parent 49e8f9c129
commit ae9ccf3450
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1384 additions and 4491 deletions

View File

@ -33,10 +33,10 @@
"afterSign": "electron-builder-notarize"
},
"resolutions": {
"@polkadot/api": "^2.1.1",
"@polkadot/api-contract": "^2.1.1",
"@polkadot/api": "^2.1.2-13",
"@polkadot/api-contract": "^2.1.2-13",
"@polkadot/keyring": "^3.5.1",
"@polkadot/types": "^2.1.1",
"@polkadot/types": "^2.1.2-13",
"@polkadot/util": "^3.5.1",
"@polkadot/util-crypto": "^3.5.1",
"@polkadot/wasm-crypto": "^1.4.1",
@ -84,7 +84,7 @@
"@babel/register": "^7.11.5",
"@babel/runtime": "^7.11.2",
"@pinata/sdk": "^1.1.11",
"@polkadot/dev": "^0.57.8",
"@polkadot/dev": "^0.58.1",
"@polkadot/ts": "^0.3.49",
"@types/bn.js": "^4.11.6",
"@types/chart.js": "^2.9.25",

View File

@ -6,7 +6,7 @@
"dependencies": {
"@babel/core": "^7.11.6",
"@babel/polyfill": "^7.11.5",
"@polkadot/dev": "^0.57.8",
"@polkadot/dev": "^0.58.1",
"@polkadot/react-components": "0.61.2-8",
"electron-log": "^4.2.4",
"electron-updater": "^4.3.5"

View File

@ -18,7 +18,7 @@
"@babel/runtime": "^7.11.2",
"@polkadot/apps-config": "0.61.2-8",
"@polkadot/apps-routing": "0.61.2-8",
"@polkadot/dev": "^0.57.8",
"@polkadot/dev": "^0.58.1",
"@polkadot/react-api": "0.61.2-8",
"@polkadot/react-components": "0.61.2-8",
"@polkadot/react-hooks": "0.61.2-8",

View File

@ -11,18 +11,22 @@
"Clear all": "Clear all",
"Code": "Code",
"Confirm ABI removal": "Confirm ABI removal",
"Constructors ({{count}})": "Constructors ({{count}})",
"Contracts": "Contracts",
"Deploy": "Deploy",
"Deploy a code hash": "Deploy a code hash",
"Deploy this code hash as a smart contract": "Deploy this code hash as a smart contract",
"Deploy with this constructor": "Deploy with this constructor",
"Execute": "Execute",
"Forget this code hash": "Forget this code hash",
"Forget this contract": "Forget this contract",
"Messages": "Messages",
"Messages ({{count}})": "Messages ({{count}})",
"Mutates contract state": "Mutates contract state",
"No code hashes available": "No code hashes available",
"No contracts available": "No contracts available",
"No documentation provided": "No documentation provided",
"Read": "Read",
"Remove": "Remove",
"Remove ABI": "Remove ABI",
"Save": "Save",
@ -45,12 +49,15 @@
"The maximum amount of gas to use for this contract call. If the call requires more, it will fail.": "The maximum amount of gas to use for this contract call. If the call requires more, it will fail.",
"The message to send to this contract. Parameters are adjusted based on the ABI provided.": "The message to send to this contract. Parameters are adjusted based on the ABI provided.",
"The value is not in a valid address format": "The value is not in a valid address format",
"This app now only supports Substrate 2.0 with Ink! 3.0. As such versions older of the contracts ABI cannot be used and will display an error when uploaded. Ensure you follow the Ink! instructions for updating if you are using an older version.": "This app now only supports Substrate 2.0 with Ink! 3.0. As such versions older of the contracts ABI cannot be used and will display an error when uploaded. Ensure you follow the Ink! instructions for updating if you are using an older version.",
"This operation does not impact the associated on-chain code or any of its contracts.": "This operation does not impact the associated on-chain code or any of its contracts.",
"This operation does not remove the uploaded code WASM and ABI from the chain, nor any deployed contracts. The forget operation only limits your access to the code on this browser.": "This operation does not remove the uploaded code WASM and ABI from the chain, nor any deployed contracts. The forget operation only limits your access to the code on this browser.",
"Unable to find deployed contract code at the specified address": "Unable to find deployed contract code at the specified address",
"Unable to find on-chain WASM code for the supplied codeHash": "Unable to find on-chain WASM code for the supplied codeHash",
"Understood, close this": "Understood, close this",
"Upload": "Upload",
"Upload WASM": "Upload WASM",
"With the ABI updates, there may still be dragons... the new ABI formats are a complete departure and required a lot of under-the-hood changes. So when something doesn't quite work as expected, please let us know via GH issues.": "With the ABI updates, there may still be dragons... the new ABI formats are a complete departure and required a lot of under-the-hood changes. So when something doesn't quite work as expected, please let us know via GH issues.",
"You are about to remove this code from your list of available code hashes. Once completed, should you need to access it again, you will have to manually add the code hash again.": "You are about to remove this code from your list of available code hashes. Once completed, should you need to access it again, you will have to manually add the code hash again.",
"You are about to remove this code's ABI. Once completed, should you need to access it again, you will have to manually re-upload it.": "You are about to remove this code's ABI. Once completed, should you need to access it again, you will have to manually re-upload it.",
"You are using an ABI with an outdated format. Please generate a new one.": "You are using an ABI with an outdated format. Please generate a new one.",
@ -59,17 +66,21 @@
"click to select or drag and drop a JSON ABI file": "click to select or drag and drop a JSON ABI file",
"code for this contract": "code for this contract",
"code hash": "code hash",
"code hashes": "code hashes",
"compiled contract WASM": "compiled contract WASM",
"contract address": "contract address",
"contract to use": "contract to use",
"contracts": "contracts",
"deploy": "deploy",
"deployment account": "deployment account",
"deployment constructor": "deployment constructor",
"endowment": "endowment",
"exec": "exec",
"execute": "execute",
"invalid ABI file selected": "invalid ABI file selected",
"maximum gas allowed": "maximum gas allowed",
"message to send": "message to send",
"read": "read",
"send as RPC call": "send as RPC call",
"send as transaction": "send as transaction",
"value": "value",

View File

@ -144,6 +144,7 @@
"Democracy overview": "",
"Deploy": "",
"Deploy a code hash": "",
"Deploy code hash": "",
"Deploy this code hash as a smart contract": "",
"Deploy with this constructor": "",
"Deposit": "",
@ -168,6 +169,7 @@
"Enter the Asset ID of the token you want to transfer.": "",
"Erroneous": "",
"Evaluated {{count}} keys in {{elapsed}}s ({{avg}} keys/s)": "",
"Execute": "",
"Extensions": "",
"External": "",
"Extrinsic submission": "",
@ -834,12 +836,14 @@
"code": "",
"code for this contract": "",
"code hash": "",
"code hashes": "",
"commission": "",
"compiled contract WASM": "",
"connected peers": "",
"contacts": "",
"contract address": "",
"contract to use": "",
"contracts": "",
"controller": "",
"controller account": "",
"conviction": "",

View File

@ -5,6 +5,7 @@ import { ThemeDef } from '@polkadot/react-components/types';
export const darkTheme: ThemeDef = {
bgInput: '#38393f',
bgInputError: '#48393f',
bgInverse: 'rgba(78, 78, 78, 0.66)',
bgMenu: '#26272c',
bgMenuHover: 'rgba(255, 255, 255, 0.05)',
@ -23,6 +24,7 @@ export const darkTheme: ThemeDef = {
export const lightTheme: ThemeDef = {
bgInput: '#fff',
bgInputError: '#fff6f6',
bgInverse: 'rgba(244, 242, 240, 0.91)',
bgMenu: '#fff',
bgMenuHover: 'rgba(255, 255, 255, 0.5)',

View File

@ -13,7 +13,7 @@
"dependencies": {
"@babel/runtime": "^7.11.2",
"@polkadot/react-components": "0.61.2-8",
"@polkadot/vanitygen": "^0.22.0-beta.2",
"@polkadot/vanitygen": "^0.22.1",
"detect-browser": "^5.1.1",
"file-saver": "^2.0.2"
},

View File

@ -13,7 +13,7 @@
"dependencies": {
"@babel/runtime": "^7.11.2",
"@polkadot/react-components": "0.61.2-8",
"@polkadot/vanitygen": "^0.22.0-beta.2",
"@polkadot/vanitygen": "^0.22.1",
"detect-browser": "^5.1.1",
"file-saver": "^2.0.2"
},

View File

@ -12,6 +12,6 @@
"license": "Apache-2.0",
"dependencies": {
"@babel/runtime": "^7.11.2",
"@polkadot/api-contract": "^2.1.1"
"@polkadot/api-contract": "^2.1.2-13"
}
}

View File

@ -4,10 +4,7 @@
import { StringOrNull } from '@polkadot/react-components/types';
import React, { useCallback, useMemo, useState } from 'react';
import { createType } from '@polkadot/types';
import { registry } from '@polkadot/react-api';
import { Button, Input, Modal } from '@polkadot/react-components';
import { useToggle } from '@polkadot/react-hooks';
import { isNull } from '@polkadot/util';
import { ABI, InputName } from '../shared';
@ -16,9 +13,12 @@ import store from '../store';
import { useTranslation } from '../translate';
import useAbi from '../useAbi';
function Add (): React.ReactElement {
interface Props {
onClose: () => void;
}
function Add ({ onClose }: Props): React.ReactElement {
const { t } = useTranslation();
const [isOpen, toggleIsOpen, setIsOpen] = useToggle();
const [codeHash, setCodeHash] = useState('');
const [isCodeHashValid, setIsCodeHashValid] = useState(false);
const [name, setName] = useState<StringOrNull>(null);
@ -41,64 +41,55 @@ function Add (): React.ReactElement {
}
store
.saveCode(createType(registry, 'Hash', codeHash), { abi, name, tags: [] })
.then((): void => setIsOpen(false))
.saveCode(codeHash, { abi, name, tags: [] })
.then(() => onClose())
.catch((error): void => {
console.error('Unable to save code', error);
});
},
[abi, codeHash, name, setIsOpen]
[abi, codeHash, name, onClose]
);
return (
<>
<Button
icon='plus'
label={t('Add an existing code hash')}
onClick={toggleIsOpen}
/>
{isOpen && (
<Modal header={t('Add an existing code hash')}>
<Modal.Content>
<Input
autoFocus
help={t('The code hash for the on-chain deployed code.')}
isError={codeHash.length > 0 && !isCodeHashValid}
label={t('code hash')}
onChange={setCodeHash}
value={codeHash}
/>
<ValidateCode
codeHash={codeHash}
onChange={setIsCodeHashValid}
/>
<InputName
isError={!isNameValid}
onChange={setName}
value={name || undefined}
/>
<ABI
contractAbi={contractAbi}
errorText={errorText}
isError={isAbiError}
isSupplied={isAbiSupplied}
isValid={isAbiValid}
onChange={onChangeAbi}
onRemove={onRemoveAbi}
withLabel
/>
</Modal.Content>
<Modal.Actions onCancel={toggleIsOpen}>
<Button
icon='save'
isDisabled={!isValid}
label={t('Save')}
onClick={_onSave}
/>
</Modal.Actions>
</Modal>
)}
</>
<Modal header={t('Add an existing code hash')}>
<Modal.Content>
<Input
autoFocus
help={t('The code hash for the on-chain deployed code.')}
isError={codeHash.length > 0 && !isCodeHashValid}
label={t('code hash')}
onChange={setCodeHash}
value={codeHash}
/>
<ValidateCode
codeHash={codeHash}
onChange={setIsCodeHashValid}
/>
<InputName
isError={!isNameValid}
onChange={setName}
value={name || undefined}
/>
<ABI
contractAbi={contractAbi}
errorText={errorText}
isError={isAbiError}
isSupplied={isAbiSupplied}
isValid={isAbiValid}
onChange={onChangeAbi}
onRemove={onRemoveAbi}
withLabel
/>
</Modal.Content>
<Modal.Actions onCancel={onClose}>
<Button
icon='save'
isDisabled={!isValid}
label={t('Save')}
onClick={_onSave}
/>
</Modal.Actions>
</Modal>
);
}

View File

@ -4,8 +4,7 @@
import { CodeStored } from '../types';
import React, { useCallback } from 'react';
import styled from 'styled-components';
import { Button, Card, Expander, Forget } from '@polkadot/react-components';
import { Button, Card, Forget } from '@polkadot/react-components';
import { useToggle } from '@polkadot/react-hooks';
import { ABI, CodeRow } from '../shared';
@ -23,7 +22,6 @@ interface Props {
function Code ({ className, code, onShowDeploy }: Props): React.ReactElement<Props> {
const { json: { codeHash } } = code;
const { t } = useTranslation();
const [isAbiOpen, toggleIsAbiOpen] = useToggle();
const [isForgetOpen, toggleIsForgetOpen] = useToggle();
const [isRemoveABIOpen, toggleIsRemoveABIOpen] = useToggle();
const { contractAbi, isAbiError, isAbiSupplied, isAbiValid, onChangeAbi, onRemoveAbi } = useAbi([code.json.abi || null, code.contractAbi || null], codeHash, true);
@ -55,83 +53,65 @@ function Code ({ className, code, onShowDeploy }: Props): React.ReactElement<Pro
[codeHash, toggleIsForgetOpen]
);
const abiNode = (
<ABI
contractAbi={contractAbi}
isError={isAbiError}
isSupplied={isAbiSupplied}
isValid={isAbiValid}
onChange={onChangeAbi}
onRemove={toggleIsRemoveABIOpen}
onSelectConstructor={_onDeployConstructor}
/>
);
return (
<Card className={className}>
<CodeRow
buttons={
<>
<Button
icon='trash'
onClick={toggleIsForgetOpen}
tooltip={t('Forget this code hash')}
/>
<Button
icon='upload'
label={t('deploy')}
onClick={_onShowDeploy}
tooltip={t('Deploy this code hash as a smart contract')}
/>
</>
}
code={code}
withTags
>
{contractAbi
? (
<Expander
isOpen={isAbiOpen}
onClick={toggleIsAbiOpen}
summary={t<string>('ABI')}
>
{abiNode}
</Expander>
)
: abiNode
}
</CodeRow>
{isForgetOpen && (
<Forget
key='modal-forget-account'
mode='code'
onClose={toggleIsForgetOpen}
onForget={_onForget}
>
<tr className={className}>
<td className='top'>
<Card>
<CodeRow
code={code || ''}
isInline
>
<p>{t<string>('You are about to remove this code from your list of available code hashes. Once completed, should you need to access it again, you will have to manually add the code hash again.')}</p>
<p>{t<string>('This operation does not remove the uploaded code WASM and ABI from the chain, nor any deployed contracts. The forget operation only limits your access to the code on this browser.')}</p>
</CodeRow>
</Forget>
)}
{isRemoveABIOpen && (
<RemoveABI
code={code}
key='modal-remove-abi'
onClose={toggleIsRemoveABIOpen}
onRemove={onRemoveAbi}
code={code}
withTags={false}
/>
{isForgetOpen && (
<Forget
key='modal-forget-account'
mode='code'
onClose={toggleIsForgetOpen}
onForget={_onForget}
>
<CodeRow
code={code || ''}
isInline
>
<p>{t<string>('You are about to remove this code from your list of available code hashes. Once completed, should you need to access it again, you will have to manually add the code hash again.')}</p>
<p>{t<string>('This operation does not remove the uploaded code WASM and ABI from the chain, nor any deployed contracts. The forget operation only limits your access to the code on this browser.')}</p>
</CodeRow>
</Forget>
)}
{isRemoveABIOpen && (
<RemoveABI
code={code}
key='modal-remove-abi'
onClose={toggleIsRemoveABIOpen}
onRemove={onRemoveAbi}
/>
)}
</Card>
</td>
<td className='all top'>
<ABI
contractAbi={contractAbi}
isError={isAbiError}
isSupplied={isAbiSupplied}
isValid={isAbiValid}
onChange={onChangeAbi}
onRemove={toggleIsRemoveABIOpen}
onSelectConstructor={_onDeployConstructor}
withMessages={false}
/>
)}
</Card>
</td>
<td className='button'>
<Button
icon='trash'
onClick={toggleIsForgetOpen}
/>
<Button
icon='upload'
label={t('deploy')}
onClick={_onShowDeploy}
/>
</td>
</tr>
);
}
export default React.memo(
styled(Code)`
max-width: 100%;
min-width: 100%;
`
);
export default React.memo(Code);

View File

@ -5,8 +5,8 @@ import { Hash } from '@polkadot/types/interfaces';
import React, { useCallback, useMemo, useState } from 'react';
import { SubmittableResult } from '@polkadot/api';
import { Button, InputAddress, InputFile, Modal, TxButton } from '@polkadot/react-components';
import { useAccountId, useApi, useNonEmptyString, useToggle } from '@polkadot/react-hooks';
import { InputAddress, InputFile, Modal, TxButton } from '@polkadot/react-components';
import { useAccountId, useNonEmptyString } from '@polkadot/react-hooks';
import { compactAddLength, isNull, isWasm } from '@polkadot/util';
import { ABI, InputName } from '../shared';
@ -14,17 +14,19 @@ import store from '../store';
import { useTranslation } from '../translate';
import useAbi from '../useAbi';
function Upload (): React.ReactElement {
interface Props {
onClose: () => void;
}
function Upload ({ onClose }: Props): React.ReactElement {
const { t } = useTranslation();
const { api } = useApi();
const [isOpen, toggleIsOpen] = useToggle();
const [accountId, setAccountId] = useAccountId();
const [[wasm, isWasmValid], setWasm] = useState<[Uint8Array | null, boolean]>([null, false]);
const [name, isNameValid, setName] = useNonEmptyString();
const { abi, contractAbi, errorText, isAbiError, isAbiSupplied, isAbiValid, onChangeAbi, onRemoveAbi } = useAbi();
const isSubmittable = useMemo(
(): boolean => !!accountId && (!isNull(name) && isNameValid) && isWasmValid && (!isAbiSupplied || isAbiValid),
() => !!accountId && (!isNull(name) && isNameValid) && isWasmValid && (!isAbiSupplied || isAbiValid),
[accountId, name, isAbiSupplied, isAbiValid, isNameValid, isWasmValid]
);
@ -38,8 +40,7 @@ function Upload (): React.ReactElement {
const _onSuccess = useCallback(
(result: SubmittableResult): void => {
const section = api.tx.contracts ? 'contracts' : 'contract';
const record = result.findRecord(section, 'CodeStored');
const record = result.findRecord('contracts', 'CodeStored');
if (record) {
const codeHash = record.event.data[0];
@ -55,69 +56,60 @@ function Upload (): React.ReactElement {
});
}
},
[api, abi, name]
[abi, name]
);
return (
<>
<Button
icon='plus'
label={t('Upload WASM')}
onClick={toggleIsOpen}
/>
{isOpen && (
<Modal header={t('Upload WASM')}>
<Modal.Content>
<InputAddress
help={t('Specify the user account to use for this deployment. Any fees will be deducted from this account.')}
isInput={false}
label={t('deployment account')}
onChange={setAccountId}
type='account'
value={accountId}
/>
<InputFile
help={t<string>('The compiled WASM for the contract that you wish to deploy. Each unique code blob will be attached with a code hash that can be used to create new instances.')}
isError={!isWasmValid}
label={t<string>('compiled contract WASM')}
onChange={_onAddWasm}
placeholder={
wasm && !isWasmValid
? t<string>('The code is not recognized as being in valid WASM format')
: null
}
/>
<InputName
isError={!isNameValid}
onChange={setName}
value={name || undefined}
/>
<ABI
contractAbi={contractAbi}
errorText={errorText}
isError={isAbiError}
isSupplied={isAbiSupplied}
isValid={isAbiValid}
onChange={onChangeAbi}
onRemove={onRemoveAbi}
withLabel
/>
</Modal.Content>
<Modal.Actions onCancel={toggleIsOpen}>
<TxButton
accountId={accountId}
icon='upload'
isDisabled={!isSubmittable}
label={t('Upload')}
onClick={toggleIsOpen}
onSuccess={_onSuccess}
params={[wasm]}
tx={api.tx.contracts ? 'contracts.putCode' : 'contract.putCode'}
/>
</Modal.Actions>
</Modal>
)}
</>
<Modal header={t('Upload WASM')}>
<Modal.Content>
<InputAddress
help={t('Specify the user account to use for this deployment. Any fees will be deducted from this account.')}
isInput={false}
label={t('deployment account')}
onChange={setAccountId}
type='account'
value={accountId}
/>
<InputFile
help={t<string>('The compiled WASM for the contract that you wish to deploy. Each unique code blob will be attached with a code hash that can be used to create new instances.')}
isError={!isWasmValid}
label={t<string>('compiled contract WASM')}
onChange={_onAddWasm}
placeholder={
wasm && !isWasmValid
? t<string>('The code is not recognized as being in valid WASM format')
: null
}
/>
<InputName
isError={!isNameValid}
onChange={setName}
value={name || undefined}
/>
<ABI
contractAbi={contractAbi}
errorText={errorText}
isError={isAbiError}
isSupplied={isAbiSupplied}
isValid={isAbiValid}
onChange={onChangeAbi}
onRemove={onRemoveAbi}
withLabel
/>
</Modal.Content>
<Modal.Actions onCancel={onClose}>
<TxButton
accountId={accountId}
icon='upload'
isDisabled={!isSubmittable}
label={t('Upload')}
onClick={onClose}
onSuccess={_onSuccess}
params={[wasm]}
tx='contracts.putCode'
/>
</Modal.Actions>
</Modal>
);
}

View File

@ -1,43 +1,43 @@
// Copyright 2017-2020 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { ComponentProps as Props } from '../types';
import React from 'react';
import { Button, CardGrid } from '@polkadot/react-components';
import React, { useRef } from 'react';
import { Table } from '@polkadot/react-components';
import contracts from '../store';
import { useTranslation } from '../translate';
import Code from './Code';
import Upload from './Upload';
import Add from './Add';
interface Props {
onShowDeploy: (codeHash?: string, constructorIndex?: number) => () => void;
updated: number;
}
function Codes ({ onShowDeploy }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const headerRef = useRef<[string?, string?, number?][]>([
[t('code hashes'), 'start'],
[],
[]
]);
return (
<>
<CardGrid
buttons={
<Button.Group isCentered>
<Upload />
<Add />
</Button.Group>
}
emptyText={t('No code hashes available')}
>
{contracts.getAllCode().map((code): React.ReactNode => {
return (
<Code
code={code}
key={code.json.codeHash}
onShowDeploy={onShowDeploy}
/>
);
})}
</CardGrid>
</>
<Table
empty={t<string>('No code hashes available')}
header={headerRef.current}
>
{contracts.getAllCode().map((code): React.ReactNode => {
return (
<Code
code={code}
key={code.json.codeHash}
onShowDeploy={onShowDeploy}
/>
);
})}
</Table>
);
}

View File

@ -6,7 +6,7 @@ import { ActionStatus } from '@polkadot/react-components/Status/types';
import React, { useCallback, useMemo, useState } from 'react';
import { AddressRow, Button, Input, Modal } from '@polkadot/react-components';
import { useApi, useNonEmptyString, useToggle } from '@polkadot/react-hooks';
import { useApi, useNonEmptyString } from '@polkadot/react-hooks';
import keyring from '@polkadot/ui-keyring';
import ValidateAddr from './ValidateAddr';
@ -14,10 +14,13 @@ import { ABI, InputName } from '../shared';
import { useTranslation } from '../translate';
import useAbi from '../useAbi';
function Add (): React.ReactElement {
interface Props {
onClose: () => void;
}
function Add ({ onClose }: Props): React.ReactElement {
const { t } = useTranslation();
const { api } = useApi();
const [isOpen, toggleIsOpen, setIsOpen] = useToggle();
const [address, setAddress] = useState<StringOrNull>(null);
const [isAddressValid, setIsAddressValid] = useState(false);
const [name, isNameValid, setName] = useNonEmptyString('New Contract');
@ -52,7 +55,7 @@ function Add (): React.ReactElement {
status.status = address ? 'success' : 'error';
status.message = 'contract added';
setIsOpen(false);
onClose();
} catch (error) {
console.error(error);
@ -60,68 +63,57 @@ function Add (): React.ReactElement {
status.message = (error as Error).message;
}
},
[abi, address, api, name, setIsOpen]
[abi, address, api, name, onClose]
);
return (
<>
<Button
icon='plus'
label={t('Add an existing contract')}
onClick={toggleIsOpen}
/>
<Modal
header={t('Add an existing contract')}
onClose={toggleIsOpen}
open={isOpen}
>
<Modal.Content>
<AddressRow
defaultName={name}
isValid
value={address || null}
>
<Input
autoFocus
help={t<string>('The address for the deployed contract instance.')}
isError={!isAddressValid}
label={t<string>('contract address')}
onChange={setAddress}
value={address || ''}
/>
<ValidateAddr
address={address}
onChange={setIsAddressValid}
/>
<InputName
isContract
isError={!isNameValid}
onChange={setName}
value={name || undefined}
/>
<ABI
contractAbi={contractAbi}
errorText={errorText}
isContract
isError={isAbiError}
isSupplied={isAbiSupplied}
isValid={isAbiValid}
onChange={onChangeAbi}
onRemove={onRemoveAbi}
withLabel
/>
</AddressRow>
</Modal.Content>
<Modal.Actions onCancel={toggleIsOpen}>
<Button
icon='save'
isDisabled={!isValid}
label={t<string>('Save')}
onClick={_onAdd}
<Modal header={t('Add an existing contract')}>
<Modal.Content>
<AddressRow
defaultName={name}
isValid
value={address || null}
>
<Input
autoFocus
help={t<string>('The address for the deployed contract instance.')}
isError={!isAddressValid}
label={t<string>('contract address')}
onChange={setAddress}
value={address || ''}
/>
</Modal.Actions>
</Modal>
</>
<ValidateAddr
address={address}
onChange={setIsAddressValid}
/>
<InputName
isContract
isError={!isNameValid}
onChange={setName}
value={name || undefined}
/>
<ABI
contractAbi={contractAbi}
errorText={errorText}
isContract
isError={isAbiError}
isSupplied={isAbiSupplied}
isValid={isAbiValid}
onChange={onChangeAbi}
onRemove={onRemoveAbi}
withLabel
/>
</AddressRow>
</Modal.Content>
<Modal.Actions onCancel={onClose}>
<Button
icon='save'
isDisabled={!isValid}
label={t<string>('Save')}
onClick={_onAdd}
/>
</Modal.Actions>
</Modal>
);
}

View File

@ -7,11 +7,11 @@ import { StringOrNull } from '@polkadot/react-components/types';
import BN from 'bn.js';
import React, { useCallback, useState, useEffect, useMemo } from 'react';
import styled from 'styled-components';
import { Button, ButtonCancel, Dropdown, IconLink, InputAddress, InputBalance, Modal, Toggle, TxButton } from '@polkadot/react-components';
import { Button, Dropdown, IconLink, InputAddress, InputBalance, Modal, TxButton } from '@polkadot/react-components';
import { PromiseContract as ApiContract } from '@polkadot/api-contract';
import { useAccountId, useFormField, useToggle } from '@polkadot/react-hooks';
import { createValue } from '@polkadot/react-params/values';
import { BN_ZERO, isNull } from '@polkadot/util';
import { BN_ZERO } from '@polkadot/util';
import { InputMegaGas, Params } from '../shared';
import Outcome from './Outcome';
@ -20,45 +20,30 @@ import { getCallMessageOptions } from './util';
import useWeight from '../useWeight';
interface Props {
callContract: ApiContract | null;
callMessageIndex: number | null;
callContract: ApiContract;
callMessageIndex: number;
className?: string;
isOpen: boolean;
onChangeCallContractAddress: (callContractAddress: StringOrNull) => void;
onChangeCallMessageIndex: (callMessageIndex: number) => void;
onClose: () => void;
}
function Call (props: Props): React.ReactElement<Props> | null {
function Call ({ callContract, callMessageIndex, className = '', onChangeCallContractAddress, onChangeCallMessageIndex, onClose }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { callContract, callMessageIndex, className = '', isOpen, onChangeCallContractAddress, onChangeCallMessageIndex, onClose } = props;
const hasRpc = callContract?.hasRpcContractsCall;
const callMessage = callContract?.getMessage(isNull(callMessageIndex) ? undefined : callMessageIndex);
const callMessage = callContract.abi.messages[callMessageIndex];
const [accountId, setAccountId] = useAccountId();
const [endowment, isEndowmentValid, setEndowment] = useFormField<BN>(BN_ZERO);
const [isBusy, , setIsBusy] = useToggle();
const [outcomes, setOutcomes] = useState<ContractCallOutcome[]>([]);
const [params, setParams] = useState<any[]>(callMessage ? callMessage.def.args.map(({ type }): any => createValue({ type })) : []);
const [useRpc, setUseRpc] = useState(hasRpc && callMessage && !callMessage.def.mutates);
const [params, setParams] = useState<any[]>(callMessage ? callMessage.args.map(({ type }) => createValue({ type })) : []);
const useWeightHook = useWeight();
const { isValid: isWeightValid, weight } = useWeightHook;
useEffect((): void => {
if (callContract && callMessageIndex) {
const callMessage = callContract.getMessage(callMessageIndex);
const callMessage = callContract.abi.messages[callMessageIndex];
setParams(callMessage ? callMessage.def.args.map(({ type }): any => createValue({ type })) : []);
if (hasRpc) {
if (!callMessage || callMessage.def.mutates) {
setUseRpc(false);
} else {
setUseRpc(true);
}
}
}
}, [callContract, callMessageIndex, hasRpc]);
setParams(callMessage ? callMessage.args.map(({ type }) => createValue({ type })) : []);
}, [callContract, callMessageIndex]);
useEffect((): void => {
setOutcomes([]);
@ -75,21 +60,23 @@ function Call (props: Props): React.ReactElement<Props> | null {
const _constructTx = useCallback(
(): unknown[] => {
if (!accountId || !callMessage || !callMessage.fn || !callContract || !callContract.address) {
if (!accountId || !callMessage || !callContract.address) {
return [];
}
return [callContract.address.toString(), endowment, weight, callMessage.fn(...params)];
return [callContract.address.toString(), endowment, weight, callMessage(...params)];
},
[accountId, callContract, callMessage, endowment, weight, params]
);
const _onSubmitRpc = useCallback(
(): void => {
if (!accountId || !callContract || !callMessage || !endowment || !weight) return;
if (!accountId || !callMessage || !endowment || !weight) return;
callContract
.call('rpc', callMessage.def.name, endowment, weight, ...params)
.read(callMessage, 0, weight, ...params)
// when we make calls to mutables, we want the endowment & weight
// .read(callMessage, endowment, weight, ...params)
.send(accountId)
.then((outcome: ContractCallOutcome) => setOutcomes([outcome, ...outcomes]))
.catch(console.error);
@ -108,20 +95,17 @@ function Call (props: Props): React.ReactElement<Props> | null {
);
const isValid = useMemo(
() => !!accountId && !!callContract && !!callContract.address && !!callContract.abi && isWeightValid && isEndowmentValid,
[accountId, callContract, isEndowmentValid, isWeightValid]
() => !!(accountId && isWeightValid && isEndowmentValid),
[accountId, isEndowmentValid, isWeightValid]
);
if (isNull(callContract) || isNull(callMessageIndex) || !callMessage) {
return null;
}
const isViaRpc = callContract.hasRpcContractsCall && !callMessage.isMutating;
return (
<Modal
className={[className || '', 'app--contracts-Modal'].join(' ')}
header={t<string>('Call a contract')}
onClose={onClose}
open={isOpen}
>
<Modal.Content>
{callContract && (
@ -146,35 +130,37 @@ function Call (props: Props): React.ReactElement<Props> | null {
{callMessageIndex !== null && (
<>
<Dropdown
defaultValue={`${callMessage.index}`}
defaultValue={`${callMessageIndex}`}
help={t<string>('The message to send to this contract. Parameters are adjusted based on the ABI provided.')}
isDisabled={isBusy}
isError={callMessage === null}
label={t<string>('message to send')}
onChange={_onChangeCallMessageIndexString}
options={getCallMessageOptions(callContract)}
value={`${callMessage.index}`}
value={`${callMessageIndex}`}
/>
<Params
isDisabled={isBusy}
onChange={setParams}
params={
callMessage
? callMessage.def.args
? callMessage.args
: undefined
}
/>
</>
)}
<InputBalance
help={t<string>('The allotted value for this contract, i.e. the amount transferred to the contract as part of this call.')}
isDisabled={isBusy}
isError={!isEndowmentValid}
isZeroable
label={t<string>('value')}
onChange={setEndowment}
value={endowment}
/>
{!isViaRpc && (
<InputBalance
help={t<string>('The allotted value for this contract, i.e. the amount transferred to the contract as part of this call.')}
isDisabled={isBusy}
isError={!isEndowmentValid}
isZeroable
label={t<string>('value')}
onChange={setEndowment}
value={endowment}
/>
)}
<InputMegaGas
help={t<string>('The maximum amount of gas to use for this contract call. If the call requires more, it will fail.')}
label={t<string>('maximum gas allowed')}
@ -182,45 +168,6 @@ function Call (props: Props): React.ReactElement<Props> | null {
/>
</div>
)}
{hasRpc && (
<Toggle
className='rpc-toggle'
label={
useRpc
? t<string>('send as RPC call')
: t<string>('send as transaction')
}
onChange={setUseRpc}
value={useRpc || false}
/>
)}
<Button.Group>
<ButtonCancel onClick={onClose} />
{useRpc
? (
<Button
icon='sign-in-alt'
isDisabled={!isValid}
label={t<string>('Call')}
onClick={_onSubmitRpc}
/>
)
: (
<TxButton
accountId={accountId}
icon='sign-in-alt'
isDisabled={!isValid}
label={t('Call')}
onClick={(): void => setIsBusy(true)}
onFailed={(): void => setIsBusy(false)}
onSuccess={(): void => setIsBusy(false)}
params={_constructTx}
tx='contracts.call'
withSpinner
/>
)
}
</Button.Group>
{outcomes.length > 0 && (
<>
<h3>
@ -244,6 +191,32 @@ function Call (props: Props): React.ReactElement<Props> | null {
</>
)}
</Modal.Content>
<Modal.Actions onCancel={onClose}>
{isViaRpc
? (
<Button
icon='sign-in-alt'
isDisabled={!isValid}
label={t<string>('Read')}
onClick={_onSubmitRpc}
/>
)
: (
<TxButton
accountId={accountId}
icon='sign-in-alt'
isDisabled={!isValid}
label={t('Execute')}
onClick={(): void => setIsBusy(true)}
onFailed={(): void => setIsBusy(false)}
onSuccess={(): void => setIsBusy(false)}
params={_constructTx}
tx='contracts.call'
withSpinner
/>
)
}
</Modal.Actions>
</Modal>
);
}

View File

@ -4,10 +4,9 @@
import { ActionStatus } from '@polkadot/react-components/Status/types';
import React, { useCallback } from 'react';
import styled from 'styled-components';
import keyring from '@polkadot/ui-keyring';
import { PromiseContract as ApiContract } from '@polkadot/api-contract';
import { AddressRow, Button, Card, Expander, Forget } from '@polkadot/react-components';
import { AddressRow, Button, Card, Forget } from '@polkadot/react-components';
import { useToggle } from '@polkadot/react-hooks';
import Messages from '../shared/Messages';
@ -53,57 +52,52 @@ function Contract ({ className, contract: { abi, address }, onCall }: Props): Re
}
return (
<Card className={className}>
{isForgetOpen && (
<Forget
address={address.toString()}
key='modal-forget-contract'
mode='contract'
onClose={toggleIsForgetOpen}
onForget={_onForget}
/>
)}
<AddressRow
buttons={
<div className='contracts--Contract-buttons'>
<Button
icon='trash'
onClick={toggleIsForgetOpen}
tooltip={t<string>('Forget this contract')}
<tr className={className}>
<td className='top'>
<Card>
{isForgetOpen && (
<Forget
address={address.toString()}
key='modal-forget-contract'
mode='contract'
onClose={toggleIsForgetOpen}
onForget={_onForget}
/>
<Button
icon='play'
label={t<string>('execute')}
onClick={onCall()}
tooltip={t<string>('Call a method on this contract')}
/>
</div>
}
isContract
isEditableName
isEditableTags
type='contract'
value={address}
withBalance={false}
withNonce={false}
withTags
>
<Expander summary={t<string>('Messages')}>
<Messages
address={address.toString()}
contractAbi={abi}
isRemovable={false}
onSelect={onCall}
)}
<AddressRow
isContract
isEditableName
isEditableTags
type='contract'
value={address}
withBalance={false}
withNonce={false}
withTags={false}
/>
</Expander>
</AddressRow>
</Card>
</Card>
</td>
<td className='all top'>
<Messages
address={address.toString()}
contractAbi={abi}
isRemovable={false}
onSelect={onCall}
withMessages
/>
</td>
<td className='button'>
<Button
icon='trash'
onClick={toggleIsForgetOpen}
/>
<Button
icon='play'
label={t<string>('exec')}
onClick={onCall()}
/>
</td>
</tr>
);
}
export default React.memo(
styled(Contract)`
min-width: 100%;
max-width: 100%;
`
);
export default React.memo(Contract);

View File

@ -46,7 +46,6 @@ function Outcome ({ className = '', onClear, outcome: { isSuccess, message, orig
className='output'
isError={!isSuccess}
value={(output || '()').toString()}
withCopy
withLabel={false}
/>
</div>

View File

@ -2,45 +2,56 @@
// SPDX-License-Identifier: Apache-2.0
import { StringOrNull } from '@polkadot/react-components/types';
import { ComponentProps as Props } from '../types';
import React, { useState, useEffect } from 'react';
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { ApiPromise } from '@polkadot/api';
import { PromiseContract as ApiContract } from '@polkadot/api-contract';
import { Button, CardGrid } from '@polkadot/react-components';
import { Table } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
import { useTranslation } from '../translate';
import Add from './Add';
import ContractCard from './Contract';
import Call from './Call';
import { getContractForAddress } from './util';
function filterContracts (api: ApiPromise, { accounts, contracts: keyringContracts }: Props): ApiContract[] {
return accounts && keyringContracts && keyringContracts
.map((address): ApiContract | null => getContractForAddress(api, address.toString()))
.filter((contract: ApiContract | null): boolean => !!contract) as ApiContract[];
export interface Props {
contracts: string[];
updated: number;
}
function Contracts (props: Props): React.ReactElement<Props> {
function filterContracts (api: ApiPromise, keyringContracts: string[] = []): ApiContract[] {
return keyringContracts
.map((address): ApiContract | null => getContractForAddress(api, address.toString()))
.filter((contract: ApiContract | null): contract is ApiContract => !!contract);
}
function Contracts ({ contracts: keyringContracts }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const { accounts, contracts: keyringContracts, hasCode, onShowDeploy } = props;
const [contracts, setContracts] = useState<ApiContract[]>(filterContracts(api, props));
const [callContractIndex, setCallContractIndex] = useState<number>(0);
const [callMessageIndex, setCallMessageIndex] = useState<number>(0);
const [isCallOpen, setIsCallOpen] = useState(false);
useEffect((): void => {
setContracts(filterContracts(api, props));
}, [accounts, api, keyringContracts, props]);
const headerRef = useRef<[string?, string?, number?][]>([
[t('contracts'), 'start'],
[],
[]
]);
const contracts = useMemo(
() => filterContracts(api, keyringContracts),
[api, keyringContracts]
);
const callContract = contracts[callContractIndex] || null;
const _toggleCall = (): void => setIsCallOpen(!isCallOpen);
const _toggleCall = useCallback(
() => setIsCallOpen((isCallOpen) => !isCallOpen),
[]
);
const _onChangeCallContractAddress = (newCallContractAddress: StringOrNull): void => {
const index = contracts.findIndex(({ address }: ApiContract): boolean => newCallContractAddress === address.toString());
const index = contracts.findIndex(({ address }: ApiContract) => newCallContractAddress === address.toString());
if (index > -1) {
index !== callContractIndex && setCallMessageIndex(0);
@ -48,36 +59,29 @@ function Contracts (props: Props): React.ReactElement<Props> {
}
};
const _onChangeCallMessageIndex = (callMessageIndex: number): void => {
!!callContract && setCallMessageIndex(callMessageIndex);
};
const _onChangeCallMessageIndex = useCallback(
(callMessageIndex: number): void => {
contracts[callContractIndex] && setCallMessageIndex(callMessageIndex);
},
[callContractIndex, contracts]
);
const _onCall = (callContractIndex: number): (_?: number) => () => void => {
return function (callMessageIndex?: number): () => void {
return function (): void {
setCallContractIndex(callContractIndex);
setCallMessageIndex(callMessageIndex || 0);
setIsCallOpen(true);
};
};
};
const _onCall = useCallback(
(callContractIndex: number) =>
(callMessageIndex?: number) =>
(): void => {
setCallContractIndex(callContractIndex);
setCallMessageIndex(callMessageIndex || 0);
setIsCallOpen(true);
},
[]
);
return (
<>
<CardGrid
buttons={
<Button.Group isCentered>
{hasCode && (
<Button
icon='upload'
label={t('Deploy a code hash')}
onClick={onShowDeploy()}
/>
)}
<Add />
</Button.Group>
}
emptyText={t<string>('No contracts available')}
<Table
empty={t<string>('No contracts available')}
header={headerRef.current}
>
{contracts.map((contract: ApiContract, index): React.ReactNode => (
<ContractCard
@ -86,15 +90,17 @@ function Contracts (props: Props): React.ReactElement<Props> {
onCall={_onCall(index)}
/>
))}
</CardGrid>
<Call
callContract={callContract}
callMessageIndex={callMessageIndex}
isOpen={isCallOpen}
onChangeCallContractAddress={_onChangeCallContractAddress}
onChangeCallMessageIndex={_onChangeCallMessageIndex}
onClose={_toggleCall}
/>
</Table>
{isCallOpen && callContract && (
<Call
callContract={callContract}
callMessageIndex={callMessageIndex}
isOpen={isCallOpen}
onChangeCallContractAddress={_onChangeCallContractAddress}
onChangeCallMessageIndex={_onChangeCallMessageIndex}
onClose={_toggleCall}
/>
)}
</>
);
}

View File

@ -1,7 +1,7 @@
// Copyright 2017-2020 @polkadot/app-contracts authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { ContractABIFn, ContractABIMessage } from '@polkadot/api-contract/types';
import { AbiMessage } from '@polkadot/api-contract/types';
import { StringOrNull } from '@polkadot/react-components/types';
import React from 'react';
@ -11,13 +11,13 @@ import { getContractAbi } from '@polkadot/react-components/util';
import MessageSignature from '../shared/MessageSignature';
export function findCallMethod (callContract: Contract | null, callMethodIndex = 0): ContractABIMessage | null {
const message = callContract && callContract.abi.abi.contract.messages[callMethodIndex];
export function findCallMethod (callContract: Contract | null, callMethodIndex = 0): AbiMessage | null {
const message = callContract && callContract.abi.messages[callMethodIndex];
return message || null;
}
export function getContractMethodFn (callContract: Contract | null, callMethodIndex: number | null): ContractABIFn | null {
export function getContractMethodFn (callContract: Contract | null, callMethodIndex: number | null): AbiMessage | null {
const fn = callContract && callContract.abi && callMethodIndex !== null && callContract.abi.messages[callMethodIndex];
return fn || null;
@ -37,9 +37,9 @@ export function getContractForAddress (api: ApiPromise, address: StringOrNull):
export function getCallMessageOptions (callContract: Contract | null): any[] {
return callContract
? callContract.messages.map(({ def: message, def: { name }, index }): { key: string; text: React.ReactNode; value: string } => {
? callContract.abi.messages.map((message, index): { key: string; text: React.ReactNode; value: string } => {
return {
key: name,
key: message.identifier,
text: (
<MessageSignature message={message} />
),

View File

@ -29,7 +29,6 @@ interface Props {
allCodes: CodeStored[];
codeHash: string;
constructorIndex?: number;
isOpen?: boolean;
onClose: VoidFn;
setCodeHash: React.Dispatch<string>;
setConstructorIndex: React.Dispatch<number>;
@ -39,7 +38,7 @@ function defaultContractName (name: string) {
return `${name} (instance)`;
}
function Deploy ({ allCodes, basePath, codeHash, constructorIndex = 0, isOpen, onClose, setCodeHash, setConstructorIndex }: Props): React.ReactElement<Props> {
function Deploy ({ allCodes, basePath, codeHash, constructorIndex = 0, onClose, setCodeHash, setConstructorIndex }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const history = useHistory();
@ -73,7 +72,7 @@ function Deploy ({ allCodes, basePath, codeHash, constructorIndex = 0, isOpen, o
return [];
}
return contractAbi.abi.contract.constructors.map((message, index) => ({
return contractAbi.constructors.map((message, index) => ({
key: `${index}`,
text: (
<MessageSignature
@ -87,10 +86,10 @@ function Deploy ({ allCodes, basePath, codeHash, constructorIndex = 0, isOpen, o
[contractAbi]
);
const [params, setParams] = useState<unknown[]>(contractAbi && constructorIndex >= 0 ? contractAbi.abi.contract.constructors[constructorIndex].args : []);
const [params, setParams] = useState<unknown[]>(contractAbi && constructorIndex >= 0 ? contractAbi.constructors[constructorIndex].args : []);
useEffect(
() => setParams(contractAbi ? contractAbi.abi.contract.constructors[constructorIndex].args : []),
() => setParams(contractAbi ? contractAbi.constructors[constructorIndex].args : []),
[constructorIndex, contractAbi]
);
@ -138,11 +137,7 @@ function Deploy ({ allCodes, basePath, codeHash, constructorIndex = 0, isOpen, o
);
return (
<Modal
header={t('Add an existing code hash')}
onClose={onClose}
open={isOpen}
>
<Modal header={t('Add an existing code hash')}>
<Modal.Content>
<InputAddress
help={t('Specify the user account to use for this deployment. Any fees will be deducted from this account.')}
@ -170,41 +165,39 @@ function Deploy ({ allCodes, basePath, codeHash, constructorIndex = 0, isOpen, o
onChange={setName}
value={name || ''}
/>
{
isAbiSupplied
? null
: (
<ABI
contractAbi={contractAbi}
errorText={errorText}
isError={isAbiError}
isSupplied={isAbiSupplied}
isValid={isAbiValid}
onChange={onChangeAbi}
onRemove={onRemoveAbi}
withLabel
/>
)
{isAbiSupplied
? null
: (
<ABI
contractAbi={contractAbi}
errorText={errorText}
isError={isAbiError}
isSupplied={isAbiSupplied}
isValid={isAbiValid}
onChange={onChangeAbi}
onRemove={onRemoveAbi}
withLabel
/>
)
}
{
contractAbi
? (
<Dropdown
help={t<string>('The deployment constructor information for this contract, as provided by the ABI.')}
isDisabled={contractAbi.abi.contract.constructors.length <= 1}
label={t('deployment constructor')}
onChange={setConstructorIndex}
options={constructOptions}
value={`${constructorIndex}`}
/>
)
: null
{contractAbi
? (
<Dropdown
help={t<string>('The deployment constructor information for this contract, as provided by the ABI.')}
isDisabled={contractAbi.constructors.length <= 1}
label={t('deployment constructor')}
onChange={setConstructorIndex}
options={constructOptions}
value={`${constructorIndex}`}
/>
)
: null
}
<Params
onChange={setParams}
params={
contractAbi && constructorIndex >= 0
? contractAbi.abi.contract.constructors[constructorIndex].args
? contractAbi.constructors[constructorIndex].args
: []
}
/>
@ -230,15 +223,7 @@ function Deploy ({ allCodes, basePath, codeHash, constructorIndex = 0, isOpen, o
onClick={onClose}
onSuccess={_onSuccess}
params={_constructCall}
tx={
api.tx.contracts
? (
!api.tx.contracts.instantiate
? 'contracts.create' // V2 (new)
: 'contracts.instantiate' // V2 (old)
)
: 'contract.create' // V1
}
tx='contracts.instantiate'
withSpinner
/>
</Modal.Actions>

View File

@ -0,0 +1,48 @@
// Copyright 2017-2020 @polkadot/app-contracts authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
import styled from 'styled-components';
import { Button } from '@polkadot/react-components';
import { useToggle } from '@polkadot/react-hooks';
import { useTranslation } from './translate';
interface Props {
className?: string;
}
function Ink3Banner ({ className = '' }: Props): React.ReactElement<Props> | null {
const [isVisible, toggleVisible] = useToggle(true);
const { t } = useTranslation();
if (!isVisible) {
return null;
}
return (
<article className={`${className} warning centered`}>
<div className='box'>
<p>{t<string>('This app now only supports Substrate 2.0 with Ink! 3.0. As such versions older of the contracts ABI cannot be used and will display an error when uploaded. Ensure you follow the Ink! instructions for updating if you are using an older version.')}</p>
<p>{t<string>("With the ABI updates, there may still be dragons... the new ABI formats are a complete departure and required a lot of under-the-hood changes. So when something doesn't quite work as expected, please let us know via GH issues.")}</p>
<Button.Group>
<Button
icon='times'
label={t<string>('Understood, close this')}
onClick={toggleVisible}
/>
</Button.Group>
</div>
</article>
);
}
export default React.memo(styled(Ink3Banner)`
.box {
padding: 0 0.5rem;
.ui--Button-Group {
margin-bottom: 0;
}
}
`);

View File

@ -2,35 +2,36 @@
// SPDX-License-Identifier: Apache-2.0
import { AppProps as Props } from '@polkadot/react-components/types';
import { ComponentProps } from './types';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Route, Switch } from 'react-router';
import { HelpOverlay, Tabs } from '@polkadot/react-components';
import { useAccounts, useContracts, useToggle } from '@polkadot/react-hooks';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import { Button, HelpOverlay, Tabs } from '@polkadot/react-components';
import { useContracts, useToggle } from '@polkadot/react-hooks';
import introMd from './md/intro.md';
import store from './store';
import Contracts from './Contracts';
import ContractAdd from './Contracts/Add';
import Codes from './Codes';
import CodeAdd from './Codes/Add';
import CodeUpload from './Codes/Upload';
import Deploy from './Deploy';
import Ink3Banner from './Ink3Banner';
import { useTranslation } from './translate';
function ContractsApp ({ basePath, onStatusChange }: Props): React.ReactElement<Props> {
function ContractsApp ({ basePath, className = '' }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { allAccounts } = useAccounts();
const { allContracts } = useContracts();
const [codeHash, setCodeHash] = useState<string | undefined>();
const [constructorIndex, setConstructorIndex] = useState(0);
const [isDeployOpen, toggleIsDeployOpen, setIsDeployOpen] = useToggle();
const [isAddOpen, toggleAdd] = useToggle();
const [isDeployOpen, toggleDeploy, setIsDeployOpen] = useToggle();
const [isHashOpen, toggleHash] = useToggle();
const [isUploadOpen, toggleUpload] = useToggle();
const [updated, setUpdated] = useState(0);
const [allCodes, setAllCodes] = useState(store.getAllCode());
const itemsRef = useRef([
{
name: 'code',
text: t('Code')
},
{
isRoot: true,
name: 'contracts',
@ -38,22 +39,14 @@ function ContractsApp ({ basePath, onStatusChange }: Props): React.ReactElement<
}
]);
const _triggerUpdate = useCallback(
(): void => {
setUpdated(Date.now());
setAllCodes(store.getAllCode());
},
[]
);
const _onShowDeploy = useCallback(
(codeHash?: string, constructorIndex = 0): () => void =>
(): void => {
setCodeHash(codeHash || (allCodes && allCodes[0] ? allCodes[0].json.codeHash : undefined));
setConstructorIndex(constructorIndex);
toggleIsDeployOpen();
toggleDeploy();
},
[allCodes, toggleIsDeployOpen]
[allCodes, toggleDeploy]
);
const _onCloseDeploy = useCallback(
@ -61,35 +54,27 @@ function ContractsApp ({ basePath, onStatusChange }: Props): React.ReactElement<
[setIsDeployOpen]
);
const componentProps = useMemo(
(): ComponentProps => ({
accounts: allAccounts,
basePath,
contracts: allContracts,
hasCode: store.hasCode,
onShowDeploy: _onShowDeploy,
onStatusChange,
updated
}),
[allAccounts, allContracts, basePath, _onShowDeploy, onStatusChange, updated]
);
useEffect(
(): void => {
store.on('new-code', _triggerUpdate);
store.on('removed-code', _triggerUpdate);
const triggerUpdate = (): void => {
setUpdated(Date.now());
setAllCodes(store.getAllCode());
};
store.loadAll()
.then((): void => setAllCodes(store.getAllCode()))
store.on('new-code', triggerUpdate);
store.on('removed-code', triggerUpdate);
store
.loadAll()
.then(() => setAllCodes(store.getAllCode()))
.catch((): void => {
// noop, handled internally
});
},
[_triggerUpdate]
[]
);
return (
<main className='contracts--App'>
<main className={`contracts--App ${className}`}>
<HelpOverlay md={introMd as string} />
<header>
<Tabs
@ -97,28 +82,67 @@ function ContractsApp ({ basePath, onStatusChange }: Props): React.ReactElement<
items={itemsRef.current}
/>
</header>
<Switch>
<Route path={`${basePath}/code`}>
<Codes {...componentProps} />
</Route>
<Route exact>
<Contracts {...componentProps} />
</Route>
</Switch>
{codeHash && (
<Button.Group>
<Button
icon='plus'
label={t('Upload WASM')}
onClick={toggleUpload}
/>
<Button
icon='plus'
label={t('Add an existing code hash')}
onClick={toggleHash}
/>
{/* <Button
icon='upload'
isDisabled={!store.hasCode}
label={t('Deploy code hash')}
onClick={_onShowDeploy()}
/> */}
<Button
icon='plus'
label={t('Add an existing contract')}
onClick={toggleAdd}
/>
</Button.Group>
<Ink3Banner />
<Contracts
contracts={allContracts}
updated={updated}
/>
<Codes
onShowDeploy={_onShowDeploy}
updated={updated}
/>
{codeHash && isDeployOpen && (
<Deploy
allCodes={allCodes}
basePath={basePath}
codeHash={codeHash}
constructorIndex={constructorIndex}
isOpen={isDeployOpen}
onClose={_onCloseDeploy}
setCodeHash={setCodeHash}
setConstructorIndex={setConstructorIndex}
/>
)}
{isUploadOpen && (
<CodeUpload onClose={toggleUpload} />
)}
{isHashOpen && (
<CodeAdd onClose={toggleHash} />
)}
{isAddOpen && (
<ContractAdd onClose={toggleAdd} />
)}
</main>
);
}
export default React.memo(ContractsApp);
export default React.memo(styled(ContractsApp)`
.ui--Table td > article {
background: transparent;
border: none;
margin: 0;
padding: 0;
}
`);

View File

@ -25,10 +25,12 @@ interface Props {
onRemoved?: () => void;
onSelect?: () => void;
onSelectConstructor?: (constructorIndex?: number) => void;
withConstructors?: boolean;
withLabel?: boolean;
withMessages?: boolean;
}
function renderMessages ({ contractAbi, isDisabled, onRemove, onSelectConstructor, withLabel }: Props): React.ReactNode {
function renderMessages ({ contractAbi, isDisabled, onRemove, onSelectConstructor, withConstructors = true, withLabel, withMessages = true }: Props): React.ReactNode {
return (
<Messages
contractAbi={contractAbi}
@ -36,7 +38,8 @@ function renderMessages ({ contractAbi, isDisabled, onRemove, onSelectConstructo
isRemovable={!isDisabled}
onRemove={onRemove}
onSelectConstructor={onSelectConstructor}
withConstructors
withConstructors={withConstructors}
withMessages={withMessages}
/>
);
}

View File

@ -9,7 +9,7 @@ import { createType } from '@polkadot/types';
import { registry } from '@polkadot/react-api';
import { toShortAddress } from '@polkadot/react-components/util';
import Row from '@polkadot/react-components/Row';
import { CopyButton, Icon } from '@polkadot/react-components';
import { Icon } from '@polkadot/react-components';
import contracts from '../store';
@ -59,22 +59,12 @@ function CodeRow ({ buttons, children, className, code: { json }, isInline, with
return (
<Row
address={
<CopyButton
isAddress
value={codeHash}
>
<span>{toShortAddress(codeHash)}</span>
</CopyButton>
}
address={<span>{toShortAddress(codeHash)}</span>}
buttons={buttons}
className={className}
icon={
<div className='ui--CodeRow-icon'>
<Icon
icon='code'
size='large'
/>
<Icon icon='code' />
</div>
}
isEditableName
@ -97,9 +87,10 @@ export default React.memo(
.ui--CodeRow-icon {
margin-right: 1em;
background: #eee;
border-radius: 50%;
color: #666;
width: 4rem;
height: 5rem;
width: 32px;
height: 32px;
padding: 0.5rem;
display: flex;
justify-content: flex-end;

View File

@ -1,10 +1,12 @@
// Copyright 2017-2020 @polkadot/app-contracts authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { ThemeProps } from '@polkadot/react-components/types';
import BN from 'bn.js';
import React from 'react';
import styled from 'styled-components';
import { InputNumber, Progress } from '@polkadot/react-components';
import { InputNumber } from '@polkadot/react-components';
import { useTranslation } from '../translate';
@ -33,51 +35,24 @@ function InputMegaGas ({ className, executionTime, help, isValid, label, megaGas
value={megaGas}
>
<div className='contracts--InputMegaGas-meter'>
{t<string>('{{executionTime}}s execution time', { replace: { executionTime: executionTime.toFixed(3) } })}
<aside>
{t<string>('{{percentage}}% of block time', { replace: { percentage } })}
</aside>
<Progress
className='contracts--InputMegaGas-progress'
total={100}
value={percentage}
/>
<div>{t<string>('{{executionTime}}s execution time', { replace: { executionTime: executionTime.toFixed(3) } })}</div>
<div>{t<string>('{{percentage}}% of block time', { replace: { percentage } })}</div>
</div>
</InputNumber>
</div>
);
}
export default React.memo(
styled(InputMegaGas)`
.contracts--InputMegaGas-input {
.ui.input {
display: flex;
input {
max-width: 15rem;
}
.contracts--InputMegaGas-meter {
flex: 1;
padding: 0.8rem 0.8rem 0;
aside {
float: right;
}
.contracts--InputMegaGas-progress {
margin-top: 0.4rem;
position: relative;
bottom: 0;
left: 0;
right: 0;
}
}
export default React.memo(styled(InputMegaGas)(({ theme }: ThemeProps) => `
.contracts--InputMegaGas-input {
.ui.input {
display: flex;
.contracts--InputMegaGas-meter {
color: ${theme.color};
flex: 1;
padding: 0.8rem 0.8rem 0;
}
}
`
);
}
`));

View File

@ -1,12 +1,12 @@
// Copyright 2017-2020 @polkadot/react-components authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { ContractABIMessage } from '@polkadot/api-contract/types';
import { AbiMessage } from '@polkadot/api-contract/types';
import React from 'react';
import styled from 'styled-components';
import { Icon, Tooltip } from '@polkadot/react-components';
import { displayType } from '@polkadot/types';
import { encodeTypeDef } from '@polkadot/types';
import { useTranslation } from '../translate';
@ -15,7 +15,7 @@ const MAX_PARAM_LENGTH = 20;
export interface Props {
asConstructor?: boolean;
className?: string;
message: ContractABIMessage;
message: AbiMessage;
params?: any[];
withTooltip?: boolean;
}
@ -26,16 +26,13 @@ function truncate (param: string): string {
: param;
}
function MessageSignature ({ className, message: { args, mutates, name, returnType }, params = [], asConstructor = false, withTooltip = false }: Props): React.ReactElement<Props> {
function MessageSignature ({ className, message: { args, identifier, isConstructor, isMutating, returnType }, params = [], withTooltip = false }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
return (
<div className={className}>
<span className='ui--MessageSignature-name'>
{name}
</span>
(
{args.map(({ name, type }, index): React.ReactNode => {
<span className='ui--MessageSignature-name'>{identifier}</span>
{' '}({args.map(({ name, type }, index): React.ReactNode => {
return (
<React.Fragment key={`${name}-args-${index}`}>
{name}:
@ -43,34 +40,33 @@ function MessageSignature ({ className, message: { args, mutates, name, returnTy
<span className='ui--MessageSignature-type'>
{params && params[index]
? <b>{truncate((params as string[])[index].toString())}</b>
: displayType(type)
: encodeTypeDef(type)
}
</span>
{index < args.length - 1 && ', '}
{index < (args.length) - 1 && ', '}
</React.Fragment>
);
})}
)
{(!asConstructor && returnType) && (
})})
{(!isConstructor && returnType) && (
<>
:
{' '}
<span className='ui--MessageSignature-returnType'>
{displayType(returnType)}
{encodeTypeDef(returnType)}
</span>
</>
)}
{mutates && (
{isMutating && (
<>
<Icon
className='ui--MessageSignature-mutates'
icon='database'
tooltip={`mutates-${name}`}
tooltip={`mutates-${identifier}`}
/>
{withTooltip && (
<Tooltip
text={t<string>('Mutates contract state')}
trigger={`mutates-${name}`}
trigger={`mutates-${identifier}`}
/>
)}
</>
@ -79,29 +75,27 @@ function MessageSignature ({ className, message: { args, mutates, name, returnTy
);
}
export default React.memo(
styled(MessageSignature)`
font-family: monospace;
font-weight: normal;
flex-grow: 1;
export default React.memo(styled(MessageSignature)`
font-family: monospace;
font-weight: normal;
flex-grow: 1;
.ui--MessageSignature-mutates {
color: #ff8600;
margin-left: 0.5rem;
opacity: 0.6;
}
.ui--MessageSignature-mutates {
color: #ff8600;
margin-left: 0.5rem;
opacity: 0.6;
}
.ui--MessageSignature-name {
color: #2f8ddb;
font-weight: bold;
}
.ui--MessageSignature-name {
color: #2f8ddb;
font-weight: bold;
}
.ui--MessageSignature-type {
color: #21a2b2;
}
.ui--MessageSignature-type {
color: #21a2b2;
}
.ui--MessageSignature-returnType {
color: #ff8600;
}
`
);
.ui--MessageSignature-returnType {
color: #ff8600;
}
`);

View File

@ -1,7 +1,7 @@
// Copyright 2017-2020 @polkadot/react-components authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { ContractABIMessage } from '@polkadot/api-contract/types';
import { AbiConstructor, AbiMessage } from '@polkadot/api-contract/types';
import React from 'react';
import styled from 'styled-components';
@ -22,13 +22,14 @@ export interface Props {
onSelect?: (messageIndex: number) => () => void;
onSelectConstructor?: (constructorIndex: number) => void;
withConstructors?: boolean;
withMessages?: boolean;
}
const NOOP = (): void => undefined;
function onSelect (props: Props, messageIndex: number): () => void {
return function (): void {
const { address: callAddress, contractAbi: { abi: { contract: { messages } } }, onSelect } = props;
const { address: callAddress, contractAbi: { messages }, onSelect } = props;
if (!callAddress || !messages || !messages[messageIndex]) {
return;
@ -40,7 +41,7 @@ function onSelect (props: Props, messageIndex: number): () => void {
function onSelectConstructor (props: Props, index: number): () => void {
return function (): void {
const { contractAbi: { abi: { contract: { constructors } } }, onSelectConstructor } = props;
const { contractAbi: { constructors }, onSelectConstructor } = props;
if (!constructors || !constructors[index]) {
return;
@ -50,64 +51,75 @@ function onSelectConstructor (props: Props, index: number): () => void {
};
}
function renderItem (props: Props, message: ContractABIMessage, index: number, asConstructor: boolean, t: <T = string> (key: string) => T): React.ReactNode {
const { docs = [], name } = message;
function filterDocs (docs: string[]): string[] {
let skip = false;
return docs
.map((line) => line.trim())
.filter((line) => line)
.filter((line, index): boolean => {
if (skip) {
return false;
} else if (index || line.startsWith('#')) {
skip = true;
return false;
}
return true;
});
}
function renderItem (props: Props, message: AbiConstructor | AbiMessage, index: number, asConstructor: boolean, t: <T = string> (key: string) => T): React.ReactNode {
const { docs = [], identifier } = message;
return (
<div
className={classes('message', !onSelect && 'exempt-hover', asConstructor && 'constructor')}
key={name}
key={identifier}
>
{!asConstructor && props.onSelect && (
<div className='accessory'>
<Button
className='execute'
icon='play'
label={message.isMutating ? t<string>('exec') : t<string>('read')}
onClick={onSelect(props, index)}
/>
</div>
)}
{asConstructor && props.onSelectConstructor && (
<Button
className='accessory'
icon='upload'
label={t<string>('deploy')}
onClick={onSelectConstructor(props, index)}
/>
)}
<div className='info'>
<MessageSignature
asConstructor={asConstructor}
message={message}
withTooltip
/>
<Expander
className='docs'
summary={
docs && docs.length > 0
? docs
.filter((line) => line !== '')
.map((line, index) => ((
<React.Fragment key={`${name}-docs-${index}`}>
<span>{line}</span>
<br />
</React.Fragment>
)))
: (
<i>{t<string>('No documentation provided')}</i>
)
<div className='docs'>
{docs && docs.length > 0
? filterDocs(docs).map((line, index) => ((
<React.Fragment key={`${identifier}-docs-${index}`}>
<span>{line}</span>
<br />
</React.Fragment>
)))
: <i>&nbsp;{t<string>('No documentation provided')}&nbsp;</i>
}
/>
</div>
</div>
{!asConstructor && props.onSelect && (
<div className='accessory'>
<Button
className='execute'
icon='play'
onClick={onSelect(props, index)}
tooltip={t<string>('Call this message')}
/>
</div>
)}
{asConstructor && props.onSelectConstructor && (
<div className='accessory'>
<Button
className='execute'
icon='upload'
onClick={onSelectConstructor(props, index)}
tooltip={t<string>('Deploy with this constructor')}
/>
</div>
)}
</div>
);
}
function renderConstructor (props: Props, index: number, t: <T = string> (key: string) => T): React.ReactNode {
const { contractAbi: { abi: { contract: { constructors } } } } = props;
const { contractAbi: { constructors } } = props;
if (!constructors[index]) {
return null;
@ -117,7 +129,7 @@ function renderConstructor (props: Props, index: number, t: <T = string> (key: s
}
function renderMessage (props: Props, index: number, t: <T = string> (key: string) => T): React.ReactNode {
const { contractAbi: { abi: { contract: { messages } } } } = props;
const { contractAbi: { messages } } = props;
if (!messages[index]) {
return null;
@ -128,12 +140,20 @@ function renderMessage (props: Props, index: number, t: <T = string> (key: strin
function Messages (props: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { className = '', contractAbi: { abi: { contract: { constructors, messages } } }, isLabelled, isRemovable, onRemove = NOOP, withConstructors } = props;
const { className = '', contractAbi: { constructors, messages }, isLabelled, isRemovable, onRemove = NOOP, withConstructors, withMessages } = props;
return (
<div className={classes(className, 'ui--Messages', isLabelled && 'labelled')}>
{withConstructors && constructors.map((_, index): React.ReactNode => renderConstructor(props, index, t))}
{messages.map((_, index): React.ReactNode => renderMessage(props, index, t))}
<div className={`ui--Messages ${className} ${isLabelled ? 'labelled' : ''}`}>
{withConstructors && (
<Expander summary={t<string>('Constructors ({{count}})', { replace: { count: constructors.length } })}>
{constructors.map((_, index) => renderConstructor(props, index, t))}
</Expander>
)}
{withMessages && (
<Expander summary={t<string>('Messages ({{count}})', { replace: { count: constructors.length } })}>
{messages.map((_, index) => renderMessage(props, index, t))}
</Expander>
)}
{isRemovable && (
<IconLink
className='remove-abi'
@ -147,12 +167,12 @@ function Messages (props: Props): React.ReactElement<Props> {
}
export default React.memo(styled(Messages)`
font-size: 0.9rem;
padding: 0;
margin: 0;
.remove-abi {
float: right;
margin-top: 0.5rem;
&:hover, &:hover :not(i) {
text-decoration: underline;
@ -168,18 +188,21 @@ export default React.memo(styled(Messages)`
width: 100%;
}
& .message {
width: calc(100% - 1rem);
background: #f8f8f8;
display: inline-flex;
margin-bottom: 0.5rem;
padding: 0.5rem;
border-radius: 0.7rem;
transition: all 0.2s;
.message+.message {
margin-top: 0.5rem;
}
&.constructor {
background: #e8f4ff;
}
& .message {
align-items: center;
// background: #f8f8f8;
border-radius: 0.25rem;
// color: #4e4e4e;
display: flex;
padding: 0.25rem 0.75rem 0.25rem 0;
// &.constructor {
// background: #e8f4ff;
// }
&.disabled {
opacity: 1 !important;
@ -187,34 +210,10 @@ export default React.memo(styled(Messages)`
color: #555 !important;
}
.accessory {
width: 3rem;
display: flex;
align-items: center;
justify-content: center;
.execute {
display: none;
background: transparent !important;
font-size: 1.5rem;
margin: 0;
padding: 0;
}
}
&:hover {
.accessory .execute {
display: block;
color: rgba(0, 0, 0, 0.2);
&:hover {
color: #2e86ab;
}
}
}
.info {
flex: 1 1;
font-size: 0.9rem;
margin-left: 1.5rem;
.docs {
font-size: 0.8rem;

View File

@ -1,7 +1,6 @@
// Copyright 2017-2020 @polkadot/app-contracts authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { ContractABIFnArg } from '@polkadot/api-contract/types';
import { TypeDef } from '@polkadot/types/types';
import { RawParams } from '@polkadot/react-params/types';
@ -10,7 +9,7 @@ import UIParams from '@polkadot/react-params';
interface Props {
isDisabled?: boolean;
params?: ContractABIFnArg[];
params?: ParamDef[];
onChange: (values: any[]) => void;
onEnter?: () => void;
}

View File

@ -1,7 +1,6 @@
// Copyright 2017-2020 @polkadot/app-contracts authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { ContractABIPre } from '@polkadot/api-contract/types';
import { Hash } from '@polkadot/types/interfaces';
import { CodeJson, CodeStored } from './types';
@ -31,25 +30,21 @@ class Store extends EventEmitter {
// eslint-disable-next-line @typescript-eslint/require-await
public async saveCode (codeHash: string | Hash, partial: Partial<CodeJson>): Promise<void> {
const hex = (typeof codeHash === 'string' ? createType(registry, 'Hash', codeHash) : codeHash).toHex();
const existing = this.getCode(hex);
const json = {
...(existing ? existing.json : {}),
...partial,
codeHash: hex,
genesisHash: api.genesisHash.toHex()
};
const key = `${KEY_CODE}${json.codeHash}`;
store.set(`${KEY_CODE}${json.codeHash}`, json);
this.addCode(json as CodeJson);
store.set(key, json);
this.addCode(key, json as CodeJson);
}
public forgetCode (codeHash: string): void {
store.remove(`${KEY_CODE}${codeHash}`);
this.removeCode(codeHash);
this.removeCode(`${KEY_CODE}${codeHash}`, codeHash);
}
public async loadAll (): Promise<void> {
@ -64,7 +59,7 @@ class Store extends EventEmitter {
}
if (key.startsWith(KEY_CODE)) {
this.addCode(json);
this.addCode(key, json);
}
});
} catch (error) {
@ -72,10 +67,10 @@ class Store extends EventEmitter {
}
}
private addCode (json: CodeJson): void {
private addCode (key: string, json: CodeJson): void {
try {
const abi = json.abi
? JSON.parse(json.abi) as ContractABIPre
? JSON.parse(json.abi) as Record<string, any>
: null;
this.allCode[json.codeHash] = {
@ -88,12 +83,14 @@ class Store extends EventEmitter {
this.emit('new-code');
} catch (error) {
console.error(error);
this.removeCode(key, json.codeHash);
}
}
private removeCode (codeHash: string): void {
private removeCode (key: string, codeHash: string): void {
try {
delete this.allCode[codeHash];
store.remove(key);
this.emit('removed-code');
} catch (error) {
console.error(error);

View File

@ -3,15 +3,6 @@
import BN from 'bn.js';
import { Abi } from '@polkadot/api-contract';
import { AppProps } from '@polkadot/react-components/types';
export interface ComponentProps extends AppProps {
accounts: string[];
contracts: string[];
hasCode: boolean;
onShowDeploy: (codeHash?: string, constructorIndex?: number) => () => void;
updated: number;
}
export interface CodeJson {
abi?: string | null;

View File

@ -22,11 +22,6 @@ interface UseAbi {
onRemoveAbi: () => void;
}
interface ContractABIOutdated {
deploy?: any;
messages?: any;
}
export default function useAbi (initialValue: [string | null, Abi | null] = [null, null], codeHash: StringOrNull = null, isRequired = false): UseAbi {
const { t } = useTranslation();
const [[abi, contractAbi, isAbiSupplied, isAbiValid], setAbi] = useState<[StringOrNull, Abi | null, boolean, boolean]>([initialValue[0], initialValue[1], !!initialValue[1], !isRequired || !!initialValue[1]]);
@ -46,13 +41,14 @@ export default function useAbi (initialValue: [string | null, Abi | null] = [nul
const json = u8aToString(u8a);
try {
const abiOutdated = JSON.parse(json) as ContractABIOutdated;
const abi = JSON.parse(json) as Record<string, any>;
if (abiOutdated.deploy || abiOutdated.messages) {
if (!abi.metadataVersion) {
throw new Error(t('You are using an ABI with an outdated format. Please generate a new one.'));
}
setAbi([json, new Abi(registry, JSON.parse(json)), true, true]);
setAbi([json, new Abi(registry, abi), true, true]);
codeHash && store.saveCode(
codeHash,
{ abi: json }

View File

@ -31,8 +31,8 @@
"homepage": "https://github.com/polkadot-js/ui/tree/master/packages/ui-reactive#readme",
"dependencies": {
"@babel/runtime": "^7.11.2",
"@polkadot/api": "^2.1.1",
"@polkadot/extension-dapp": "^0.35.0-beta.8",
"@polkadot/api": "^2.1.2-13",
"@polkadot/extension-dapp": "^0.35.0-beta.10",
"rxjs-compat": "^6.6.3"
}
}

View File

@ -16,11 +16,11 @@
"@fortawesome/react-fontawesome": "^0.1.11",
"@polkadot/keyring": "^3.5.1",
"@polkadot/react-api": "0.61.2-8",
"@polkadot/react-identicon": "^0.60.0-beta.0",
"@polkadot/react-qr": "^0.60.0-beta.0",
"@polkadot/react-identicon": "^0.60.1",
"@polkadot/react-qr": "^0.60.1",
"@polkadot/react-query": "0.61.2-8",
"@polkadot/ui-keyring": "^0.60.0-beta.0",
"@polkadot/ui-settings": "^0.60.0-beta.0",
"@polkadot/ui-keyring": "^0.60.1",
"@polkadot/ui-settings": "^0.60.1",
"@polkadot/util": "^3.5.1",
"@polkadot/util-crypto": "^3.5.1",
"chart.js": "^2.9.3",

View File

@ -24,7 +24,7 @@ export interface Props extends RowProps {
}
const DEFAULT_ADDR = '5'.padEnd(48, 'x');
const ICON_SIZE = 48;
const ICON_SIZE = 32;
function AddressRow ({ buttons, children, className, defaultName, isContract = false, isDisabled, isInline, isValid: propsIsValid, overlay, value, withTags = false }: Props): React.ReactElement<Props> | null {
const { accountIndex, isNull, name, onSaveName, onSaveTags, setName, setTags, tags } = useAccountInfo(value ? value.toString() : null, isContract);

View File

@ -1,6 +1,8 @@
// Copyright 2017-2020 @polkadot/react-components authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { ThemeProps } from './types';
import React, { useCallback, useState, createRef } from 'react';
import Dropzone, { DropzoneRef } from 'react-dropzone';
import styled from 'styled-components';
@ -129,8 +131,8 @@ function InputFile ({ accept, className = '', clearContent, help, isDisabled, is
: dropZone;
}
export default React.memo(styled(InputFile)`
background: #fff;
export default React.memo(styled(InputFile)(({ theme }: ThemeProps) => `
background: ${theme.bgInput};
border: 1px solid rgba(34, 36, 38, 0.15);
border-radius: 0.28571429rem;
font-size: 1rem;
@ -139,16 +141,11 @@ export default React.memo(styled(InputFile)`
width: 100% !important;
&.error {
background: #fff6f6;
background: ${theme.bgInputError};
border-color: #e0b4b4;
}
&:hover {
background: #fefefe;
cursor: pointer;
}
.label {
color: rgba(0, 0, 0, .6);
}
`);
`));

View File

@ -19,7 +19,7 @@ export default css`
word-break: break-all;
&.error {
background: #fff6f6;
background: ${({ theme }: ThemeProps) => theme.bgInputError};
border-color: #e0b4b4;
}

View File

@ -105,8 +105,8 @@ export default css`
}
}
&.disabled.error input {
background-color: #fff6f6;
&.error input {
background-color: ${({ theme }: ThemeProps) => theme.bgInputError};
border-color: #e0b4b4;
}

View File

@ -127,6 +127,7 @@ export interface NullContract {
export interface ThemeDef {
bgInput: string;
bgInputError: string;
bgInverse: string;
bgMenu: string;
bgMenuHover: string;

View File

@ -1,7 +1,7 @@
// Copyright 2017-2020 @polkadot/react-components authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { ContractABIPre } from '@polkadot/api-contract/types';
import { AnyJson } from '@polkadot/types/types';
import { Abi } from '@polkadot/api-contract';
import { registry } from '@polkadot/react-api';
@ -17,9 +17,9 @@ export default function getContractAbi (address: string | null): Abi | null {
const meta = getAddressMeta(address, 'contract');
try {
const data = meta.contract && JSON.parse(meta.contract.abi) as ContractABIPre;
const data = meta.contract && JSON.parse(meta.contract.abi) as AnyJson;
abi = new Abi(registry, data as ContractABIPre);
abi = new Abi(registry, data);
} catch (error) {
// invalid address, maybe
}

View File

@ -5,7 +5,7 @@ import { Props, Props as CProps } from '../types';
import React, { useRef } from 'react';
import { classes } from '@polkadot/react-components/util';
import { displayType } from '@polkadot/types';
import { encodeTypeDef } from '@polkadot/types';
import { isUndefined } from '@polkadot/util';
import findComponent from './findComponent';
@ -19,8 +19,8 @@ function Param ({ className = '', defaultValue, isDisabled, isInOption, isOption
}
const label = isUndefined(name)
? displayType(type)
: `${name}: ${displayType(type)}`;
? encodeTypeDef(type)
: `${name}: ${encodeTypeDef(type)}`;
return isOptional
? (

4263
yarn.lock

File diff suppressed because it is too large Load Diff