diff --git a/src/hooks/useContractCaller.ts b/src/hooks/useContractCaller.ts index e1e59b1c..c0b0e0ae 100644 --- a/src/hooks/useContractCaller.ts +++ b/src/hooks/useContractCaller.ts @@ -1,22 +1,72 @@ -import { useMemo } from 'react' -import { useCall } from 'useink' -import { AbiMessage, ContractPromise } from '@/services/substrate/types' +import { useEffect, useMemo, useState } from 'react' +import { Call, useCall } from 'useink' +import { + AbiMessage, + ContractPromise, + Registry +} from '@/services/substrate/types' import { useNetworkApi } from './useNetworkApi' +import { getDecodedOutput, stringify } from '@/utils/contractExecResult' +import { decodeError } from '@/services/useink/utils/decodeError' -export function useContractCaller( - contract: ContractPromise, - method: AbiMessage['method'] -) { +interface Props { + contractPromise: ContractPromise + abiMessage: AbiMessage + substrateRegistry: Registry +} + +interface UseContractCallerReturn { + caller: Call + outcome: string + error: string | undefined +} + +export function useContractCaller({ + contractPromise: contract, + abiMessage, + substrateRegistry +}: Props): UseContractCallerReturn { const { network: chainId } = useNetworkApi() + const [outcome, setOutcome] = useState('') + const [error, setError] = useState() + const { method } = abiMessage const callContractArgs = useMemo(() => { return { chainContract: { contract, chainId }, method } }, [chainId, contract, method]) - const caller = useCall( + const caller = useCall( callContractArgs.chainContract, callContractArgs.method ) - return { caller } + useEffect(() => { + setError(undefined) + + if (caller.result?.ok) { + const { decodedOutput } = getDecodedOutput( + { + debugMessage: caller.result.value.raw.debugMessage, + result: caller.result.value.raw.result + }, + abiMessage, + substrateRegistry + ) + setOutcome(decodedOutput) + } else { + setError( + decodeError( + caller.result?.error, + callContractArgs.chainContract.contract + ) + ) + } + }, [ + abiMessage, + callContractArgs.chainContract, + caller.result, + substrateRegistry + ]) + + return { caller, outcome, error } } diff --git a/src/services/useink/utils/decodeError.ts b/src/services/useink/utils/decodeError.ts new file mode 100644 index 00000000..03ad2134 --- /dev/null +++ b/src/services/useink/utils/decodeError.ts @@ -0,0 +1,36 @@ +import { + ContractPromise, + DispatchError, + RegistryError +} from '@/services/substrate/types.js' + +export type RegistryErrorMethod = string + +export const getRegistryError = ( + error: DispatchError | undefined, + { api }: ContractPromise +): RegistryError | undefined => { + if (!error?.isModule) return + + return api?.registry.findMetaError(error.asModule) +} + +const formatErrorMessage = (registryError: RegistryError): string => + `${registryError.section}.${registryError.method}: ${registryError.docs}` + +export const decodeError = ( + dispatchError: DispatchError | undefined, + chainContract: ContractPromise | undefined, + moduleMessages?: Record, + defaultMessage?: string +): string | undefined => { + if (!chainContract) return undefined + const registryError = getRegistryError(dispatchError, chainContract) + if (!registryError) return undefined + + return ( + moduleMessages?.[registryError.method] || + defaultMessage || + formatErrorMessage(registryError) + ) +} diff --git a/src/services/useink/utils/pickDecodedError.ts b/src/services/useink/utils/pickDecodedError.ts deleted file mode 100644 index e75d82c2..00000000 --- a/src/services/useink/utils/pickDecodedError.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - CallResult, - Contract, - RegistryErrorMethod, - decodeError -} from 'useink/dist/core' - -/// pickDecodedError is a helper function to quickly get a decoded error from a call. -/// -/// // e.g. -/// const call = useCall(contract, 'foo'); -/// pickDecodedError(call, contract); -export function pickDecodedError( - call: CallResult | undefined, - contract: Contract, - moduleMessages?: Record, - defaultMessage?: string -): string | undefined { - const { result } = call || {} - if (!result || result?.ok) return - return decodeError(result.error, contract, moduleMessages, defaultMessage) -} diff --git a/src/utils/contractExecResult.ts b/src/utils/contractExecResult.ts index b2d6a7c9..02841b42 100644 --- a/src/utils/contractExecResult.ts +++ b/src/utils/contractExecResult.ts @@ -24,7 +24,7 @@ function getReturnTypeName(type: TypeDef | null | undefined): string { return type?.lookupName || type?.type || '' } -function stringify(obj: unknown): string { +export function stringify(obj: unknown): string { return JSON5.stringify(obj, null, 2) } @@ -53,6 +53,10 @@ function extractOutcome(returnValue: AnyJson): AnyJson { } function getOutcomeText(outcome: AnyJson): string { + if (isContractResult(outcome) && outcome.Ok === null) { + return 'null' + } + if (!isContractResult(outcome)) { return typeof outcome === 'object' && outcome !== null ? stringify(outcome) diff --git a/src/view/ContractDetailView/ContractInteractionForm/ReadMethodsForm.tsx b/src/view/ContractDetailView/ContractInteractionForm/ReadMethodsForm.tsx index 9236b7ad..7d90870d 100644 --- a/src/view/ContractDetailView/ContractInteractionForm/ReadMethodsForm.tsx +++ b/src/view/ContractDetailView/ContractInteractionForm/ReadMethodsForm.tsx @@ -1,6 +1,6 @@ -import { useEffect, useState } from 'react' +import { useEffect } from 'react' import { ContractInteractionProps } from '.' -import { Box, Stack, Typography } from '@mui/material' +import { Box, FormHelperText, Stack, Typography } from '@mui/material' import { MethodDocumentation } from '../MethodDocumentation' import { AbiParam } from '@/services/substrate/types' import { ButtonCall } from './styled' @@ -24,8 +24,11 @@ export function ReadMethodsForm({ substrateRegistry, expanded }: Props) { - const { caller } = useContractCaller(contractPromise, abiMessage.method) - const [outcome, setOutcome] = useState('') + const { caller, outcome, error } = useContractCaller({ + contractPromise, + abiMessage, + substrateRegistry + }) useEffect(() => { if (!expanded) return @@ -33,20 +36,6 @@ export function ReadMethodsForm({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [inputData, expanded]) - useEffect(() => { - if (caller.result?.ok) { - const { decodedOutput, isError } = getDecodedOutput( - { - debugMessage: caller.result.value.raw.debugMessage, - result: caller.result.value.raw.result - }, - abiMessage, - substrateRegistry - ) - setOutcome(decodedOutput) - } - }, [abiMessage, caller.result, substrateRegistry]) - return ( + {error && ( + + {error} + + )} diff --git a/src/view/ContractDetailView/ContractInteractionForm/WriteMethodsForm.tsx b/src/view/ContractDetailView/ContractInteractionForm/WriteMethodsForm.tsx index ec118ca3..ba565e8d 100644 --- a/src/view/ContractDetailView/ContractInteractionForm/WriteMethodsForm.tsx +++ b/src/view/ContractDetailView/ContractInteractionForm/WriteMethodsForm.tsx @@ -1,24 +1,31 @@ import { Box, CircularProgress, Stack, Typography } from '@mui/material' import { ContractInteractionProps } from '.' import { AbiParam } from '@/services/substrate/types' +import { useDryRunExecution } from '../useDryRunExecution' +import { MethodDocumentation } from '../MethodDocumentation' +import { ButtonCall } from './styled' +import { CopyBlock, atomOneDark } from 'react-code-blocks' type Props = React.PropsWithChildren< - Pick< - ContractInteractionProps, - 'abiMessage' | 'expanded' | 'contractPromise' - > & { + Omit & { abiParams: AbiParam[] inputData: unknown[] | undefined } > -export function WriteMethodsForm({ children, abiParams }: Props) { - // const { outcome, executeDryRun } = useDryRunExecution({ - // contractPromise, - // message: abiMessage, - // params: inputData, - // autoRun: false - // }) +export function WriteMethodsForm({ + children, + abiParams, + abiMessage, + contractPromise, + inputData +}: Props) { + const { outcome, executeDryRun, isSubmitting } = useDryRunExecution({ + contractPromise, + message: abiMessage, + params: inputData, + autoRun: false + }) return ( Outcome - {/* + - {caller.isSubmitting ? ( + {isSubmitting ? ( ) : ( )} - caller.send(inputData)}>Call - */} + Call + - {/* */} + ) diff --git a/src/view/ContractDetailView/ContractInteractionForm/index.tsx b/src/view/ContractDetailView/ContractInteractionForm/index.tsx index 5cc82777..f6500b50 100644 --- a/src/view/ContractDetailView/ContractInteractionForm/index.tsx +++ b/src/view/ContractDetailView/ContractInteractionForm/index.tsx @@ -40,6 +40,7 @@ export function ContractInteractionForm({ abiParams={abiParams} inputData={inputData} substrateRegistry={substrateRegistry} + expanded={expanded} > - Added by + By {''} {truncateAddress(userContract.userAddress, 4)} diff --git a/src/view/ContractDetailView/useDryRunExecution.tsx b/src/view/ContractDetailView/useDryRunExecution.tsx index 3d593706..463310da 100644 --- a/src/view/ContractDetailView/useDryRunExecution.tsx +++ b/src/view/ContractDetailView/useDryRunExecution.tsx @@ -2,8 +2,6 @@ import { useCallback, useMemo, useState } from 'react' import { AbiMessage, ContractPromise } from '@/services/substrate/types' import { useGetDryRun } from '@/hooks/useGetDryRun' import { useDebouncedEffect } from '@/hooks/useDebouncedEffect' -import { MySkeleton } from '../components/MySkeleton' -import { pickDecodedError } from '@/services/useink/utils/pickDecodedError' interface UseDryRunExecutionProps { contractPromise: ContractPromise @@ -30,13 +28,12 @@ export function useDryRunExecution({ const executeDryRun = useCallback(async () => { const result = await dryRun.send(memoizedParams) - console.log('__dryRun', dryRun) if (result?.ok) { setOutcome( `Contract call will be successful executed with ${result.value.partialFee.toString()} fee` ) } else { - // pickDecodedError(result, cRococoContract, {}, '--') + // pickDecodedError(result, contractPromise, {}, '--') setOutcome('Contract will be reverted') } }, [dryRun, memoizedParams]) diff --git a/src/view/components/WalletConnectButton/AccountSelect/index.tsx b/src/view/components/WalletConnectButton/AccountSelect/index.tsx index c4f2d07d..fa9d9e3a 100644 --- a/src/view/components/WalletConnectButton/AccountSelect/index.tsx +++ b/src/view/components/WalletConnectButton/AccountSelect/index.tsx @@ -41,10 +41,7 @@ export function AccountSelect({ if (!accounts) return ( - + ) if (!currentAccount)