diff --git a/CHANGELOG.md b/CHANGELOG.md index d28feaf42..9a3d3007f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Features +- [#37](https://github.com/alleslabs/celatone-frontend/pull/37) Add contract details data loader - [#31](https://github.com/alleslabs/celatone-frontend/pull/31) Add contract details page ui skeleton - [#41](https://github.com/alleslabs/celatone-frontend/pull/41) Add Github action for tracking CHANGELOG.md for changes diff --git a/src/env.ts b/src/env.ts index 8d90c643c..87e400b31 100644 --- a/src/env.ts +++ b/src/env.ts @@ -13,7 +13,7 @@ export const CELATONE_FALLBACK_GAS_PRICE: Record = { }, }; -export const CELATONE_CONTRACT_ADDRESS = ( +export const CELATONE_APP_CONTRACT_ADDRESS = ( chainName: string ): CelatoneContractAddress => { switch (chainName) { diff --git a/src/lib/app-fns/tx/execute.tsx b/src/lib/app-fns/tx/execute.tsx index 2c6e20fc3..339b27f8e 100644 --- a/src/lib/app-fns/tx/execute.tsx +++ b/src/lib/app-fns/tx/execute.tsx @@ -10,7 +10,7 @@ import type { Observable } from "rxjs"; import { ExplorerLink } from "lib/components/ExplorerLink"; import type { Activity } from "lib/stores/contract"; -import type { TxResultRendering } from "lib/types"; +import type { ContractAddr, TxResultRendering } from "lib/types"; import { TxStreamPhase } from "lib/types"; import { encode, formatUFee } from "lib/utils"; @@ -18,7 +18,7 @@ import { catchTxError, postTx, sendingTx } from "./common"; interface ExecuteTxParams { address: string; - contractAddress: string; + contractAddress: ContractAddr; fee: StdFee; msg: object; client: SigningCosmWasmClient; diff --git a/src/lib/app-provider/contexts/app.tsx b/src/lib/app-provider/contexts/app.tsx index 5c3f431a4..b53c8cc47 100644 --- a/src/lib/app-provider/contexts/app.tsx +++ b/src/lib/app-provider/contexts/app.tsx @@ -21,12 +21,12 @@ import { useCodeStore, useContractStore } from "lib/hooks"; import type { ChainGasPrice, Token, U } from "lib/types"; import { formatUserKey } from "lib/utils"; -interface AppProviderProps { +interface AppProviderProps { children: ReactNode; fallbackGasPrice: Record; - contractAddress: (currentChainName: string) => ContractAddress; + appContractAddressMap: (currentChainName: string) => AppContractAddress; constants: Constants; } @@ -36,12 +36,12 @@ interface AppContextInterface< Constants extends AppConstants = AppConstants > { chainGasPrice: ChainGasPrice; - contractAddress: ContractAddress; + appContractAddress: ContractAddress; constants: Constants; explorerLink: { - contractAddr: string; - txs: string; - address: string; + contractUrl: string; + txUrl: string; + userUrl: string; }; indexerGraphClient: GraphQLClient; } @@ -49,12 +49,12 @@ interface AppContextInterface< // eslint-disable-next-line @typescript-eslint/no-explicit-any const AppContext = createContext>({ chainGasPrice: { denom: "", gasPrice: "0" as U }, - contractAddress: {}, + appContractAddress: {}, constants: { gasAdjustment: 0 }, explorerLink: { - contractAddr: "", - txs: "", - address: "", + contractUrl: "", + txUrl: "", + userUrl: "", }, indexerGraphClient: new GraphQLClient(""), }); @@ -62,7 +62,7 @@ const AppContext = createContext>({ export const AppProvider = ({ children, fallbackGasPrice, - contractAddress, + appContractAddressMap, constants, }: AppProviderProps) => { const { currentChainName, currentChainRecord, setCurrentChain } = useWallet(); @@ -87,30 +87,29 @@ export const AppProvider = ({ const chainBoundStates = useMemo(() => { return { explorerLink: { - contractAddr: getExplorerContractAddressUrl(currentChainName), - txs: getExplorerTxUrl(currentChainName), - address: getExplorerUserAddressUrl(currentChainName), + contractUrl: getExplorerContractAddressUrl(currentChainName), + txUrl: getExplorerTxUrl(currentChainName), + userUrl: getExplorerUserAddressUrl(currentChainName), }, indexerGraphClient: getIndexerGraphClient(currentChainName), }; }, [currentChainName]); - const states = useMemo< - AppContextInterface - >(() => { - return { + const states = useMemo>( + () => ({ chainGasPrice, - contractAddress: contractAddress(currentChainName), + appContractAddress: appContractAddressMap(currentChainName), constants, ...chainBoundStates, - }; - }, [ - chainGasPrice, - contractAddress, - currentChainName, - constants, - chainBoundStates, - ]); + }), + [ + chainGasPrice, + appContractAddressMap, + currentChainName, + constants, + chainBoundStates, + ] + ); useEffect(() => { if (currentChainName) { diff --git a/src/lib/app-provider/tx/execute.ts b/src/lib/app-provider/tx/execute.ts index 8a29742a8..d34ac1bb6 100644 --- a/src/lib/app-provider/tx/execute.ts +++ b/src/lib/app-provider/tx/execute.ts @@ -5,12 +5,13 @@ import { useCallback } from "react"; import { executeContractTx } from "lib/app-fns/tx/execute"; import { useUserKey } from "lib/hooks/useUserKey"; import type { Activity } from "lib/stores/contract"; +import type { ContractAddr } from "lib/types"; export interface ExecuteStreamParams { onTxSucceed?: (userKey: string, activity: Activity) => void; onTxFailed?: () => void; estimatedFee: StdFee | undefined; - contractAddress: string; + contractAddress: ContractAddr; msg: object; } diff --git a/src/lib/components/InstantiateOffchainDetail.tsx b/src/lib/components/InstantiateOffchainDetail.tsx index 695269eae..5cc2551fb 100644 --- a/src/lib/components/InstantiateOffchainDetail.tsx +++ b/src/lib/components/InstantiateOffchainDetail.tsx @@ -14,6 +14,7 @@ import { } from "lib/data"; import { useContractStore } from "lib/hooks"; import { useUserKey } from "lib/hooks/useUserKey"; +import type { ContractAddr } from "lib/types"; import { ListSelection } from "./forms/ListSelection"; import { TagSelection } from "./forms/TagSelection"; @@ -22,7 +23,7 @@ interface InstantiateOffChainFormProps { title?: string; subtitle?: string; cta?: boolean; - contractAddress: string; + contractAddress: ContractAddr; contractLabel: string; } diff --git a/src/lib/components/modal/EditTags.tsx b/src/lib/components/modal/EditTags.tsx index 43b306dbd..f8a1621b5 100644 --- a/src/lib/components/modal/EditTags.tsx +++ b/src/lib/components/modal/EditTags.tsx @@ -19,7 +19,7 @@ export function EditTags({ contractInfo }: EditTagsProps) { const [tagResult, setTagResult] = useState(contractInfo.tags ?? []); const handleSave = useHandleContractSave({ title: "Updated tags successfully!", - address: contractInfo.address, + contractAddress: contractInfo.contractAddress, instantiator: contractInfo.instantiator, label: contractInfo.label, created: contractInfo.created, @@ -48,7 +48,7 @@ export function EditTags({ contractInfo }: EditTagsProps) { {contractInfo.name ?? contractInfo.label} diff --git a/src/lib/components/modal/code/SaveNewCode.tsx b/src/lib/components/modal/code/SaveNewCode.tsx index 4aefbb9ae..7879d0f80 100644 --- a/src/lib/components/modal/code/SaveNewCode.tsx +++ b/src/lib/components/modal/code/SaveNewCode.tsx @@ -12,7 +12,7 @@ import { MAX_CODE_DESCRIPTION_LENGTH, } from "lib/data"; import { useCodeStore, useEndpoint, useUserKey } from "lib/hooks"; -import { getCodeIdInfo } from "lib/services/contract"; +import { getCodeIdInfo } from "lib/services/code"; interface ModalProps { buttonProps: ButtonProps; diff --git a/src/lib/components/modal/contract/AddToOtherList.tsx b/src/lib/components/modal/contract/AddToOtherList.tsx index b2be7ede4..3d08df03b 100644 --- a/src/lib/components/modal/contract/AddToOtherList.tsx +++ b/src/lib/components/modal/contract/AddToOtherList.tsx @@ -25,7 +25,7 @@ export function AddToOtherList({ const handleSave = useHandleContractSave({ title: "Action complete!", - address: contractInfo.address, + contractAddress: contractInfo.contractAddress, instantiator: contractInfo.instantiator, label: contractInfo.label, created: contractInfo.created, @@ -52,7 +52,7 @@ export function AddToOtherList({ {contractInfo.name ?? contractInfo.label} diff --git a/src/lib/components/modal/contract/EditContract.tsx b/src/lib/components/modal/contract/EditContract.tsx index ec282a350..0ad7af2da 100644 --- a/src/lib/components/modal/contract/EditContract.tsx +++ b/src/lib/components/modal/contract/EditContract.tsx @@ -34,7 +34,7 @@ export const EditContract = ({ contractInfo, menuItemProps }: ModalProps) => { const handleSave = useHandleContractSave({ title: "Action Complete", - address: contractInfo.address, + contractAddress: contractInfo.contractAddress, instantiator: contractInfo.instantiator, label: contractInfo.label, created: contractInfo.created, @@ -54,7 +54,10 @@ export const EditContract = ({ contractInfo, menuItemProps }: ModalProps) => { Contract Address - + } trigger={} diff --git a/src/lib/components/modal/contract/RemoveContract.tsx b/src/lib/components/modal/contract/RemoveContract.tsx index 990c874e1..32ab9b93a 100644 --- a/src/lib/components/modal/contract/RemoveContract.tsx +++ b/src/lib/components/modal/contract/RemoveContract.tsx @@ -21,11 +21,11 @@ export function RemoveContract({ }: ModalProps) { const displayName = contractInfo.name ? contractInfo.name - : truncate(contractInfo.address); + : truncate(contractInfo.contractAddress); const handleRemove = useHandleContractSave({ title: `Removed ${displayName} from ${list.label}`, - address: contractInfo.address, + contractAddress: contractInfo.contractAddress, instantiator: contractInfo.instantiator, label: contractInfo.label, created: contractInfo.created, diff --git a/src/lib/components/modal/contract/SaveNewContract.tsx b/src/lib/components/modal/contract/SaveNewContract.tsx index c5714bb62..e7748b0f7 100644 --- a/src/lib/components/modal/contract/SaveNewContract.tsx +++ b/src/lib/components/modal/contract/SaveNewContract.tsx @@ -18,8 +18,8 @@ import { } from "lib/data"; import { useContractStore, useEndpoint } from "lib/hooks"; import { useHandleContractSave } from "lib/hooks/useHandleSave"; -import { queryContractWithTime } from "lib/services/contract"; -import type { Option, RpcContractError } from "lib/types"; +import { queryInstantiateInfo } from "lib/services/contract"; +import type { ContractAddr, Option, RpcContractError } from "lib/types"; import { formatSlugName } from "lib/utils"; interface SaveNewContractProps { @@ -28,7 +28,7 @@ interface SaveNewContractProps { } export function SaveNewContract({ list, buttonProps }: SaveNewContractProps) { const { - contractAddress: { example: exampleContractAddress }, + appContractAddress: { example: exampleContractAddress }, } = useCelatoneApp(); const initialList = list.value === formatSlugName(INSTANTIATED_LIST_NAME) ? [] : [list]; @@ -62,8 +62,8 @@ export function SaveNewContract({ list, buttonProps }: SaveNewContractProps) { // TODO: Abstract query const { refetch } = useQuery( - ["query", "contractWithTime", contractAddress], - async () => queryContractWithTime(endpoint, contractAddress), + ["query", "instantiateInfo", contractAddress], + async () => queryInstantiateInfo(endpoint, contractAddress as ContractAddr), { enabled: false, retry: false, @@ -72,7 +72,7 @@ export function SaveNewContract({ list, buttonProps }: SaveNewContractProps) { onSuccess(data) { setInstantiator(data.instantiator); setLabel(data.label); - setCreated(data.created); + setCreated(data.createdTime); setName(data.label); setStatus({ state: "success", @@ -121,7 +121,7 @@ export function SaveNewContract({ list, buttonProps }: SaveNewContractProps) { const handleSave = useHandleContractSave({ title: `Saved ${name.trim().length ? name : label}`, - address: contractAddress, + contractAddress: contractAddress as ContractAddr, instantiator, label, created, diff --git a/src/lib/components/modal/select-contract/SelectContract.tsx b/src/lib/components/modal/select-contract/SelectContract.tsx index 8de579efe..843b184b6 100644 --- a/src/lib/components/modal/select-contract/SelectContract.tsx +++ b/src/lib/components/modal/select-contract/SelectContract.tsx @@ -25,7 +25,7 @@ import { DEFAULT_RPC_ERROR } from "lib/data"; import { useContractStore, useEndpoint } from "lib/hooks"; import { useInstantiatedByMe } from "lib/model/contract"; import { queryContract } from "lib/services/contract"; -import type { RpcContractError } from "lib/types"; +import type { ContractAddr, RpcContractError } from "lib/types"; import { AllContractLists } from "./AllContractLists"; import { ListDetail } from "./ListDetail"; @@ -39,7 +39,9 @@ export const SelectContract = ({ notSelected, onContractSelect, }: SelectContractProps) => { - const { contractAddress } = useCelatoneApp(); + const { + appContractAddress: { example: exampleContractAddress }, + } = useCelatoneApp(); const { isOpen, onOpen, onClose } = useDisclosure(); const [listSlug, setListSlug] = useState(""); @@ -71,7 +73,7 @@ export const SelectContract = ({ // TODO: Abstract query const { refetch, isFetching, isRefetching } = useQuery( ["query", "contract", searchManual], - async () => queryContract(endpoint, searchManual), + async () => queryContract(endpoint, searchManual as ContractAddr), { enabled: false, retry: false, @@ -124,7 +126,7 @@ export const SelectContract = ({ const inputValue = e.target.value; setSearchManual(inputValue); }} - placeholder={`ex. ${contractAddress.example}`} + placeholder={`ex. ${exampleContractAddress}`} size="md" /> - + diff --git a/src/lib/pages/contracts/components/table/ContractNameCell.tsx b/src/lib/pages/contracts/components/table/ContractNameCell.tsx index 78708328a..4e92dd1d2 100644 --- a/src/lib/pages/contracts/components/table/ContractNameCell.tsx +++ b/src/lib/pages/contracts/components/table/ContractNameCell.tsx @@ -14,7 +14,7 @@ export const ContractNameCell = ({ }: ContractNameCellProps) => { const onSave = useHandleContractSave({ title: "Changed name successfully!", - address: contract.address, + contractAddress: contract.contractAddress, instantiator: contract.instantiator, label: contract.label, created: contract.created, diff --git a/src/lib/pages/execute/components/ExecuteArea.tsx b/src/lib/pages/execute/components/ExecuteArea.tsx index 5f23c92ee..405ee4a20 100644 --- a/src/lib/pages/execute/components/ExecuteArea.tsx +++ b/src/lib/pages/execute/components/ExecuteArea.tsx @@ -20,7 +20,7 @@ import { MsgType } from "lib/types"; import { composeMsg, jsonPrettify, jsonValidate } from "lib/utils"; interface ExecuteAreaProps { - contractAddress: string; + contractAddress: ContractAddr; initialMsg: string; cmds: [string, string][]; } diff --git a/src/lib/pages/execute/index.tsx b/src/lib/pages/execute/index.tsx index 5df5f500e..49b0f9bfd 100644 --- a/src/lib/pages/execute/index.tsx +++ b/src/lib/pages/execute/index.tsx @@ -82,19 +82,24 @@ const Execute = () => { useEffect(() => { (async () => { - const contractAddr = getFirstQueryParam(router.query.contract); - const contractState = getContractInfo(contractAddr); + const contractAddressParam = getFirstQueryParam( + router.query.contract + ) as ContractAddr; + const contractState = getContractInfo(contractAddressParam); let decodeMsg = decode(getFirstQueryParam(router.query.msg)); if (decodeMsg && jsonValidate(decodeMsg) !== null) { - onContractSelect(contractAddr); + onContractSelect(contractAddressParam); decodeMsg = ""; } const jsonMsg = jsonPrettify(decodeMsg); if (!contractState) { try { - const onChainDetail = await queryContract(endpoint, contractAddr); - setContractName(onChainDetail.result?.label); + const onChainDetail = await queryContract( + endpoint, + contractAddressParam + ); + setContractName(onChainDetail.contract_info.label); } catch { setContractName("Invalid Contract"); } @@ -102,9 +107,9 @@ const Execute = () => { setContractName(contractState.name ?? contractState.label); } - setContractAddress(contractAddr); + setContractAddress(contractAddressParam); setInitialMsg(jsonMsg); - if (!contractAddr) setCmds([]); + if (!contractAddressParam) setCmds([]); })(); }, [router, endpoint, getContractInfo, onContractSelect]); @@ -179,7 +184,7 @@ const Execute = () => { diff --git a/src/lib/pages/home/components/RecentlyViewContracts.tsx b/src/lib/pages/home/components/RecentlyViewContracts.tsx index 587ebb7f7..99a14cdfe 100644 --- a/src/lib/pages/home/components/RecentlyViewContracts.tsx +++ b/src/lib/pages/home/components/RecentlyViewContracts.tsx @@ -1,11 +1,13 @@ import { Heading, Box, Flex, Text } from "@chakra-ui/react"; import { ContractListTable } from "lib/pages/contracts/components/ContractListTable"; +import type { ContractAddr } from "lib/types"; /* TODO: change data -> recently view contracts */ const contracts = [ { - address: "terra18kw0z0nmpk9drz4qxq8y7xvh05tr7spyzja3rq", + contractAddress: + "terra18kw0z0nmpk9drz4qxq8y7xvh05tr7spyzja3rq" as ContractAddr, name: "Deposit asset", tags: ["deposit", "lending"], instantiator: "terra18kw0z0nmpk9drz4qxq8y7xvh05tr7spyzja3rq", @@ -15,7 +17,8 @@ const contracts = [ created: new Date(), }, { - address: "terra18kw0z0nmpk9drz4qxq8y7xvh05tr7spyzja3rq", + contractAddress: + "terra18kw0z0nmpk9drz4qxq8y7xvh05tr7spyzja3rq" as ContractAddr, name: "Borrow asset", tags: ["deposit", "lending", "borrow", "beeb", "margin"], instantiator: "terra18kw0z0nmpk9drz4qxq8y7xvh05tr7spyzja3rq", @@ -24,7 +27,8 @@ const contracts = [ created: new Date(), }, { - address: "terra18kw0z0nmpk9drz4qxq8y7xvh05tr7spyzja3rq", + contractAddress: + "terra18kw0z0nmpk9drz4qxq8y7xvh05tr7spyzja3rq" as ContractAddr, name: "", tags: ["deposit", "lending", "borrow", "margin"], instantiator: "terra18kw0z0nmpk9drz4qxq8y7xvh05tr7spyzja3rq", @@ -33,7 +37,8 @@ const contracts = [ created: new Date(), }, { - address: "terra18kw0z0nmpk9drz4qxq8y7xvh05tr7spyzja3rq", + contractAddress: + "terra18kw0z0nmpk9drz4qxq8y7xvh05tr7spyzja3rq" as ContractAddr, name: "Deposit asset to Lorem", tags: [], instantiator: "terra18kw0z0nmpk9drz4qxq8y7xvh05tr7spyzja3rq", diff --git a/src/lib/pages/instantiate/completed.tsx b/src/lib/pages/instantiate/completed.tsx index 0e8a31f3e..47762054a 100644 --- a/src/lib/pages/instantiate/completed.tsx +++ b/src/lib/pages/instantiate/completed.tsx @@ -8,6 +8,7 @@ import { InstantiateOffChainDetail } from "lib/components/InstantiateOffchainDet import { TxReceiptRender } from "lib/components/tx/receipt"; import WasmPageContainer from "lib/components/WasmPageContainer"; import { getExplorerContractAddressUrl } from "lib/data"; +import type { ContractAddr } from "lib/types"; import { formatUFee } from "lib/utils"; import type { InstantiateTxInfo } from "."; @@ -106,7 +107,7 @@ const Completed = ({ txInfo }: CompletedProps) => { diff --git a/src/lib/pages/pastTxs/components/PastTxTable.tsx b/src/lib/pages/pastTxs/components/PastTxTable.tsx index 5201c786a..fa7e7a6f5 100644 --- a/src/lib/pages/pastTxs/components/PastTxTable.tsx +++ b/src/lib/pages/pastTxs/components/PastTxTable.tsx @@ -195,7 +195,7 @@ const PastTxTable = ({ element }: PastTxTableProps) => { const renderExecute = useCallback( (executeMsgs: Array) => { const tags = [Object.keys(executeMsgs[0].msg)[0]]; - const contractAddr = executeMsgs[0].contract; + const contractAddress = executeMsgs[0].contract; if (executeMsgs.length > 1) { tags.push(Object.keys(executeMsgs[1].msg)[0]); } @@ -203,7 +203,7 @@ const PastTxTable = ({ element }: PastTxTableProps) => { setIsAccordion(true); setButton("resend"); // Multiple Execute msgs - if (executeMsgs.some((msg) => msg.contract !== contractAddr)) { + if (executeMsgs.some((msg) => msg.contract !== contractAddress)) { if (!element.success) { setButton(""); return ( diff --git a/src/lib/pages/pastTxs/query/graphqlQuery.ts b/src/lib/pages/pastTxs/query/graphqlQuery.ts index f08033b60..f04fc7d92 100644 --- a/src/lib/pages/pastTxs/query/graphqlQuery.ts +++ b/src/lib/pages/pastTxs/query/graphqlQuery.ts @@ -135,14 +135,14 @@ export const queryWithActionsFromTxs = ( // Handle the case where contract address is searched export const queryAddrFromContracts = (actionsFilter: string) => { return gql` - query QueryAddrFromContracts($userAddr: String!, $contractAddr: String!, $pageSize: Int!, $offset: Int!) { + query QueryAddrFromContracts($userAddr: String!, $contractAddress: String!, $pageSize: Int!, $offset: Int!) { contract_transactions( where: { transaction: { account: { address: { _eq: $userAddr } }, ${actionsFilter !== "" ? `${actionsFilter},` : ""} } - contract: { address: { _eq: $contractAddr } } + contract: { address: { _eq: $contractAddress } } } limit: $pageSize, offset: $offset, @@ -168,7 +168,7 @@ export const queryAddrFromContracts = (actionsFilter: string) => { account: { address: { _eq: $userAddr } }, ${actionsFilter !== "" ? `${actionsFilter},` : ""} } - contract: { address: { _eq: $contractAddr } } + contract: { address: { _eq: $contractAddress } } } ) { aggregate { diff --git a/src/lib/pages/pastTxs/query/useTxQuery.ts b/src/lib/pages/pastTxs/query/useTxQuery.ts index 3518a4ac2..69728842a 100644 --- a/src/lib/pages/pastTxs/query/useTxQuery.ts +++ b/src/lib/pages/pastTxs/query/useTxQuery.ts @@ -133,7 +133,7 @@ export const useTxQuery = ( queryAddrFromContracts(actionsFilter()), { userAddr, - contractAddr: search, + contractAddress: search, pageSize, offset, } diff --git a/src/lib/pages/query/components/QueryArea.tsx b/src/lib/pages/query/components/QueryArea.tsx index 11ff291fb..2e80e0197 100644 --- a/src/lib/pages/query/components/QueryArea.tsx +++ b/src/lib/pages/query/components/QueryArea.tsx @@ -11,13 +11,13 @@ import JsonInput from "lib/components/json/JsonInput"; import { DEFAULT_RPC_ERROR } from "lib/data"; import { useContractStore, useEndpoint, useUserKey } from "lib/hooks"; import { queryData } from "lib/services/contract"; -import type { RpcQueryError } from "lib/types"; +import type { ContractAddr, RpcQueryError } from "lib/types"; import { encode, jsonPrettify, jsonValidate } from "lib/utils"; import JsonReadOnly from "./JsonReadOnly"; interface QueryAreaProps { - contractAddress: string; + contractAddress: ContractAddr; initialMsg: string; cmds: [string, string][]; } diff --git a/src/lib/pages/query/index.tsx b/src/lib/pages/query/index.tsx index cd21590e2..49c865d27 100644 --- a/src/lib/pages/query/index.tsx +++ b/src/lib/pages/query/index.tsx @@ -11,7 +11,7 @@ import { SelectContract } from "lib/components/modal/select-contract"; import PageContainer from "lib/components/PageContainer"; import { useContractStore, useEndpoint, useMobile } from "lib/hooks"; import { queryContract, queryData } from "lib/services/contract"; -import type { RpcQueryError } from "lib/types"; +import type { ContractAddr, RpcQueryError } from "lib/types"; import { jsonPrettify, getFirstQueryParam, @@ -27,15 +27,15 @@ const Query = () => { const endpoint = useEndpoint(); const isMobile = useMobile(); - const [addr, setAddr] = useState(""); - const [name, setName] = useState(""); - const [cmds, setCmds] = useState<[string, string][]>([]); + const [contractAddress, setContractAddress] = useState(""); + const [contractName, setContractName] = useState(""); const [initialMsg, setInitialMsg] = useState(""); + const [cmds, setCmds] = useState<[string, string][]>([]); const goToExecute = () => { router.push({ pathname: "/execute", - query: { ...(addr && { contract: addr }) }, + query: { ...(contractAddress && { contract: contractAddress }) }, }); }; @@ -55,10 +55,11 @@ const Query = () => { // TODO: Abstract query and make query key const { isFetching } = useQuery( - ["query", "cmds", endpoint, addr, '{"": {}}'], - async () => queryData(endpoint, addr, '{"": {}}'), + ["query", "cmds", endpoint, contractAddress, '{"": {}}'], + async () => + queryData(endpoint, contractAddress as ContractAddr, '{"": {}}'), { - enabled: !!addr, + enabled: !!contractAddress, retry: false, cacheTime: 0, refetchOnWindowFocus: false, @@ -74,33 +75,38 @@ const Query = () => { useEffect(() => { (async () => { - const contractAddr = getFirstQueryParam(router.query.contract); - const contractState = getContractInfo(contractAddr); + const contractAddressParam = getFirstQueryParam( + router.query.contract + ) as ContractAddr; + const contractState = getContractInfo(contractAddressParam); let decodeMsg = decode(getFirstQueryParam(router.query.msg)); if (decodeMsg && jsonValidate(decodeMsg) !== null) { - onContractSelect(contractAddr); + onContractSelect(contractAddressParam); decodeMsg = ""; } const jsonMsg = jsonPrettify(decodeMsg); if (!contractState) { try { - const onChainDetail = await queryContract(endpoint, contractAddr); - setName(onChainDetail.result?.label); + const onChainDetail = await queryContract( + endpoint, + contractAddressParam + ); + setContractName(onChainDetail.contract_info.label); } catch { - setName("Invalid Contract"); + setContractName("Invalid Contract"); } } else { - setName(contractState.name ?? contractState.label); + setContractName(contractState.name ?? contractState.label); } - setAddr(contractAddr); + setContractAddress(contractAddressParam); setInitialMsg(jsonMsg); - if (!contractAddr) setCmds([]); + if (!contractAddressParam) setCmds([]); })(); }, [router, endpoint, getContractInfo, onContractSelect]); - const notSelected = addr.length === 0; + const notSelected = contractAddress.length === 0; return ( @@ -139,7 +145,7 @@ const Query = () => { Contract Address {!notSelected ? ( { textColor={notSelected ? "text.disabled" : "text.dark"} variant="body2" > - {notSelected ? "Not Selected" : name} + {notSelected ? "Not Selected" : contractName} @@ -168,7 +174,11 @@ const Query = () => { /> - + ); }; diff --git a/src/lib/services/code.ts b/src/lib/services/code.ts new file mode 100644 index 000000000..762f6663c --- /dev/null +++ b/src/lib/services/code.ts @@ -0,0 +1,27 @@ +import axios from "axios"; + +import type { ContractAddr, HumanAddr } from "lib/types"; + +interface CodeIdInfoResponse { + code_info: { + code_id: string; + creator: HumanAddr | ContractAddr; + data_hash: string; + instantiate_permission: { + permission: string; + address: string; + addresses: string[]; + }; + }; + data: string; +} + +export const getCodeIdInfo = async ( + endpoint: string, + id: number +): Promise => { + const { data } = await axios.get( + `${endpoint}/cosmwasm/wasm/v1/code/${id}` + ); + return data; +}; diff --git a/src/lib/services/contract.ts b/src/lib/services/contract.ts index f11f061a8..2424fff7f 100644 --- a/src/lib/services/contract.ts +++ b/src/lib/services/contract.ts @@ -1,26 +1,22 @@ +import type { Coin } from "@cosmjs/stargate"; import axios from "axios"; +import type { ContractAddr, HumanAddr } from "lib/types"; import { encode } from "lib/utils"; -export const queryData = async ( - endpoint: string, - contract: string, - msg: string -) => { - const b64 = encode(msg); - const { data } = await axios.get( - `${endpoint}/cosmwasm/wasm/v1/contract/${contract}/smart/${b64}` - ); - return data; -}; - interface ContractResponse { - height: string; - result: { - address: string; - code_id: number; - creator: string; + address: ContractAddr; + contract_info: { + code_id: string; + creator: HumanAddr | ContractAddr; + admin?: HumanAddr | ContractAddr; label: string; + created?: { + block_height: number; + tx_index: number; + }; + ibc_port_id: string; + extension?: string; }; } @@ -32,32 +28,116 @@ interface BlockResponse { }; } -export const queryContract = async (endpoint: string, contract: string) => { - // TODO: change to `${endpoint}/cosmwasm/wasm/v1/contract/${contract}` - const { data } = await axios.get( - `${endpoint}/wasm/contract/${contract}` +interface BalancesResponse { + balances: Coin[]; +} + +interface PublicInfoResponse { + slug: string; + name: string; + address: ContractAddr; + description: string; +} + +export interface InstantiateInfo { + contractAddress: ContractAddr; + codeId: string; + instantiator: HumanAddr | ContractAddr; + admin?: HumanAddr | ContractAddr; + label: string; + createdHeight: number; + createdTime: Date; + ibcPortId: string; + raw: ContractResponse; +} + +export interface PublicInfo { + slug: string; + name: string; + contractAddress: ContractAddr; + description: string; +} + +export const queryData = async ( + endpoint: string, + contractAddress: ContractAddr, + msg: string +) => { + const b64 = encode(msg); + const { data } = await axios.get( + `${endpoint}/cosmwasm/wasm/v1/contract/${contractAddress}/smart/${b64}` ); return data; }; -export const queryContractWithTime = async ( +export const queryContract = async ( endpoint: string, - contract: string + contractAddress: ContractAddr ) => { - const res = await queryContract(endpoint, contract); - const { data } = await axios.get( - `${endpoint}/cosmos/base/tendermint/v1beta1/blocks/${res.height}` + const { data } = await axios.get( + `${endpoint}/cosmwasm/wasm/v1/contract/${contractAddress}` ); + return data; +}; + +export const queryInstantiateInfo = async ( + endpoint: string, + contractAddress: ContractAddr +): Promise => { + const res = await queryContract(endpoint, contractAddress); + + // TODO: check `created` field for contracts created with proposals + let createdHeight; + let createdTime; + if (res.contract_info.created) { + const { data } = await axios.get( + `${endpoint}/cosmos/base/tendermint/v1beta1/blocks/${res.contract_info.created.block_height}` + ); + createdHeight = res.contract_info.created.block_height; + createdTime = new Date(data.block.header.time); + } else { + // TODO: revisit default value + createdHeight = 0; + createdTime = new Date(0); + } return { - address: res.result.address, - instantiator: res.result.creator, - label: res.result.label, - created: new Date(data.block.header.time), + contractAddress: res.address, + codeId: res.contract_info.code_id, + instantiator: res.contract_info.creator, + admin: res.contract_info.admin, + label: res.contract_info.label, + createdHeight, + createdTime, + ibcPortId: res.contract_info.ibc_port_id, + raw: res, }; }; -export const getCodeIdInfo = async (endpoint: string, id: number) => { - const { data } = await axios.get(`${endpoint}/cosmwasm/wasm/v1/code/${id}`); +export const queryContractBalances = async ( + endpoint: string, + contractAddress: ContractAddr +) => { + const { data } = await axios.get( + `${endpoint}/cosmos/bank/v1beta1/balances/${contractAddress}?pagination.limit=0` + ); return data; }; + +export const queryPublicInfo = async ( + chainName: string | undefined, + chainId: string | undefined, + contractAddress: ContractAddr +): Promise => { + if (!chainName || !chainId) return undefined; + return axios + .get( + `https://cosmos-registry.alleslabs.dev/data/${chainName}/${chainId}/contracts.json` + ) + .then(({ data }) => { + const publicInfo = data.find((info) => info.address === contractAddress); + return publicInfo + ? { ...publicInfo, contractAddress: publicInfo.address } + : undefined; + }); +}; diff --git a/src/lib/services/contractService.ts b/src/lib/services/contractService.ts index 28dd57826..397387133 100644 --- a/src/lib/services/contractService.ts +++ b/src/lib/services/contractService.ts @@ -6,11 +6,19 @@ import { indexerGraphClient } from "lib/data/graphql"; import { getInstantiatedListByUserQueryDocument, getInstantiatedCountByUserQueryDocument, + getInstantiateDetailByContractQueryDocument, } from "lib/data/queries"; import type { ContractInfo } from "lib/stores/contract"; +import type { ContractAddr, HumanAddr } from "lib/types"; +import { parseTxHash } from "lib/utils/parser"; + +interface InstantiateDetail { + initMsg: string; + initTxHash?: string; +} export const useInstantiatedCountByUserQuery = ( - walletAddr: string | undefined + walletAddr: HumanAddr | undefined ): UseQueryResult => { const queryFn = useCallback(async () => { if (!walletAddr) return undefined; @@ -30,7 +38,7 @@ export const useInstantiatedCountByUserQuery = ( }; export const useInstantiatedListByUserQuery = ( - walletAddr: string | undefined + walletAddr: HumanAddr | undefined ): UseQueryResult => { const queryFn = useCallback(async () => { if (!walletAddr) return undefined; @@ -41,7 +49,7 @@ export const useInstantiatedListByUserQuery = ( }) .then(({ contracts }) => contracts.map((contract) => ({ - address: contract.address, + contractAddress: contract.address as ContractAddr, instantiator: walletAddr, label: contract.label, created: new Date(`${contract.transaction?.block?.timestamp}Z`), @@ -55,3 +63,25 @@ export const useInstantiatedListByUserQuery = ( enabled: !!walletAddr, }); }; + +export const useInstantiateDetailByContractQuery = ( + contractAddress: ContractAddr +): UseQueryResult => { + const queryFn = useCallback(async () => { + return indexerGraphClient + .request(getInstantiateDetailByContractQueryDocument, { contractAddress }) + .then(({ contracts_by_pk }) => ({ + // TODO: revisit undefined after backend remove nullable + initMsg: contracts_by_pk?.init_msg ?? "{}", + initTxHash: parseTxHash(contracts_by_pk?.transaction?.hash), + })); + }, [contractAddress]); + + return useQuery( + ["instantiate_detail_by_contract", contractAddress], + queryFn, + { + keepPreviousData: true, + } + ); +}; diff --git a/src/lib/stores/contract.ts b/src/lib/stores/contract.ts index d8ae85eef..f54ad0c20 100644 --- a/src/lib/stores/contract.ts +++ b/src/lib/stores/contract.ts @@ -2,11 +2,11 @@ import { makeAutoObservable } from "mobx"; import { isHydrated, makePersistable } from "mobx-persist-store"; import { INSTANTIATED_LIST_NAME, SAVED_LIST_NAME } from "lib/data"; -import type { Option, Dict } from "lib/types"; +import type { Option, Dict, ContractAddr } from "lib/types"; import { formatSlugName } from "lib/utils"; export interface ContractInfo { - address: string; + contractAddress: ContractAddr; instantiator: string; label: string; created: Date; @@ -19,7 +19,7 @@ export interface ContractInfo { interface ContractList { name: string; slug: string; - contracts: string[]; + contracts: ContractAddr[]; lastUpdated: Date; isInfoEditable: boolean; isContractRemovable: boolean; @@ -47,7 +47,7 @@ export interface Activity { type: "query" | "execute"; action: string; sender: string | undefined; - contractAddress: string; + contractAddress: ContractAddr; msg: string; // base64 timestamp: Date; } @@ -127,24 +127,24 @@ export class ContractStore { return contractListByUserKey.map((contractListInfo) => ({ ...contractListInfo, - contracts: contractListInfo.contracts.map((contractAddr) => { + contracts: contractListInfo.contracts.map((contractAddress) => { if (!contractInfoByUserKey) return { - address: contractAddr, + contractAddress, instantiator: "TODO", label: "TODO", created: new Date(0), }; - const contractInfo = contractInfoByUserKey[contractAddr]; + const contractInfo = contractInfoByUserKey[contractAddress]; return { ...contractInfo }; }), })); } - getContractInfo(address: string): ContractInfo | undefined { - return this.contractInfo[this.userKey]?.[address]; + getContractInfo(contractAddress: string): ContractInfo | undefined { + return this.contractInfo[this.userKey]?.[contractAddress]; } isContractListExist(userKey: string, name: string): boolean { @@ -229,7 +229,7 @@ export class ContractStore { updateContractInfo( userKey: string, - contractAddr: string, + contractAddress: ContractAddr, instantiator: string, label: string, created: Date, @@ -238,8 +238,8 @@ export class ContractStore { tags?: string[], lists?: Option[] ) { - const contractInfo = this.contractInfo[userKey]?.[contractAddr] ?? { - address: contractAddr, + const contractInfo = this.contractInfo[userKey]?.[contractAddress] ?? { + contractAddress, instantiator, label, created, @@ -252,13 +252,18 @@ export class ContractStore { ? description.trim() : undefined; if (tags !== undefined) { - this.updateAllTags(userKey, contractAddr, contractInfo.tags ?? [], tags); + this.updateAllTags( + userKey, + contractAddress, + contractInfo.tags ?? [], + tags + ); contractInfo.tags = tags.length ? tags : undefined; } if (lists !== undefined) { this.updateContractInAllLists( userKey, - contractAddr, + contractAddress, contractInfo.lists ?? [], lists ); @@ -267,13 +272,13 @@ export class ContractStore { this.contractInfo[userKey] = { ...this.contractInfo[userKey], - [contractAddr]: contractInfo, + [contractAddress]: contractInfo, }; } private updateAllTags( userKey: string, - contractAddr: string, + contractAddress: string, oldTags: string[], newTags: string[] ) { @@ -283,7 +288,7 @@ export class ContractStore { removedTags.forEach((oldTag) => { const tagInfo = tags.get(oldTag); if (tagInfo) { - tagInfo.delete(contractAddr); + tagInfo.delete(contractAddress); if (tagInfo.size === 0) tags.delete(oldTag); else tags.set(oldTag, tagInfo); } @@ -293,9 +298,9 @@ export class ContractStore { addedTags.forEach((newTag) => { const tagInfo = tags.get(newTag); if (!tagInfo) { - tags.set(newTag, new Set([contractAddr])); + tags.set(newTag, new Set([contractAddress])); } else { - tags.set(newTag, tagInfo.add(contractAddr)); + tags.set(newTag, tagInfo.add(contractAddress)); } }); @@ -304,7 +309,7 @@ export class ContractStore { private updateContractInAllLists( userKey: string, - contractAddr: string, + contractAddress: ContractAddr, oldLists: Option[], newLists: Option[] ) { @@ -312,42 +317,42 @@ export class ContractStore { newLists.every((newList) => newList.value !== oldList.value) ); removedLists.forEach((slug) => { - this.removeContractFromList(userKey, slug.value, contractAddr); + this.removeContractFromList(userKey, slug.value, contractAddress); }); const addedLists = newLists.filter((newList) => oldLists.every((oldList) => oldList.value !== newList.value) ); addedLists.forEach((slug) => { - this.addContractToList(userKey, slug.value, contractAddr); + this.addContractToList(userKey, slug.value, contractAddress); }); } private addContractToList( userKey: string, slug: string, - contractAddr: string + contractAddress: ContractAddr ) { const list = this.getContractList(userKey).find( (each) => each.slug === slug ); if (!list) return; - list.contracts = Array.from(new Set(list.contracts).add(contractAddr)); + list.contracts = Array.from(new Set(list.contracts).add(contractAddress)); list.lastUpdated = new Date(); } private removeContractFromList( userKey: string, slug: string, - contractAddr: string + contractAddress: string ) { const list = this.getContractList(userKey).find( (each) => each.slug === slug ); if (!list) return; - list.contracts = list.contracts.filter((addr) => addr !== contractAddr); + list.contracts = list.contracts.filter((addr) => addr !== contractAddress); list.lastUpdated = new Date(); } diff --git a/src/lib/utils/parser.ts b/src/lib/utils/parser.ts new file mode 100644 index 000000000..12c7eb24f --- /dev/null +++ b/src/lib/utils/parser.ts @@ -0,0 +1,2 @@ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const parseTxHash = (txHash: any) => (txHash as string).substring(2); diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index efa5417e6..bed18f54f 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -20,7 +20,7 @@ import Head from "next/head"; import defaultSEOConfig from "../../next-seo.config"; import { CELATONE_CONSTANTS, - CELATONE_CONTRACT_ADDRESS, + CELATONE_APP_CONTRACT_ADDRESS, CELATONE_FALLBACK_GAS_PRICE, } from "env"; import { AppProvider } from "lib/app-provider/contexts/app"; @@ -77,7 +77,7 @@ const MyApp = ({ Component, pageProps }: AppProps) => {