Commit c159bf91 authored by tom's avatar tom

contract read fixes

parent c9f557b8
......@@ -47,3 +47,27 @@ export interface SmartContractMethodInput {
export interface SmartContractMethodOutput extends SmartContractMethodInput {
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 React from 'react';
import { useAccount, useDisconnect } from 'wagmi';
......@@ -6,7 +6,7 @@ import { useAccount, useDisconnect } from 'wagmi';
import AddressIcon from 'ui/shared/address/AddressIcon';
const ContractConnectWallet = () => {
const { isOpen, open } = useWeb3Modal();
const { open } = useWeb3Modal();
const { address, isDisconnected } = useAccount();
const { disconnect } = useDisconnect();
......@@ -19,15 +19,11 @@ const ContractConnectWallet = () => {
}, [ disconnect ]);
const content = (() => {
if (isOpen) {
return <span>connecting...</span>;
}
if (isDisconnected || !address) {
return (
<>
<span>Disconnected.</span>
<Link ml={ 1 } onClick={ handleConnect }>Connect wallet</Link>
<span>Disconnected</span>
<Button ml={ 3 } onClick={ handleConnect } size="sm" variant="outline">Connect wallet</Button>
</>
);
}
......@@ -37,7 +33,7 @@ const ContractConnectWallet = () => {
<span>Connected to </span>
<AddressIcon address={{ hash: address, is_contract: false }} mx={ 2 }/>
<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 React from 'react';
import type { SubmitHandler } 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 appConfig from 'configs/app/config';
......@@ -14,7 +14,8 @@ import ContractMethodField from './ContractMethodField';
interface Props<T extends SmartContractMethod> {
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;
}
......@@ -36,7 +37,10 @@ const sortFields = (data: Array<SmartContractMethodInput>) => ([ a ]: [string, s
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(() => {
return data.payable && (!('inputs' in data) || data.inputs.length === 0) ? [ {
......@@ -49,19 +53,24 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, caller, i
const { control, handleSubmit, setValue } = useForm<MethodFormFields>({
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)
.sort(sortFields(inputs))
.map(([ , value ]) => value);
const result = await caller(data, args);
result && setResult(result);
}, [ caller, data, inputs ]);
const resultBgColor = useColorModeValue('blackAlpha.50', 'whiteAlpha.50');
setResult(undefined);
setLoading(true);
onSubmit(data, args)
.then((result) => {
setResult(result);
setLoading(false);
})
.catch((error) => {
setResult(error);
setLoading(false);
});
}, [ onSubmit, data, inputs ]);
return (
<Box>
......@@ -72,7 +81,7 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, caller, i
flexDir={{ base: 'column', lg: 'row' }}
rowGap={ 2 }
alignItems={{ base: 'flex-start', lg: 'center' }}
onSubmit={ handleSubmit(onSubmit) }
onSubmit={ handleSubmit(onFormSubmit) }
flexWrap="wrap"
>
{ inputs.map(({ type, name }, index) => {
......@@ -84,10 +93,13 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, caller, i
placeholder={ `${ name }(${ type })` }
control={ control }
setValue={ setValue }
isDisabled={ isLoading }
/>
);
}) }
<Button
isLoading={ isLoading }
loadingText={ isWrite ? 'Write' : 'Query' }
variant="outline"
size="sm"
flexShrink={ 0 }
......@@ -102,18 +114,7 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, caller, i
<Text>{ data.outputs.map(({ type }) => type).join(', ') }</Text>
</Flex>
) }
{ result.length > 0 && (
<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>
) }
{ result && renderResult(data, result) }
</Box>
);
};
......
......@@ -12,9 +12,10 @@ interface Props {
setValue: UseFormSetValue<MethodFormFields>;
placeholder: 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 handleClear = React.useCallback(() => {
......@@ -24,7 +25,7 @@ const ContractMethodField = ({ control, name, placeholder, setValue }: Props) =>
const renderInput = React.useCallback(({ field }: { field: ControllerRenderProps<MethodFormFields> }) => {
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">
<Input
{ ...field }
......@@ -39,7 +40,7 @@ const ContractMethodField = ({ control, name, placeholder, setValue }: Props) =>
</InputGroup>
</FormControl>
);
}, [ handleClear, name, placeholder ]);
}, [ handleClear, isDisabled, name, placeholder ]);
return (
<Controller
......
......@@ -39,7 +39,7 @@ const ContractMethodsAccordion = <T extends SmartContractMethod>({ data, renderC
<Accordion allowMultiple position="relative" onChange={ handleAccordionStateChange } index={ expandedSections }>
{ data.map((item, index) => {
return (
<AccordionItem key={ index } as="section">
<AccordionItem key={ index } as="section" _first={{ borderTopWidth: '0' }}>
<h2>
<AccordionButton px={ 0 } py={ 3 } _hover={{ bgColor: 'inherit' }}>
<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 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 useApiQuery from 'lib/api/useApiQuery';
......@@ -10,7 +12,7 @@ import ContractMethodsAccordion from 'ui/address/contract/ContractMethodsAccordi
import ContentLoader from 'ui/shared/ContentLoader';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import { useContractContext } from './context';
import ContractConnectWallet from './ContractConnectWallet';
import ContractMethodCallable from './ContractMethodCallable';
import ContractMethodConstant from './ContractMethodConstant';
......@@ -21,6 +23,7 @@ interface Props {
const ContractRead = ({ isProxy }: Props) => {
const router = useRouter();
const apiFetch = useApiFetch();
const { address: userAddress } = useAccount();
const addressHash = router.query.id?.toString();
......@@ -31,10 +34,8 @@ const ContractRead = ({ isProxy }: Props) => {
},
});
const contract = useContractContext();
const contractCaller = React.useCallback(async(item: SmartContractReadMethod, args: Array<string>) => {
await apiFetch('contract_method_query', {
const handleMethodFormSubmit = React.useCallback(async(item: SmartContractReadMethod, args: Array<string>) => {
return apiFetch<'contract_method_query', SmartContractQueryMethodRead>('contract_method_query', {
pathParams: { id: addressHash },
fetchParams: {
method: 'POST',
......@@ -42,20 +43,40 @@ const ContractRead = ({ isProxy }: Props) => {
args,
method_id: item.method_id,
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
console.log('__>__', { result });
if (result.is_error) {
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' ] ];
}, [ addressHash, apiFetch, contract, isProxy ]);
return (
<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) => {
if (item.inputs.length === 0) {
if (item.outputs.some(({ value }) => value)) {
return (
<Flex flexDir="column" rowGap={ 1 }>
{ item.outputs.map((output, index) => <ContractMethodConstant key={ index } data={ output }/>) }
......@@ -67,10 +88,11 @@ const ContractRead = ({ isProxy }: Props) => {
<ContractMethodCallable
key={ id + '_' + index }
data={ item }
caller={ contractCaller }
onSubmit={ handleMethodFormSubmit }
renderResult={ renderResult }
/>
);
}, [ contractCaller ]);
}, [ handleMethodFormSubmit, renderResult ]);
if (isError) {
return <DataFetchAlert/>;
......@@ -80,7 +102,12 @@ const ContractRead = ({ isProxy }: Props) => {
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) => {
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) {
return;
}
......@@ -50,16 +50,21 @@ const ContractWrite = ({ isProxy }: Props) => {
return [ [ 'string', 'this is mock' ] ];
}, [ contract ]);
const renderResult = React.useCallback(() => {
return <span>result</span>;
}, []);
const renderContent = React.useCallback((item: SmartContractWriteMethod, index: number, id: number) => {
return (
<ContractMethodCallable
key={ id + '_' + index }
data={ item }
caller={ contractCaller }
onSubmit={ handleMethodFormSubmit }
renderResult={ renderResult }
isWrite
/>
);
}, [ contractCaller ]);
}, [ handleMethodFormSubmit, renderResult ]);
if (isError) {
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 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 React from 'react';
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_night';
import 'ace-builds/src-noconflict/ext-language_tools';
......@@ -19,7 +19,7 @@ const CodeEditorBase = chakra(({ id, value, className }: Props) => {
return (
<AceEditor
className={ className }
mode="javascript" // TODO need to find mode for solidity
mode="csharp" // TODO need to find mode for solidity
theme={ theme }
value={ value }
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