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:
parent
49e8f9c129
commit
ae9ccf3450
@ -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",
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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": "",
|
||||
|
||||
@ -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)',
|
||||
|
||||
@ -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"
|
||||
},
|
||||
|
||||
@ -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"
|
||||
},
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -46,7 +46,6 @@ function Outcome ({ className = '', onClear, outcome: { isSuccess, message, orig
|
||||
className='output'
|
||||
isError={!isSuccess}
|
||||
value={(output || '()').toString()}
|
||||
withCopy
|
||||
withLabel={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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} />
|
||||
),
|
||||
|
||||
@ -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>
|
||||
|
||||
48
packages/page-contracts/src/Ink3Banner.tsx
Normal file
48
packages/page-contracts/src/Ink3Banner.tsx
Normal 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;
|
||||
}
|
||||
}
|
||||
`);
|
||||
@ -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;
|
||||
}
|
||||
`);
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
`
|
||||
);
|
||||
}
|
||||
`));
|
||||
|
||||
@ -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;
|
||||
}
|
||||
`);
|
||||
|
||||
@ -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> {t<string>('No documentation provided')} </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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 }
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
`);
|
||||
`));
|
||||
|
||||
@ -19,7 +19,7 @@ export default css`
|
||||
word-break: break-all;
|
||||
|
||||
&.error {
|
||||
background: #fff6f6;
|
||||
background: ${({ theme }: ThemeProps) => theme.bgInputError};
|
||||
border-color: #e0b4b4;
|
||||
}
|
||||
|
||||
|
||||
@ -105,8 +105,8 @@ export default css`
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled.error input {
|
||||
background-color: #fff6f6;
|
||||
&.error input {
|
||||
background-color: ${({ theme }: ThemeProps) => theme.bgInputError};
|
||||
border-color: #e0b4b4;
|
||||
}
|
||||
|
||||
|
||||
@ -127,6 +127,7 @@ export interface NullContract {
|
||||
|
||||
export interface ThemeDef {
|
||||
bgInput: string;
|
||||
bgInputError: string;
|
||||
bgInverse: string;
|
||||
bgMenu: string;
|
||||
bgMenuHover: string;
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
? (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user