diff --git a/frontend/src/app/(routes)/cosmwasm/[network]/PageContracts.tsx b/frontend/src/app/(routes)/cosmwasm/[network]/PageContracts.tsx index 94e058b08..2e700d761 100644 --- a/frontend/src/app/(routes)/cosmwasm/[network]/PageContracts.tsx +++ b/frontend/src/app/(routes)/cosmwasm/[network]/PageContracts.tsx @@ -1,20 +1,31 @@ 'use client'; import TopNav from '@/components/TopNav'; -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useAppSelector } from '@/custom-hooks/StateHooks'; import Contracts from '../components/Contracts'; import AllContracts from '../components/AllContracts'; import DialogTxContractStatus from '../components/DialogTxUploadCodeStatus'; import DialogTxExecuteStatus from '../components/DialogTxExecuteStatus'; import DialogTxInstantiateStatus from '../components/DialogTxInstantiateStatus'; +import { useRouter, useSearchParams } from 'next/navigation'; const PageContracts = ({ chainName }: { chainName: string }) => { + const router = useRouter(); const nameToChainIDs: Record = useAppSelector( (state) => state.wallet.nameToChainIDs ); const chainID = nameToChainIDs[chainName]; const tabs = ['Contracts', 'All Contracts']; const [selectedTab, setSelectedTab] = useState('Contracts'); + + const paramContractAddress = useSearchParams().get('contract'); + + useEffect(() => { + if (paramContractAddress?.length) { + setSelectedTab('Contracts'); + } + }, [paramContractAddress]); + return (
@@ -35,6 +46,7 @@ const PageContracts = ({ chainName }: { chainName: string }) => { } onClick={() => { setSelectedTab(tab); + router.push(`/cosmwasm/${chainName.toLowerCase()}`); }} > {tab} @@ -55,7 +67,7 @@ const PageContracts = ({ chainName }: { chainName: string }) => { {selectedTab === 'Contracts' ? ( ) : ( - + )}
diff --git a/frontend/src/app/(routes)/cosmwasm/components/AllContracts.tsx b/frontend/src/app/(routes)/cosmwasm/components/AllContracts.tsx index 45a101a3b..b66d47c49 100644 --- a/frontend/src/app/(routes)/cosmwasm/components/AllContracts.tsx +++ b/frontend/src/app/(routes)/cosmwasm/components/AllContracts.tsx @@ -1,7 +1,30 @@ +import useGetChainInfo from '@/custom-hooks/useGetChainInfo'; +import { useSearchParams } from 'next/navigation'; import React from 'react'; +import Contracts from './all-contracts/Contracts'; +import Codes from './all-contracts/Codes'; -const AllContracts = () => { - return
Comming Soon...
; +const AllContracts = (props: { chainID: string }) => { + const { chainID } = props; + const { getChainInfo } = useGetChainInfo(); + const { restURLs, chainName } = getChainInfo(chainID); + + const selectedCodeId = useSearchParams().get('code_id'); + + return ( +
+ {selectedCodeId ? ( + + ) : ( + + )} +
+ ); }; export default AllContracts; diff --git a/frontend/src/app/(routes)/cosmwasm/components/ContractInfo.tsx b/frontend/src/app/(routes)/cosmwasm/components/ContractInfo.tsx index 77700a037..9fc52a4d5 100644 --- a/frontend/src/app/(routes)/cosmwasm/components/ContractInfo.tsx +++ b/frontend/src/app/(routes)/cosmwasm/components/ContractInfo.tsx @@ -3,6 +3,7 @@ import React, { useState } from 'react'; import QueryContract from './QueryContract'; import useGetChainInfo from '@/custom-hooks/useGetChainInfo'; import ExecuteContract from './ExecuteContract'; +import Image from 'next/image'; const ContractInfo = ({ chainID }: { chainID: string }) => { const selectedContractAddress = useAppSelector( @@ -11,8 +12,10 @@ const ContractInfo = ({ chainID }: { chainID: string }) => { const selectedContractInfo = useAppSelector( (state) => state.cosmwasm.chains?.[chainID]?.contractInfo ); - const tabs = ['Query Contract', 'Execute Contract']; - const [selectedTab, setSelectedTab] = useState('Query Contract'); + const tabs = ['Execute Contract', 'Query Contract']; + const [selectedTab, setSelectedTab] = useState('Execute Contract'); + const [infoOpen, setInfoOpen] = useState(false); + const { getChainInfo } = useGetChainInfo(); const { restURLs, @@ -20,30 +23,47 @@ const ContractInfo = ({ chainID }: { chainID: string }) => { address: walletAddress, chainName, } = getChainInfo(chainID); + + return ( -
+
- {selectedContractInfo.label || selectedContractAddress} + {selectedContractInfo?.label || selectedContractAddress}
-
- - - - - +
+
+
Contract Info
+ setInfoOpen((prev) => !prev)} + className="cursor-pointer" + src={'/expand-icon.svg'} + height={24} + width={24} + alt="Expand" + /> +
+ {infoOpen ? ( +
+ + + + + +
+ ) : null}
diff --git a/frontend/src/app/(routes)/cosmwasm/components/Contracts.tsx b/frontend/src/app/(routes)/cosmwasm/components/Contracts.tsx index 87cd8443e..3fc2e27f7 100644 --- a/frontend/src/app/(routes)/cosmwasm/components/Contracts.tsx +++ b/frontend/src/app/(routes)/cosmwasm/components/Contracts.tsx @@ -1,9 +1,16 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import SearchContracts from './SearchContracts'; import DeployContract from './DeployContract'; import ContractInfo from './ContractInfo'; +import { useSearchParams } from 'next/navigation'; +import useContracts from '@/custom-hooks/useContracts'; +import useGetChainInfo from '@/custom-hooks/useGetChainInfo'; +import { setContract } from '@/store/features/cosmwasm/cosmwasmSlice'; +import { useAppDispatch } from '@/custom-hooks/StateHooks'; +import { CircularProgress } from '@mui/material'; const Contracts = ({ chainID }: { chainID: string }) => { + const dispatch = useAppDispatch(); const [deployContractOpen, setDeployContractOpen] = useState(false); const [selectedContract, setSelectedContract] = useState({ address: '', @@ -14,6 +21,44 @@ const Contracts = ({ chainID }: { chainID: string }) => { setSelectedContract({ address, name }); }; + const { getChainInfo } = useGetChainInfo(); + const { restURLs } = getChainInfo(chainID); + + const { getContractInfo, contractError, contractLoading } = useContracts(); + + const paramsContractAddress = useSearchParams().get('contract'); + + const fetchContractInfo = async () => { + try { + const { data } = await getContractInfo({ + address: paramsContractAddress || '', + baseURLs: restURLs, + }); + if (data) { + dispatch( + setContract({ + chainID, + contractAddress: data?.address, + contractInfo: data?.contract_info, + }) + ); + setSelectedContract({ + address: data?.address, + name: data?.contract_info?.label, + }); + } + /* eslint-disable @typescript-eslint/no-explicit-any */ + } catch (error: any) { + console.log(error); + } + }; + + useEffect(() => { + if (paramsContractAddress?.length) { + fetchContractInfo(); + } + }, [paramsContractAddress]); + return (
@@ -52,6 +97,16 @@ const Contracts = ({ chainID }: { chainID: string }) => { {deployContractOpen && !selectedContract.address ? ( ) : null} + {contractLoading ? ( +
+ +
+ Fetching contract info +
+
+ ) : contractError ? ( +
{contractError}
+ ) : null} {selectedContract.address ? : null}
); diff --git a/frontend/src/app/(routes)/cosmwasm/components/DialogAddressesList.tsx b/frontend/src/app/(routes)/cosmwasm/components/DialogAddressesList.tsx new file mode 100644 index 000000000..325b9613a --- /dev/null +++ b/frontend/src/app/(routes)/cosmwasm/components/DialogAddressesList.tsx @@ -0,0 +1,67 @@ +import CommonCopy from '@/components/CommonCopy'; +import { dialogBoxPaperPropStyles } from '@/utils/commonStyles'; +import { CLOSE_ICON_PATH } from '@/utils/constants'; +import { Dialog, DialogContent } from '@mui/material'; +import Image from 'next/image'; +import React from 'react'; + +const DialogAddressesList = ({ + addresses, + onClose, + open, +}: { + open: boolean; + onClose: () => void; + addresses: string[]; +}) => { + const handleDialogClose = () => { + onClose(); + }; + return ( + + +
+
+
+ close +
+
+
+
+
+ Allowed Addresses +
+
+ List of addresses that are allowed to instantiate contract{' '} +
+
+
+
+ {addresses.map((address) => ( +
+ +
+ ))} +
+
+
+
+
+ ); +}; + +export default DialogAddressesList; diff --git a/frontend/src/app/(routes)/cosmwasm/components/DialogSearchContract.tsx b/frontend/src/app/(routes)/cosmwasm/components/DialogSearchContract.tsx index 7d3f07676..35932e62c 100644 --- a/frontend/src/app/(routes)/cosmwasm/components/DialogSearchContract.tsx +++ b/frontend/src/app/(routes)/cosmwasm/components/DialogSearchContract.tsx @@ -9,6 +9,7 @@ import { setContract } from '@/store/features/cosmwasm/cosmwasmSlice'; import useContracts from '@/custom-hooks/useContracts'; import ContractItem from './ContractItem'; import SearchInputField from './SearchInputField'; +import { useRouter } from 'next/navigation'; interface DialogSearchContractI { open: boolean; @@ -21,6 +22,7 @@ interface DialogSearchContractI { const DialogSearchContract = (props: DialogSearchContractI) => { const { onClose, open, chainID, restURLs, handleSelectContract } = props; const dispatch = useAppDispatch(); + const router = useRouter(); const handleClose = () => { onClose(); setSearchTerm(''); @@ -54,6 +56,7 @@ const DialogSearchContract = (props: DialogSearchContractI) => { contractInfo: searchResult?.contract_info, }) ); + router.push(`?contract=${searchResult?.address}`); handleSelectContract( searchResult?.address, searchResult?.contract_info?.label diff --git a/frontend/src/app/(routes)/cosmwasm/components/ExecuteContract.tsx b/frontend/src/app/(routes)/cosmwasm/components/ExecuteContract.tsx index d71e2b185..e6e836579 100644 --- a/frontend/src/app/(routes)/cosmwasm/components/ExecuteContract.tsx +++ b/frontend/src/app/(routes)/cosmwasm/components/ExecuteContract.tsx @@ -1,14 +1,13 @@ import useContracts from '@/custom-hooks/useContracts'; -import { CircularProgress, SelectChangeEvent, TextField } from '@mui/material'; -import React, { useState } from 'react'; +import { SelectChangeEvent } from '@mui/material'; +import React, { useEffect, useState } from 'react'; import AttachFunds from './AttachFunds'; import useGetChainInfo from '@/custom-hooks/useGetChainInfo'; import { useAppDispatch, useAppSelector } from '@/custom-hooks/StateHooks'; -import { TxStatus } from '@/types/enums'; import { executeContract } from '@/store/features/cosmwasm/cosmwasmSlice'; import { getFormattedFundsList } from '@/utils/util'; -import { queryInputStyles } from '../styles'; import { setError } from '@/store/features/common/commonSlice'; +import ExecuteContractInputs from './ExecuteContractInputs'; interface ExecuteContractI { address: string; @@ -27,7 +26,15 @@ const ExecuteContract = (props: ExecuteContractI) => { // ---------------DEPENDENCIES---------------// // ------------------------------------------// const dispatch = useAppDispatch(); - const { getExecutionOutput } = useContracts(); + const { + getExecutionOutput, + getExecuteMessages, + executeMessagesError, + executeMessagesLoading, + getExecuteMessagesInputs, + executeInputsError, + executeInputsLoading, + } = useContracts(); const { getDenomInfo } = useGetChainInfo(); const { decimals, minimalDenom } = getDenomInfo(chainID); @@ -44,6 +51,11 @@ const ExecuteContract = (props: ExecuteContractI) => { }, ]); const [fundsInput, setFundsInput] = useState(''); + const [executeMessages, setExecuteMessages] = useState([]); + const [selectedMessage, setSelectedMessage] = useState(''); + const [executeMessageInputs, setExecuteMessageInputs] = useState( + [] + ); const txExecuteLoading = useAppSelector( (state) => state.cosmwasm.chains?.[chainID].txExecute.status @@ -52,7 +64,7 @@ const ExecuteContract = (props: ExecuteContractI) => { // ------------------------------------------------// // -----------------CHANGE HANDLERS----------------// // ------------------------------------------------// - const handleQueryChange = ( + const handleExecuteInputChange = ( e: React.ChangeEvent ) => { setExecuteInput(e.target.value); @@ -62,6 +74,34 @@ const ExecuteContract = (props: ExecuteContractI) => { setAttachFundType(event.target.value); }; + const handleSelectMessage = async (msg: string) => { + setExecuteInput(`{\n\t"${msg}": {}\n}`); + setSelectedMessage(msg); + const { messages } = await getExecuteMessagesInputs({ + chainID, + contractAddress: address, + rpcURLs, + msg: { [msg]: {} }, + msgName: msg, + extractedMessages: [], + }); + setExecuteMessageInputs(messages); + }; + + const handleSelectedMessageInputChange = (value: string) => { + setExecuteInput( + JSON.stringify( + { + [selectedMessage]: { + [value]: '', + }, + }, + undefined, + 2 + ) + ); + }; + // ----------------------------------------------------// // -----------------CUSTOM VALIDATIONS-----------------// // ----------------------------------------------------// @@ -111,7 +151,16 @@ const ExecuteContract = (props: ExecuteContractI) => { // ------------------------------------------// // ---------------TRANSACTION----------------// // ------------------------------------------// - const onExecute = async () => { + const onExecute = async (input: string) => { + if (!input?.length) { + dispatch( + setError({ + type: 'error', + message: 'Please enter execution message', + }) + ); + return; + } if (!formatExecutionMessage()) return; if (attachFundType === 'json' && !validateFunds()) return; @@ -125,7 +174,7 @@ const ExecuteContract = (props: ExecuteContractI) => { executeContract({ chainID, contractAddress: address, - msgs: executeInput, + msgs: input, rpcURLs, walletAddress, funds: attachedFunds, @@ -135,44 +184,43 @@ const ExecuteContract = (props: ExecuteContractI) => { ); }; + // ------------------------------------------// + // ---------------SIDE EFFECT----------------// + // ------------------------------------------// + useEffect(() => { + setSelectedMessage(''); + setExecuteMessageInputs([]); + const fetchMessages = async () => { + const { messages } = await getExecuteMessages({ + chainID, + contractAddress: address, + rpcURLs, + }); + setExecuteMessages(messages); + }; + fetchMessages(); + }, [address]); + return ( -
-
-
- - - -
-
-
+
+ +
Attach Funds
diff --git a/frontend/src/app/(routes)/cosmwasm/components/ExecuteContractInputs.tsx b/frontend/src/app/(routes)/cosmwasm/components/ExecuteContractInputs.tsx new file mode 100644 index 000000000..718bbbfbb --- /dev/null +++ b/frontend/src/app/(routes)/cosmwasm/components/ExecuteContractInputs.tsx @@ -0,0 +1,239 @@ +import { CircularProgress, TextField } from '@mui/material'; +import React, { useEffect, useState } from 'react'; +import { queryInputStyles } from '../styles'; +import { TxStatus } from '@/types/enums'; +import MessageInputFields from './MessageInputFields'; + +const ExecuteContractInputs = (props: ExecuteContractInputsI) => { + const { + messagesLoading, + executeMessages, + handleSelectMessage, + executeMessageInputs, + selectedMessage, + handleSelectedMessageInputChange, + executeInput, + handleExecuteInputChange, + onExecute, + executionLoading, + formatJSON, + executeInputsError, + executeInputsLoading, + messagesError, + contractAddress, + } = props; + + // ------------------------------------------// + // ------------------STATES------------------// + // ------------------------------------------// + const [isJSONInput, setIsJSONInput] = useState(false); + const [messageInputFields, setMessageInputFields] = useState< + MessageInputField[] + >([]); + + // ------------------------------------------------// + // -----------------CHANGE HANDLERS----------------// + // ------------------------------------------------// + const handleInputMessageChange = ( + e: React.ChangeEvent, + index: number + ) => { + const input = e.target.value; + const updatedFields = messageInputFields.map((value, key) => { + if (index === key) { + value.value = input; + } + return value; + }); + setMessageInputFields(updatedFields); + }; + + const executeContract = () => { + let messageInputs = {}; + messageInputFields.forEach((field) => { + messageInputs = { ...messageInputs, [field.name]: field.value }; + }); + const executionInput = JSON.stringify( + { + [selectedMessage]: messageInputs, + }, + undefined, + 2 + ); + onExecute(executionInput); + }; + + // ------------------------------------------// + // ---------------SIDE EFFECTS----------------// + // ------------------------------------------// + useEffect(() => { + const inputFields: MessageInputField[] = []; + executeMessageInputs.forEach((messageInput) => { + inputFields.push({ name: messageInput, open: false, value: '' }); + }); + setMessageInputFields(inputFields); + }, [executeMessageInputs]); + + useEffect(() => { + setMessageInputFields([]); + }, [contractAddress]); + + return ( +
+
+
+ Execution Messages: + {messagesLoading ? ( + + {' '} + Fetching messages{' '} + + ) : executeMessages?.length ? null : ( + No messages found + )} +
+
+ {executeMessages?.map((msg) => ( +
handleSelectMessage(msg)} + key={msg} + className={`query-shortcut-msg ${selectedMessage === msg ? 'primary-gradient' : ''}`} + > + {msg} +
+ ))} +
+
+ {isJSONInput && executeMessageInputs?.length ? ( +
+
+ Suggested Inputs for{' '} + {selectedMessage}: +
+
+ {executeMessageInputs?.map((msg) => ( +
handleSelectedMessageInputChange(msg)} + key={msg} + className="query-shortcut-msg" + > + {msg} +
+ ))} +
+
+ ) : null} +
+
+ {isJSONInput + ? 'Enter execution message in JSON format:' + : messageInputFields.length + ? 'Enter fields to execute:' + : 'Execution Input:'} +
+
+ +
+
+ {isJSONInput ? ( +
+ + + +
+ ) : executeInputsLoading ? ( +
+ + Fetching message inputs + +
+ ) : executeInputsError ? ( +
+ Couldn't fetch message inputs, Please switch to JSON format +
+ ) : !messageInputFields.length ? ( +
+ {selectedMessage?.length ? ( +
+
+
{selectedMessage}
+
+ +
+ ) : ( +
+ {messagesError ? ( +
+ Couldn't fetch messages, Please switch to JSON format +
+ ) : ( +
+ - Select a message to execute - +
+ )} +
+ )} +
+ ) : ( + + )} +
+ ); +}; + +export default ExecuteContractInputs; diff --git a/frontend/src/app/(routes)/cosmwasm/components/MessageInputFields.tsx b/frontend/src/app/(routes)/cosmwasm/components/MessageInputFields.tsx index 0504fde8a..601318f5c 100644 --- a/frontend/src/app/(routes)/cosmwasm/components/MessageInputFields.tsx +++ b/frontend/src/app/(routes)/cosmwasm/components/MessageInputFields.tsx @@ -1,40 +1,28 @@ import { TxStatus } from '@/types/enums'; import { CircularProgress } from '@mui/material'; -import Image from 'next/image'; import React from 'react'; const MessageInputFields = ({ fields, handleChange, onQuery, - expandField, queryLoading, + isQuery, }: { fields: MessageInputField[]; handleChange: (e: React.ChangeEvent, index: number) => void; - onQuery: (index: number) => void; - expandField: (index: number) => void; + onQuery: () => void; queryLoading: TxStatus; + isQuery: boolean; }) => { return (
- {fields.map((field, index) => ( -
-
-
{field.name}
- expandField(index)} - className="cursor-pointer" - src={'/expand-icon.svg'} - height={24} - width={24} - alt="Expand" - /> -
- {field?.open ? ( +
+ {fields.map((field, index) => ( +
+
+
{field.name}
+
-
- ) : null} -
- ))} +
+ ))} +
+ +
); }; diff --git a/frontend/src/app/(routes)/cosmwasm/components/QueryContract.tsx b/frontend/src/app/(routes)/cosmwasm/components/QueryContract.tsx index 627db21db..adab14c65 100644 --- a/frontend/src/app/(routes)/cosmwasm/components/QueryContract.tsx +++ b/frontend/src/app/(routes)/cosmwasm/components/QueryContract.tsx @@ -61,6 +61,8 @@ const QueryContract = (props: QueryContractI) => { address, baseURLs, queryMsg: { [msg]: {} }, + msgName: msg, + extractedMessages: [], }); setContractMessageInputs(messages); }; @@ -129,6 +131,8 @@ const QueryContract = (props: QueryContractI) => { // ---------------SIDE EFFECT----------------// // ------------------------------------------// useEffect(() => { + setSelectedMessage(''); + setContractMessageInputs([]); const fetchMessages = async () => { const { messages } = await getContractMessages({ address, baseURLs }); setContractMessages(messages); @@ -153,8 +157,10 @@ const QueryContract = (props: QueryContractI) => { messageInputsLoading={messageInputsLoading} messageInputsError={messageInputsError} messagesError={messagesError} + contractAddress={address} />
+
Query Output:
{JSON.stringify(queryOutput, undefined, 2)}
diff --git a/frontend/src/app/(routes)/cosmwasm/components/QueryContractInputs.tsx b/frontend/src/app/(routes)/cosmwasm/components/QueryContractInputs.tsx index b1414c9c8..d1479a0b6 100644 --- a/frontend/src/app/(routes)/cosmwasm/components/QueryContractInputs.tsx +++ b/frontend/src/app/(routes)/cosmwasm/components/QueryContractInputs.tsx @@ -20,6 +20,7 @@ const QueryContractInputs = (props: QueryContractInputsI) => { messageInputsLoading, messageInputsError, messagesError, + contractAddress, } = props; // ------------------------------------------// @@ -47,24 +48,17 @@ const QueryContractInputs = (props: QueryContractInputsI) => { setMessageInputFields(updatedFields); }; - const expandField = (index: number) => { - const updatedFields = messageInputFields.map((field, i) => { - if (i === index) { - return { ...field, open: !field.open }; - } else { - return { ...field, open: false }; - } - }); - - setMessageInputFields(updatedFields); - }; - - const queryContract = (index: number) => { + const queryContract = () => { + const messageInputs = messageInputFields.reduce( + (acc, field) => ({ + ...acc, + [field.name]: field.value, + }), + {} + ); const queryInput = JSON.stringify( { - [selectedMessage]: { - [messageInputFields[index].name]: messageInputFields[index].value, - }, + [selectedMessage]: messageInputs, }, undefined, 2 @@ -83,17 +77,21 @@ const QueryContractInputs = (props: QueryContractInputsI) => { setMessageInputFields(inputFields); }, [contractMessageInputs]); + useEffect(() => { + setMessageInputFields([]); + }, [contractAddress]); + return (
- Suggested Messages: + Queries: {messagesLoading ? ( Fetching messages{' '} ) : contractMessages?.length ? null : ( - {' '}No messages found + No messages found )}
@@ -101,14 +99,20 @@ const QueryContractInputs = (props: QueryContractInputsI) => {
handleSelectMessage(msg)} key={msg} - className={`query-shortcut-msg ${!isJSONInput && selectedMessage === msg ? 'primary-gradient' : ''}`} + className={`query-shortcut-msg ${selectedMessage === msg ? 'primary-gradient' : ''}`} > {msg}
))}
- {contractMessageInputs?.length ? ( + {isJSONInput && messageInputsLoading ? ( +
+ + Fetching message inputs + +
+ ) : isJSONInput && contractMessageInputs?.length ? (
Suggested Inputs for{' '} @@ -197,9 +201,9 @@ const QueryContractInputs = (props: QueryContractInputsI) => { ) : !messageInputFields.length ? (
{selectedMessage?.length ? ( -
+
-
{selectedMessage}
+
{selectedMessage}
diff --git a/frontend/src/app/(routes)/cosmwasm/components/all-contracts/CodeItem.tsx b/frontend/src/app/(routes)/cosmwasm/components/all-contracts/CodeItem.tsx new file mode 100644 index 000000000..a85fa3d4c --- /dev/null +++ b/frontend/src/app/(routes)/cosmwasm/components/all-contracts/CodeItem.tsx @@ -0,0 +1,33 @@ +import CommonCopy from '@/components/CommonCopy'; +import { shortenMsg } from '@/utils/util'; +import Link from 'next/link'; +import React from 'react'; +import PermissionsData from './PermissionsData'; + +const CodeItem = ({ code }: { code: CodeInfo }) => { + return ( + + {code.code_id} + + + {shortenMsg(code.data_hash, 25)} + + + + + + + + + + ); +}; + +export default CodeItem; diff --git a/frontend/src/app/(routes)/cosmwasm/components/all-contracts/Codes.tsx b/frontend/src/app/(routes)/cosmwasm/components/all-contracts/Codes.tsx new file mode 100644 index 000000000..07cbf1e87 --- /dev/null +++ b/frontend/src/app/(routes)/cosmwasm/components/all-contracts/Codes.tsx @@ -0,0 +1,57 @@ +import { useAppDispatch, useAppSelector } from '@/custom-hooks/StateHooks'; +import { getAllCodes } from '@/store/features/cosmwasm/cosmwasmSlice'; +import { TxStatus } from '@/types/enums'; +import { CircularProgress } from '@mui/material'; +import React, { useEffect } from 'react'; +import CodesList from './CodesList'; + +const Codes = ({ + chainID, + baseURLs, +}: { + chainID: string; + baseURLs: string[]; +}) => { + const dispatch = useAppDispatch(); + const codesLoading = useAppSelector( + (state) => state.cosmwasm.chains?.[chainID]?.codes.status + ); + + const codes = useAppSelector( + (state) => state.cosmwasm.chains?.[chainID]?.codes.data.codes + ); + + useEffect(() => { + dispatch(getAllCodes({ baseURLs, chainID })); + }, []); + + return ( +
+ {codesLoading === TxStatus.PENDING ? ( +
+
+ +
+ Fetching Codes + +
+
+
+ ) : codes?.length ? ( + + ) : ( +
+
+ {codesLoading === TxStatus.REJECTED ? ( +
- Failed to fetch codes -
+ ) : ( + '- No Codes Found -' + )} +
+
+ )} +
+ ); +}; + +export default Codes; diff --git a/frontend/src/app/(routes)/cosmwasm/components/all-contracts/CodesList.tsx b/frontend/src/app/(routes)/cosmwasm/components/all-contracts/CodesList.tsx new file mode 100644 index 000000000..97b423462 --- /dev/null +++ b/frontend/src/app/(routes)/cosmwasm/components/all-contracts/CodesList.tsx @@ -0,0 +1,28 @@ +import React from 'react' +import TableHeader from './TableHeader'; +import CodeItem from './CodeItem'; + +const CodesList = (props: { codes: CodeInfo[] }) => { + const { codes } = props; + const tableColumnTitle = ['Code Id', 'Code Hash', 'Creator', 'Permissions']; + return ( +
+ + + + {tableColumnTitle.map((title) => ( + + ))} + + + + {codes.map((code) => ( + + ))} + +
+
+ ); + }; + +export default CodesList \ No newline at end of file diff --git a/frontend/src/app/(routes)/cosmwasm/components/all-contracts/ContractItem.tsx b/frontend/src/app/(routes)/cosmwasm/components/all-contracts/ContractItem.tsx new file mode 100644 index 000000000..fe6b179fa --- /dev/null +++ b/frontend/src/app/(routes)/cosmwasm/components/all-contracts/ContractItem.tsx @@ -0,0 +1,26 @@ +import CommonCopy from '@/components/CommonCopy'; +import Link from 'next/link'; +import React from 'react'; + +const ContractItem = ({ contract }: { contract: string }) => { + return ( + + + + + + + + + + + ); +}; + +export default ContractItem; diff --git a/frontend/src/app/(routes)/cosmwasm/components/all-contracts/Contracts.tsx b/frontend/src/app/(routes)/cosmwasm/components/all-contracts/Contracts.tsx new file mode 100644 index 000000000..da4e182f8 --- /dev/null +++ b/frontend/src/app/(routes)/cosmwasm/components/all-contracts/Contracts.tsx @@ -0,0 +1,94 @@ +import { useAppDispatch, useAppSelector } from '@/custom-hooks/StateHooks'; +import { getAllContractsByCode } from '@/store/features/cosmwasm/cosmwasmSlice'; +import { TxStatus } from '@/types/enums'; +import { CircularProgress, Tooltip } from '@mui/material'; +import Image from 'next/image'; +import { useRouter } from 'next/navigation'; +import React, { useEffect } from 'react'; +import ContractsList from './ContractsList'; + +const Contracts = ({ + codeId, + baseURLs, + chainID, + chainName, +}: { + codeId: string; + chainID: string; + baseURLs: string[]; + chainName: string; +}) => { + const router = useRouter(); + const dispatch = useAppDispatch(); + + useEffect(() => { + dispatch( + getAllContractsByCode({ + baseURLs, + chainID, + codeId, + }) + ); + }, [codeId]); + + const contractsLoading = useAppSelector( + (state) => state.cosmwasm.chains?.[chainID]?.contracts.status + ); + + const contracts = useAppSelector( + (state) => state.cosmwasm.chains?.[chainID]?.contracts.data.contracts + ); + + return ( +
+
+ + { + router.push(`/cosmwasm/${chainName.toLowerCase()}`); + }} + className="cursor-pointer" + src="/go-back-icon.svg" + width={32} + height={32} + alt="Go Back" + draggable={false} + /> + +
+ Contracts List of Code: {codeId} +
+
+
+ {contractsLoading === TxStatus.PENDING ? ( +
+
+ +
+ + Fetching Contracts + + +
+
+
+ ) : contracts?.length ? ( + + ) : ( +
+
+ {contractsLoading === TxStatus.REJECTED ? ( +
+ - Failed to fetch contracts - +
+ ) : ( + '- No Contracts Found -' + )} +
+
+ )} +
+
+ ); +}; +export default Contracts; diff --git a/frontend/src/app/(routes)/cosmwasm/components/all-contracts/ContractsList.tsx b/frontend/src/app/(routes)/cosmwasm/components/all-contracts/ContractsList.tsx new file mode 100644 index 000000000..51859d531 --- /dev/null +++ b/frontend/src/app/(routes)/cosmwasm/components/all-contracts/ContractsList.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import TableHeader from './TableHeader'; +import ContractItem from './ContractItem'; + +const ContractsList = ({ contracts }: { contracts: string[] }) => { + const tableColumnTitles = ['Contracts', 'Action']; + return ( +
+ + + + {tableColumnTitles.map((title) => ( + + ))} + + + + {contracts.map((contract) => ( + + ))} + +
+
+ ); +}; + +export default ContractsList; diff --git a/frontend/src/app/(routes)/cosmwasm/components/all-contracts/PermissionsData.tsx b/frontend/src/app/(routes)/cosmwasm/components/all-contracts/PermissionsData.tsx new file mode 100644 index 000000000..0e468e1e4 --- /dev/null +++ b/frontend/src/app/(routes)/cosmwasm/components/all-contracts/PermissionsData.tsx @@ -0,0 +1,45 @@ +import Image from 'next/image'; +import React, { useState } from 'react'; +import DialogAddressesList from '../DialogAddressesList'; + +const PermissionsData = ({ + permission, +}: { + permission: InstantiatePermission; +}) => { + const permissionType = permission.permission; + const [showAddresses, setShowAddresses] = useState(false); + const handleDialogOpen = ( + e: React.MouseEvent, + value: boolean + ) => { + setShowAddresses(value); + e.stopPropagation(); + }; + return ( +
+ {permission.addresses?.length ? ( +
+
{permissionType}
+ handleDialogOpen(e, true)} + className="cursor-pointer" + src="/view-more-icon.svg" + height={20} + width={20} + alt="View Addresses" + /> + setShowAddresses(false)} + open={showAddresses} + /> +
+ ) : ( +
{permissionType}
+ )} +
+ ); +}; + +export default PermissionsData; diff --git a/frontend/src/app/(routes)/cosmwasm/components/all-contracts/TableHeader.tsx b/frontend/src/app/(routes)/cosmwasm/components/all-contracts/TableHeader.tsx new file mode 100644 index 000000000..734f6b7b7 --- /dev/null +++ b/frontend/src/app/(routes)/cosmwasm/components/all-contracts/TableHeader.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +const TableHeader = ({ title }: { title: string }) => { + return ( + +
+ {title} +
+ + ); +}; + +export default TableHeader; diff --git a/frontend/src/app/(routes)/cosmwasm/cosmwasm.css b/frontend/src/app/(routes)/cosmwasm/cosmwasm.css index 3a7d752ef..372416476 100644 --- a/frontend/src/app/(routes)/cosmwasm/cosmwasm.css +++ b/frontend/src/app/(routes)/cosmwasm/cosmwasm.css @@ -1,5 +1,5 @@ .tabs { - @apply flex gap-10 items-center justify-center border-b-[1px] border-[#ffffff1e] mt-6; + @apply flex gap-10 items-center justify-start border-b-[1px] border-[#ffffff1e] mt-6; } .menu-item { @@ -52,7 +52,7 @@ .execute-field-wrapper, .execute-output-box { - @apply w-[50%] max-h-[440px] p-6; + @apply max-h-[440px] p-6; } .execute-field-wrapper { @@ -169,3 +169,24 @@ .change-input-type-btn { @apply bg-[#232034] text-[12px] font-semibold px-3 py-[10px] flex justify-center items-center rounded-lg; } + +.codes-table { + @apply rounded-3xl text-white flex flex-col flex-1; + backdrop-filter: blur(2px); +} + +.codes-table th { + @apply pb-4; +} + +.codes-table td { + @apply font-light leading-normal pt-4 pb-4 px-2; +} + +.codes-table tbody tr { + @apply hover:bg-[#ffffff0D]; +} + +.select-btn { + @apply px-3 py-[6px] rounded-lg; +} diff --git a/frontend/src/custom-hooks/useContracts.ts b/frontend/src/custom-hooks/useContracts.ts index eb6f36382..7c496dd86 100644 --- a/frontend/src/custom-hooks/useContracts.ts +++ b/frontend/src/custom-hooks/useContracts.ts @@ -10,6 +10,7 @@ import chainDenoms from '@/utils/chainDenoms.json'; import useGetChainInfo from './useGetChainInfo'; import { Event } from 'cosmjs-types/tendermint/abci/types'; import { toUtf8 } from '@cosmjs/encoding'; +import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'; declare let window: WalletWindow; @@ -59,6 +60,10 @@ const useContracts = () => { const [messagesError, setMessagesError] = useState(''); const [messageInputsLoading, setMessageInputsLoading] = useState(false); const [messageInputsError, setMessageInputsError] = useState(''); + const [executeMessagesLoading, setExecuteMessagesLoading] = useState(false); + const [executeMessagesError, setExecuteMessagesError] = useState(''); + const [executeInputsLoading, setExecuteInputsLoading] = useState(false); + const [executeInputsError, setExecuteInputsError] = useState(''); const getContractInfo = async ({ address, @@ -124,36 +129,51 @@ const useContracts = () => { address, baseURLs, queryMsg, + extractedMessages, + msgName, }: { address: string; baseURLs: string[]; queryMsg: any; + msgName: string; + extractedMessages: string[]; }) => { - let messages: string[] = []; - try { - setMessageInputsLoading(true); - setMessageInputsError(''); - await queryContract(baseURLs, address, btoa(JSON.stringify(queryMsg))); - return { - messages: [], - }; - /* eslint-disable @typescript-eslint/no-explicit-any */ - } catch (error: any) { - const errMsg = error.message; - if ( - errMsg?.includes('expected') || - errMsg?.includes('missing field') - ) { - messages = extractContractMessages(error.message); - } else { - messages = []; - setMessageInputsError('Failed to fetch message inputs'); + setMessageInputsLoading(true); + setMessageInputsError(''); + + const queryWithRetry = async (msg: { + [key: string]: any; + }): Promise => { + try { + await queryContract(baseURLs, address, btoa(JSON.stringify(queryMsg))); + return; + } catch (error: any) { + const errMsg = error.message; + if (errMsg?.includes('Failed to query contract')) { + setMessageInputsError('Failed to fetch messages'); + extractedMessages = []; + } else if (errMsg?.includes('expected')) { + setMessageInputsError('Failed to fetch messages'); + extractedMessages = []; + } else { + const newlyExtractedMessages = extractContractMessages(error.message); + if (newlyExtractedMessages.length === 0) { + return; + } else { + extractedMessages.push(...newlyExtractedMessages); + for (const field of extractedMessages) { + msg[msgName][field] = '1'; + } + await queryWithRetry(msg); + } + } } - } finally { - setMessageInputsLoading(false); - } + }; + + await queryWithRetry(queryMsg); + setMessageInputsLoading(false); return { - messages, + messages: extractedMessages, }; }; @@ -183,7 +203,17 @@ const useContracts = () => { contractAddress: string; }) => { const { dummyAddress, dummyWallet } = await getDummyWallet({ chainID }); - const client = await connectWithSigner(rpcURLs, dummyWallet); + let messages: string[] = []; + setExecuteMessagesLoading(true); + setExecuteMessagesError(''); + let client: SigningCosmWasmClient; + try { + client = await connectWithSigner(rpcURLs, dummyWallet); + } catch (error: any) { + setExecuteMessagesError('Failed to fetch messages'); + setExecuteMessagesLoading(false); + return { messages: [] }; + } try { await client.simulate( dummyAddress, @@ -200,9 +230,100 @@ const useContracts = () => { ], undefined ); + return { + messages: [], + }; } catch (error: any) { - console.log(error); + const errMsg = error.message; + if (errMsg?.includes('expected') || errMsg?.includes('missing field')) { + messages = extractContractMessages(error.message); + } else { + messages = []; + setExecuteMessagesError('Failed to fetch messages'); + } + } finally { + setExecuteMessagesLoading(false); + } + return { + messages, + }; + }; + + const getExecuteMessagesInputs = async ({ + rpcURLs, + chainID, + contractAddress, + msg, + msgName, + extractedMessages, + }: { + rpcURLs: string[]; + chainID: string; + contractAddress: string; + msg: { [key: string]: any }; + msgName: string; + extractedMessages: string[]; + }): Promise<{ messages: string[] }> => { + const { dummyAddress, dummyWallet } = await getDummyWallet({ chainID }); + setExecuteInputsLoading(true); + setExecuteInputsError(''); + let client: SigningCosmWasmClient; + try { + client = await connectWithSigner(rpcURLs, dummyWallet); + } catch (error: any) { + setExecuteInputsError('Failed to fetch messages'); + setExecuteInputsLoading(false); + return { messages: [] }; } + + const executeWithRetry = async (msg: { + [key: string]: any; + }): Promise => { + try { + await client.simulate( + dummyAddress, + [ + { + typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', + value: { + sender: dummyAddress, + contract: contractAddress, + msg: Buffer.from(JSON.stringify(msg)), + funds: [], + }, + }, + ], + undefined + ); + + return; + } catch (error: any) { + const errMsg = error.message; + if (errMsg?.includes('expected') || errMsg?.includes('429')) { + setExecuteInputsError('Failed to fetch messages'); + extractedMessages = []; + } else if (errMsg?.includes('Insufficient')) { + setExecuteInputsError(''); + return; + } else { + const newlyExtractedMessages = extractContractMessages(error.message); + setExecuteInputsError(''); + if (newlyExtractedMessages.length === 0) { + return; + } else { + extractedMessages.push(...newlyExtractedMessages); + for (const field of extractedMessages) { + msg[msgName][field] = '1'; + } + await executeWithRetry(msg); + } + } + } + }; + + await executeWithRetry(msg); + setExecuteInputsLoading(false); + return { messages: extractedMessages }; }; const getExecutionOutput = async ({ @@ -378,9 +499,14 @@ const useContracts = () => { uploadContract, instantiateContract, getContractMessageInputs, + getExecuteMessagesInputs, messageInputsLoading, messageInputsError, messagesError, + executeMessagesError, + executeMessagesLoading, + executeInputsError, + executeInputsLoading, }; }; diff --git a/frontend/src/custom-hooks/useDummyWallet.ts b/frontend/src/custom-hooks/useDummyWallet.ts index 5410aca53..5c96a4ee1 100644 --- a/frontend/src/custom-hooks/useDummyWallet.ts +++ b/frontend/src/custom-hooks/useDummyWallet.ts @@ -1,29 +1,21 @@ import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; -import { useState } from 'react'; import useGetChainInfo from './useGetChainInfo'; import { DUMMY_WALLET_MNEMONIC } from '@/utils/constants'; export const useDummyWallet = () => { const { getChainInfo } = useGetChainInfo(); - const [dummyWallet, setDummyWallet] = useState(); - const [dummyAddress, setDummyAddress] = useState(''); const getDummyWallet = async ({ chainID }: { chainID: string }) => { - console.log(DUMMY_WALLET_MNEMONIC) const { prefix } = getChainInfo(chainID); - if (DUMMY_WALLET_MNEMONIC) { - const wallet = await DirectSecp256k1HdWallet.fromMnemonic( - DUMMY_WALLET_MNEMONIC, - { - prefix, - } - ); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic( + DUMMY_WALLET_MNEMONIC, + { + prefix, + } + ); + const allAccounts = await wallet.getAccounts(); + const { address } = allAccounts[0]; - setDummyWallet(wallet); - - const { address } = (await wallet.getAccounts())[0]; - setDummyAddress(address); - } - return { dummyWallet, dummyAddress }; + return { dummyWallet: wallet, dummyAddress: address }; }; return { getDummyWallet }; }; diff --git a/frontend/src/store/features/cosmwasm/cosmwasmService.ts b/frontend/src/store/features/cosmwasm/cosmwasmService.ts index 98c1dd693..188599cfc 100644 --- a/frontend/src/store/features/cosmwasm/cosmwasmService.ts +++ b/frontend/src/store/features/cosmwasm/cosmwasmService.ts @@ -1,10 +1,15 @@ 'use client'; +import { axiosGetRequestWrapper } from '@/utils/RequestWrapper'; import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'; const getContractURL = (baseURL: string, address: string) => `${baseURL}/cosmwasm/wasm/v1/contract/${address}`; +const codesURL = '/cosmwasm/wasm/v1/code'; +const contractsByCodeURL = (codeId: string) => + `/cosmwasm/wasm/v1/code/${codeId}/contracts`; + const getContractQueryURL = ( baseURL: string, address: string, @@ -79,6 +84,18 @@ export const connectWithSigner = async (urls: string[], offlineSigner: any) => { throw new Error('Unable to connect to any RPC URLs'); }; +export const getCodes = async (baseURLs: string[]) => { + return axiosGetRequestWrapper(baseURLs, codesURL, 10); +}; + +export const getContractsByCode = async ( + baseURLs: string[], + codeId: string +) => { + const uri = contractsByCodeURL(codeId); + return axiosGetRequestWrapper(baseURLs, uri, 10); +}; + const result = { contract: getContract, }; diff --git a/frontend/src/store/features/cosmwasm/cosmwasmSlice.ts b/frontend/src/store/features/cosmwasm/cosmwasmSlice.ts index 55f6f5072..3957fed13 100644 --- a/frontend/src/store/features/cosmwasm/cosmwasmSlice.ts +++ b/frontend/src/store/features/cosmwasm/cosmwasmSlice.ts @@ -8,6 +8,7 @@ import { setError } from '../common/commonSlice'; import axios from 'axios'; import { cleanURL } from '@/utils/util'; import { parseTxResult } from '@/utils/signing'; +import { getCodes, getContractsByCode } from './cosmwasmService'; /* eslint-disable @typescript-eslint/no-explicit-any */ export const contractInfoEmptyState = { @@ -49,6 +50,21 @@ interface Chain { error: string; queryOutput: string; }; + codes: { + status: TxStatus; + error: string; + data: { + codes: CodeInfo[]; + }; + }; + contracts: { + status: TxStatus; + error: string; + data: { + contracts: string[]; + codeId: string; + }; + }; } interface Chains { @@ -109,6 +125,19 @@ const initialState: CosmwasmState = { status: TxStatus.INIT, error: '', }, + codes: { + data: { codes: [] }, + error: '', + status: TxStatus.INIT, + }, + contracts: { + data: { + codeId: '', + contracts: [], + }, + error: '', + status: TxStatus.INIT, + }, }, }; @@ -134,6 +163,59 @@ export const queryContractInfo = createAsyncThunk( } ); +export const getAllCodes = createAsyncThunk( + 'cosmwasm/get-codes', + async ( + data: { baseURLs: string[]; chainID: string }, + { rejectWithValue, dispatch } + ) => { + try { + const response = await getCodes(data.baseURLs); + return { + data: response.data, + chainID: data.chainID, + }; + } catch (error: any) { + const errMsg = error?.message || 'Failed to fetch codes'; + dispatch( + setError({ + message: errMsg, + type: 'error', + }) + ); + return rejectWithValue(errMsg); + } + } +); + +export const getAllContractsByCode = createAsyncThunk( + 'cosmwasm/get-contract-by-code', + async ( + data: { baseURLs: string[]; codeId: string; chainID: string }, + { rejectWithValue, dispatch } + ) => { + try { + const response = await getContractsByCode(data.baseURLs, data.codeId); + return { + data: { + contracts: response.data, + code: data.codeId, + }, + chainID: data.chainID, + }; + } catch (error: any) { + const errMsg = error?.message || 'Failed to fetch contracts'; + dispatch( + setError({ + message: errMsg, + type: 'error', + }) + ); + return rejectWithValue(errMsg); + } + } +); + export const executeContract = createAsyncThunk( 'cosmwasm/execute-contract', async (data: ExecuteContractInputs, { rejectWithValue, dispatch }) => { @@ -287,6 +369,55 @@ export const cosmwasmSlice = createSlice({ state.chains[chainID].query.error = action.error.message || ERR_UNKNOWN; state.chains[chainID].query.queryOutput = '{}'; }); + + builder + .addCase(getAllCodes.pending, (state, action) => { + const chainID = action.meta.arg.chainID; + if (!state.chains[chainID]) { + state.chains[chainID] = cloneDeep(initialState.defaultState); + } + state.chains[chainID].codes.status = TxStatus.PENDING; + state.chains[chainID].codes.error = ''; + }) + .addCase(getAllCodes.fulfilled, (state, action) => { + const chainID = action.meta.arg.chainID; + state.chains[chainID].codes.status = TxStatus.IDLE; + state.chains[chainID].codes.error = ''; + state.chains[chainID].codes.data.codes = + action.payload.data?.code_infos || []; + }) + .addCase(getAllCodes.rejected, (state, action) => { + const chainID = action.meta.arg.chainID; + state.chains[chainID].codes.status = TxStatus.REJECTED; + state.chains[chainID].codes.error = action.error.message || ERR_UNKNOWN; + state.chains[chainID].codes.data.codes = []; + }); + builder + .addCase(getAllContractsByCode.pending, (state, action) => { + const chainID = action.meta.arg.chainID; + if (!state.chains[chainID]) { + state.chains[chainID] = cloneDeep(initialState.defaultState); + } + state.chains[chainID].contracts.status = TxStatus.PENDING; + state.chains[chainID].contracts.error = ''; + }) + .addCase(getAllContractsByCode.fulfilled, (state, action) => { + const chainID = action.meta.arg.chainID; + state.chains[chainID].contracts.status = TxStatus.IDLE; + state.chains[chainID].contracts.error = ''; + state.chains[chainID].contracts.data.contracts = + action.payload.data?.contracts?.contracts || []; + state.chains[chainID].contracts.data.codeId = + action.payload.data?.code || ''; + }) + .addCase(getAllContractsByCode.rejected, (state, action) => { + const chainID = action.meta.arg.chainID; + state.chains[chainID].contracts.status = TxStatus.REJECTED; + state.chains[chainID].contracts.error = + action.error.message || ERR_UNKNOWN; + state.chains[chainID].contracts.data.contracts = []; + state.chains[chainID].contracts.data.codeId = ''; + }); builder .addCase(executeContract.pending, (state, action) => { const chainID = action.meta.arg.chainID; diff --git a/frontend/src/types/cosmwasm.d.ts b/frontend/src/types/cosmwasm.d.ts index ac54364b7..915df04ad 100644 --- a/frontend/src/types/cosmwasm.d.ts +++ b/frontend/src/types/cosmwasm.d.ts @@ -172,6 +172,7 @@ interface QueryContractInputsI { messageInputsLoading: boolean; messageInputsError: string; messagesError: string; + contractAddress: string; } interface MessageInputField { @@ -179,3 +180,35 @@ interface MessageInputField { value: string; open: boolean; } + +interface ExecuteContractInputsI { + messagesLoading: boolean; + executeMessages: string[]; + handleSelectMessage: (msg: string) => Promise; + selectedMessage: string; + executeMessageInputs: string[]; + handleSelectedMessageInputChange: (value: string) => void; + executeInput: string; + handleExecuteInputChange: ( + e: React.ChangeEvent + ) => void; + onExecute: (input: string) => void; + executionLoading: TxStatus; + formatJSON: () => void; + executeInputsLoading: boolean; + executeInputsError: string; + messagesError: string; + contractAddress: string; +} + +interface CodeInfo { + code_id: string; + creator: string; + data_hash: string; + instantiate_permission: InstantiatePermission; +} + +interface InstantiatePermission { + permission: string; + addresses: string[]; +} diff --git a/frontend/src/utils/util.ts b/frontend/src/utils/util.ts index 517e61b1f..9c20ee0ac 100644 --- a/frontend/src/utils/util.ts +++ b/frontend/src/utils/util.ts @@ -473,11 +473,19 @@ export function formatValidatorStatsValue( } export function extractContractMessages(inputString: string): string[] { + let errMsg = ''; + if (inputString.includes('expected')) { + errMsg = inputString.split('expected')[1]; + } else if (inputString.includes('missing')) { + errMsg = inputString.split('missing')[1]; + } else { + errMsg = inputString; + } const pattern: RegExp = /`(\w+)`/g; const matches: string[] = []; let match: RegExpExecArray | null; - while ((match = pattern.exec(inputString)) !== null) { + while ((match = pattern.exec(errMsg)) !== null) { matches.push(match[1]); }