Commit c159bf91 authored by tom's avatar tom

contract read fixes

parent c9f557b8
...@@ -47,3 +47,27 @@ export interface SmartContractMethodInput { ...@@ -47,3 +47,27 @@ export interface SmartContractMethodInput {
export interface SmartContractMethodOutput extends SmartContractMethodInput { export interface SmartContractMethodOutput extends SmartContractMethodInput {
value?: string; value?: string;
} }
export interface SmartContractQueryMethodReadSuccess {
is_error: false;
result: {
names: Array<string>;
output: Array<{
type: string;
value: string;
}>;
};
}
export interface SmartContractQueryMethodReadError {
is_error: true;
result: {
code: number;
message: string;
raw?: string;
} | {
error: string;
};
}
export type SmartContractQueryMethodRead = SmartContractQueryMethodReadSuccess | SmartContractQueryMethodReadError;
import { Alert, chakra, Link } from '@chakra-ui/react'; import { Alert, Button, chakra } from '@chakra-ui/react';
import { useWeb3Modal } from '@web3modal/react'; import { useWeb3Modal } from '@web3modal/react';
import React from 'react'; import React from 'react';
import { useAccount, useDisconnect } from 'wagmi'; import { useAccount, useDisconnect } from 'wagmi';
...@@ -6,7 +6,7 @@ import { useAccount, useDisconnect } from 'wagmi'; ...@@ -6,7 +6,7 @@ import { useAccount, useDisconnect } from 'wagmi';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
const ContractConnectWallet = () => { const ContractConnectWallet = () => {
const { isOpen, open } = useWeb3Modal(); const { open } = useWeb3Modal();
const { address, isDisconnected } = useAccount(); const { address, isDisconnected } = useAccount();
const { disconnect } = useDisconnect(); const { disconnect } = useDisconnect();
...@@ -19,15 +19,11 @@ const ContractConnectWallet = () => { ...@@ -19,15 +19,11 @@ const ContractConnectWallet = () => {
}, [ disconnect ]); }, [ disconnect ]);
const content = (() => { const content = (() => {
if (isOpen) {
return <span>connecting...</span>;
}
if (isDisconnected || !address) { if (isDisconnected || !address) {
return ( return (
<> <>
<span>Disconnected.</span> <span>Disconnected</span>
<Link ml={ 1 } onClick={ handleConnect }>Connect wallet</Link> <Button ml={ 3 } onClick={ handleConnect } size="sm" variant="outline">Connect wallet</Button>
</> </>
); );
} }
...@@ -37,7 +33,7 @@ const ContractConnectWallet = () => { ...@@ -37,7 +33,7 @@ const ContractConnectWallet = () => {
<span>Connected to </span> <span>Connected to </span>
<AddressIcon address={{ hash: address, is_contract: false }} mx={ 2 }/> <AddressIcon address={{ hash: address, is_contract: false }} mx={ 2 }/>
<chakra.span fontWeight={ 600 }>{ address }</chakra.span> <chakra.span fontWeight={ 600 }>{ address }</chakra.span>
<Link ml={ 2 } onClick={ handleDisconnect }>Disconnect</Link> <Button ml={ 3 } onClick={ handleDisconnect } size="sm" variant="outline">Disconnect</Button>
</> </>
); );
})(); })();
......
import { Box, Button, chakra, Flex, Icon, Text, useColorModeValue } from '@chakra-ui/react'; import { Box, Button, chakra, Flex, Icon, Text } from '@chakra-ui/react';
import _fromPairs from 'lodash/fromPairs'; import _fromPairs from 'lodash/fromPairs';
import React from 'react'; import React from 'react';
import type { SubmitHandler } from 'react-hook-form'; import type { SubmitHandler } from 'react-hook-form';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import type { MethodFormFields } from './types'; import type { MethodFormFields, ContractMethodCallResult } from './types';
import type { SmartContractMethodInput, SmartContractMethod } from 'types/api/contract'; import type { SmartContractMethodInput, SmartContractMethod } from 'types/api/contract';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
...@@ -14,7 +14,8 @@ import ContractMethodField from './ContractMethodField'; ...@@ -14,7 +14,8 @@ import ContractMethodField from './ContractMethodField';
interface Props<T extends SmartContractMethod> { interface Props<T extends SmartContractMethod> {
data: T; data: T;
caller: (data: T, args: Array<string>) => Promise<Array<Array<string>> | undefined>; onSubmit: (data: T, args: Array<string>) => Promise<ContractMethodCallResult<T>>;
renderResult: (data: T, result: ContractMethodCallResult<T>) => React.ReactNode;
isWrite?: boolean; isWrite?: boolean;
} }
...@@ -36,7 +37,10 @@ const sortFields = (data: Array<SmartContractMethodInput>) => ([ a ]: [string, s ...@@ -36,7 +37,10 @@ const sortFields = (data: Array<SmartContractMethodInput>) => ([ a ]: [string, s
return 0; return 0;
}; };
const ContractMethodCallable = <T extends SmartContractMethod>({ data, caller, isWrite }: Props<T>) => { const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit, renderResult, isWrite }: Props<T>) => {
const [ result, setResult ] = React.useState<ContractMethodCallResult<T>>();
const [ isLoading, setLoading ] = React.useState(false);
const inputs = React.useMemo(() => { const inputs = React.useMemo(() => {
return data.payable && (!('inputs' in data) || data.inputs.length === 0) ? [ { return data.payable && (!('inputs' in data) || data.inputs.length === 0) ? [ {
...@@ -49,19 +53,24 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, caller, i ...@@ -49,19 +53,24 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, caller, i
const { control, handleSubmit, setValue } = useForm<MethodFormFields>({ const { control, handleSubmit, setValue } = useForm<MethodFormFields>({
defaultValues: _fromPairs(inputs.map(({ name }, index) => [ getFieldName(name, index), '' ])), defaultValues: _fromPairs(inputs.map(({ name }, index) => [ getFieldName(name, index), '' ])),
}); });
const [ result, setResult ] = React.useState<Array<Array<string>>>([ ]);
const onSubmit: SubmitHandler<MethodFormFields> = React.useCallback(async(formData) => { const onFormSubmit: SubmitHandler<MethodFormFields> = React.useCallback(async(formData) => {
const args = Object.entries(formData) const args = Object.entries(formData)
.sort(sortFields(inputs)) .sort(sortFields(inputs))
.map(([ , value ]) => value); .map(([ , value ]) => value);
setResult(undefined);
const result = await caller(data, args); setLoading(true);
result && setResult(result);
onSubmit(data, args)
}, [ caller, data, inputs ]); .then((result) => {
setResult(result);
const resultBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50'); setLoading(false);
})
.catch((error) => {
setResult(error);
setLoading(false);
});
}, [ onSubmit, data, inputs ]);
return ( return (
<Box> <Box>
...@@ -72,7 +81,7 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, caller, i ...@@ -72,7 +81,7 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, caller, i
flexDir={{ base: 'column', lg: 'row' }} flexDir={{ base: 'column', lg: 'row' }}
rowGap={ 2 } rowGap={ 2 }
alignItems={{ base: 'flex-start', lg: 'center' }} alignItems={{ base: 'flex-start', lg: 'center' }}
onSubmit={ handleSubmit(onSubmit) } onSubmit={ handleSubmit(onFormSubmit) }
flexWrap="wrap" flexWrap="wrap"
> >
{ inputs.map(({ type, name }, index) => { { inputs.map(({ type, name }, index) => {
...@@ -84,10 +93,13 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, caller, i ...@@ -84,10 +93,13 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, caller, i
placeholder={ `${ name }(${ type })` } placeholder={ `${ name }(${ type })` }
control={ control } control={ control }
setValue={ setValue } setValue={ setValue }
isDisabled={ isLoading }
/> />
); );
}) } }) }
<Button <Button
isLoading={ isLoading }
loadingText={ isWrite ? 'Write' : 'Query' }
variant="outline" variant="outline"
size="sm" size="sm"
flexShrink={ 0 } flexShrink={ 0 }
...@@ -102,18 +114,7 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, caller, i ...@@ -102,18 +114,7 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, caller, i
<Text>{ data.outputs.map(({ type }) => type).join(', ') }</Text> <Text>{ data.outputs.map(({ type }) => type).join(', ') }</Text>
</Flex> </Flex>
) } ) }
{ result.length > 0 && ( { result && renderResult(data, result) }
<Box mt={ 3 } p={ 4 } borderRadius="md" bgColor={ resultBgColor } fontSize="sm">
<p>
[ <chakra.span fontWeight={ 600 }>{ 'name' in data ? data.name : '' }</chakra.span> method response ]
</p>
<p>[</p>
{ result.map(([ key, value ], index) => (
<chakra.p key={ index } whiteSpace="break-spaces" wordBreak="break-all"> { key }: { value }</chakra.p>
)) }
<p>]</p>
</Box>
) }
</Box> </Box>
); );
}; };
......
...@@ -12,9 +12,10 @@ interface Props { ...@@ -12,9 +12,10 @@ interface Props {
setValue: UseFormSetValue<MethodFormFields>; setValue: UseFormSetValue<MethodFormFields>;
placeholder: string; placeholder: string;
name: string; name: string;
isDisabled: boolean;
} }
const ContractMethodField = ({ control, name, placeholder, setValue }: Props) => { const ContractMethodField = ({ control, name, placeholder, setValue, isDisabled }: Props) => {
const ref = React.useRef<HTMLInputElement>(null); const ref = React.useRef<HTMLInputElement>(null);
const handleClear = React.useCallback(() => { const handleClear = React.useCallback(() => {
...@@ -24,7 +25,7 @@ const ContractMethodField = ({ control, name, placeholder, setValue }: Props) => ...@@ -24,7 +25,7 @@ const ContractMethodField = ({ control, name, placeholder, setValue }: Props) =>
const renderInput = React.useCallback(({ field }: { field: ControllerRenderProps<MethodFormFields> }) => { const renderInput = React.useCallback(({ field }: { field: ControllerRenderProps<MethodFormFields> }) => {
return ( return (
<FormControl id={ name } maxW={{ base: '100%', lg: 'calc((100% - 24px) / 3)' }}> <FormControl id={ name } maxW={{ base: '100%', lg: 'calc((100% - 24px) / 3)' }} isDisabled={ isDisabled }>
<InputGroup size="xs"> <InputGroup size="xs">
<Input <Input
{ ...field } { ...field }
...@@ -39,7 +40,7 @@ const ContractMethodField = ({ control, name, placeholder, setValue }: Props) => ...@@ -39,7 +40,7 @@ const ContractMethodField = ({ control, name, placeholder, setValue }: Props) =>
</InputGroup> </InputGroup>
</FormControl> </FormControl>
); );
}, [ handleClear, name, placeholder ]); }, [ handleClear, isDisabled, name, placeholder ]);
return ( return (
<Controller <Controller
......
...@@ -39,7 +39,7 @@ const ContractMethodsAccordion = <T extends SmartContractMethod>({ data, renderC ...@@ -39,7 +39,7 @@ const ContractMethodsAccordion = <T extends SmartContractMethod>({ data, renderC
<Accordion allowMultiple position="relative" onChange={ handleAccordionStateChange } index={ expandedSections }> <Accordion allowMultiple position="relative" onChange={ handleAccordionStateChange } index={ expandedSections }>
{ data.map((item, index) => { { data.map((item, index) => {
return ( return (
<AccordionItem key={ index } as="section"> <AccordionItem key={ index } as="section" _first={{ borderTopWidth: '0' }}>
<h2> <h2>
<AccordionButton px={ 0 } py={ 3 } _hover={{ bgColor: 'inherit' }}> <AccordionButton px={ 0 } py={ 3 } _hover={{ bgColor: 'inherit' }}>
<Box as="span" fontFamily="heading" fontWeight={ 500 } fontSize="lg" mr={ 1 }> <Box as="span" fontFamily="heading" fontWeight={ 500 } fontSize="lg" mr={ 1 }>
......
import { Flex } from '@chakra-ui/react'; import { Alert, Box, chakra, Flex, useColorModeValue } from '@chakra-ui/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import { useAccount } from 'wagmi';
import type { SmartContractReadMethod } from 'types/api/contract'; import type { ContractMethodReadResult } from './types';
import type { SmartContractReadMethod, SmartContractQueryMethodRead } from 'types/api/contract';
import useApiFetch from 'lib/api/useApiFetch'; import useApiFetch from 'lib/api/useApiFetch';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
...@@ -10,7 +12,7 @@ import ContractMethodsAccordion from 'ui/address/contract/ContractMethodsAccordi ...@@ -10,7 +12,7 @@ import ContractMethodsAccordion from 'ui/address/contract/ContractMethodsAccordi
import ContentLoader from 'ui/shared/ContentLoader'; import ContentLoader from 'ui/shared/ContentLoader';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
import { useContractContext } from './context'; import ContractConnectWallet from './ContractConnectWallet';
import ContractMethodCallable from './ContractMethodCallable'; import ContractMethodCallable from './ContractMethodCallable';
import ContractMethodConstant from './ContractMethodConstant'; import ContractMethodConstant from './ContractMethodConstant';
...@@ -21,6 +23,7 @@ interface Props { ...@@ -21,6 +23,7 @@ interface Props {
const ContractRead = ({ isProxy }: Props) => { const ContractRead = ({ isProxy }: Props) => {
const router = useRouter(); const router = useRouter();
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
const { address: userAddress } = useAccount();
const addressHash = router.query.id?.toString(); const addressHash = router.query.id?.toString();
...@@ -31,10 +34,8 @@ const ContractRead = ({ isProxy }: Props) => { ...@@ -31,10 +34,8 @@ const ContractRead = ({ isProxy }: Props) => {
}, },
}); });
const contract = useContractContext(); const handleMethodFormSubmit = React.useCallback(async(item: SmartContractReadMethod, args: Array<string>) => {
return apiFetch<'contract_method_query', SmartContractQueryMethodRead>('contract_method_query', {
const contractCaller = React.useCallback(async(item: SmartContractReadMethod, args: Array<string>) => {
await apiFetch('contract_method_query', {
pathParams: { id: addressHash }, pathParams: { id: addressHash },
fetchParams: { fetchParams: {
method: 'POST', method: 'POST',
...@@ -42,20 +43,40 @@ const ContractRead = ({ isProxy }: Props) => { ...@@ -42,20 +43,40 @@ const ContractRead = ({ isProxy }: Props) => {
args, args,
method_id: item.method_id, method_id: item.method_id,
contract_type: isProxy ? 'proxy' : 'regular', contract_type: isProxy ? 'proxy' : 'regular',
from: userAddress,
}, },
}, },
}); });
}, [ addressHash, apiFetch, isProxy, userAddress ]);
const resultBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
const result = await contract?.functions[item.name](...args); const renderResult = React.useCallback((item: SmartContractReadMethod, result: ContractMethodReadResult) => {
if ('status' in result) {
return <Alert status="error" mt={ 3 } p={ 4 } borderRadius="md" fontSize="sm">{ result.statusText }</Alert>;
}
// eslint-disable-next-line no-console if (result.is_error) {
console.log('__>__', { result }); const message = 'error' in result.result ? result.result.error : result.result.message;
return <Alert status="error" mt={ 3 } p={ 4 } borderRadius="md" fontSize="sm">{ message }</Alert>;
}
return [ [ 'string', 'this is mock' ] ]; return (
}, [ addressHash, apiFetch, contract, isProxy ]); <Box mt={ 3 } p={ 4 } borderRadius="md" bgColor={ resultBgColor } fontSize="sm">
<p>
[ <chakra.span fontWeight={ 600 }>{ 'name' in item ? item.name : '' }</chakra.span> method response ]
</p>
<p>[</p>
{ result.result.output.map(({ type, value }, index) => (
<chakra.p key={ index } whiteSpace="break-spaces" wordBreak="break-all"> { type }: { String(value) }</chakra.p>
)) }
<p>]</p>
</Box>
);
}, [ resultBgColor ]);
const renderContent = React.useCallback((item: SmartContractReadMethod, index: number, id: number) => { const renderContent = React.useCallback((item: SmartContractReadMethod, index: number, id: number) => {
if (item.inputs.length === 0) { if (item.outputs.some(({ value }) => value)) {
return ( return (
<Flex flexDir="column" rowGap={ 1 }> <Flex flexDir="column" rowGap={ 1 }>
{ item.outputs.map((output, index) => <ContractMethodConstant key={ index } data={ output }/>) } { item.outputs.map((output, index) => <ContractMethodConstant key={ index } data={ output }/>) }
...@@ -67,10 +88,11 @@ const ContractRead = ({ isProxy }: Props) => { ...@@ -67,10 +88,11 @@ const ContractRead = ({ isProxy }: Props) => {
<ContractMethodCallable <ContractMethodCallable
key={ id + '_' + index } key={ id + '_' + index }
data={ item } data={ item }
caller={ contractCaller } onSubmit={ handleMethodFormSubmit }
renderResult={ renderResult }
/> />
); );
}, [ contractCaller ]); }, [ handleMethodFormSubmit, renderResult ]);
if (isError) { if (isError) {
return <DataFetchAlert/>; return <DataFetchAlert/>;
...@@ -80,7 +102,12 @@ const ContractRead = ({ isProxy }: Props) => { ...@@ -80,7 +102,12 @@ const ContractRead = ({ isProxy }: Props) => {
return <ContentLoader/>; return <ContentLoader/>;
} }
return <ContractMethodsAccordion data={ data } renderContent={ renderContent }/>; return (
<>
<ContractConnectWallet/>
<ContractMethodsAccordion data={ data } renderContent={ renderContent }/>
</>
);
}; };
export default ContractRead; export default React.memo(ContractRead);
...@@ -30,7 +30,7 @@ const ContractWrite = ({ isProxy }: Props) => { ...@@ -30,7 +30,7 @@ const ContractWrite = ({ isProxy }: Props) => {
const contract = useContractContext(); const contract = useContractContext();
const contractCaller = React.useCallback(async(item: SmartContractWriteMethod, args: Array<string>) => { const handleMethodFormSubmit = React.useCallback(async(item: SmartContractWriteMethod, args: Array<string>) => {
if (!contract) { if (!contract) {
return; return;
} }
...@@ -50,16 +50,21 @@ const ContractWrite = ({ isProxy }: Props) => { ...@@ -50,16 +50,21 @@ const ContractWrite = ({ isProxy }: Props) => {
return [ [ 'string', 'this is mock' ] ]; return [ [ 'string', 'this is mock' ] ];
}, [ contract ]); }, [ contract ]);
const renderResult = React.useCallback(() => {
return <span>result</span>;
}, []);
const renderContent = React.useCallback((item: SmartContractWriteMethod, index: number, id: number) => { const renderContent = React.useCallback((item: SmartContractWriteMethod, index: number, id: number) => {
return ( return (
<ContractMethodCallable <ContractMethodCallable
key={ id + '_' + index } key={ id + '_' + index }
data={ item } data={ item }
caller={ contractCaller } onSubmit={ handleMethodFormSubmit }
renderResult={ renderResult }
isWrite isWrite
/> />
); );
}, [ contractCaller ]); }, [ handleMethodFormSubmit, renderResult ]);
if (isError) { if (isError) {
return <DataFetchAlert/>; return <DataFetchAlert/>;
......
import type { SmartContractQueryMethodRead, SmartContractMethod } from 'types/api/contract';
import type { ResourceError } from 'lib/api/resources';
export type MethodFormFields = Record<string, string>; export type MethodFormFields = Record<string, string>;
export type ContractMethodReadResult = SmartContractQueryMethodRead | ResourceError;
export type ContractMethodWriteResult = unknown;
export type ContractMethodCallResult<T extends SmartContractMethod> =
T extends { method_id: string } ? ContractMethodReadResult : ContractMethodWriteResult;
import { chakra, useColorModeValue } from '@chakra-ui/react'; import { chakra, useColorModeValue } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import AceEditor from 'react-ace'; import AceEditor from 'react-ace';
import 'ace-builds/src-noconflict/mode-javascript'; import 'ace-builds/src-noconflict/mode-csharp';
import 'ace-builds/src-noconflict/theme-tomorrow'; import 'ace-builds/src-noconflict/theme-tomorrow';
import 'ace-builds/src-noconflict/theme-tomorrow_night'; import 'ace-builds/src-noconflict/theme-tomorrow_night';
import 'ace-builds/src-noconflict/ext-language_tools'; import 'ace-builds/src-noconflict/ext-language_tools';
...@@ -19,7 +19,7 @@ const CodeEditorBase = chakra(({ id, value, className }: Props) => { ...@@ -19,7 +19,7 @@ const CodeEditorBase = chakra(({ id, value, className }: Props) => {
return ( return (
<AceEditor <AceEditor
className={ className } className={ className }
mode="javascript" // TODO need to find mode for solidity mode="csharp" // TODO need to find mode for solidity
theme={ theme } theme={ theme }
value={ value } value={ value }
name={ id } name={ id }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment