From 59aaf9a9788e53b31a44cb670410c5f7b09408f1 Mon Sep 17 00:00:00 2001 From: songwongtp <16089160+songwongtp@users.noreply.github.com> Date: Mon, 23 Jan 2023 17:22:52 +0700 Subject: [PATCH 1/6] feat: migration step 2 --- CHANGELOG.md | 1 + src/env.ts | 1 + src/lib/app-fns/tx/migrate.tsx | 83 ++++++++++ src/lib/app-provider/tx/migrate.ts | 44 ++++++ src/lib/components/CodeSelectSection.tsx | 70 +++++++++ src/lib/data/constant.ts | 1 + src/lib/pages/instantiate/instantiate.tsx | 58 ++----- .../migrate/components/MigrateContract.tsx | 142 ++++++++++++++++-- src/lib/pages/migrate/index.tsx | 7 +- src/lib/pages/migrate/types.ts | 2 +- src/lib/types/tx/msg.ts | 11 +- 11 files changed, 354 insertions(+), 66 deletions(-) create mode 100644 src/lib/app-fns/tx/migrate.tsx create mode 100644 src/lib/app-provider/tx/migrate.ts create mode 100644 src/lib/components/CodeSelectSection.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 46093d7b5..f17321dba 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 +- [#120](https://github.com/alleslabs/celatone-frontend/pull/120) Add simulate migrate fee and the final migration step - [#108](https://github.com/alleslabs/celatone-frontend/pull/108) Add migrate options on migrate page and upload new code for migration - [#94](https://github.com/alleslabs/celatone-frontend/pull/94) Add unsupported assets in contract details page - [#72](https://github.com/alleslabs/celatone-frontend/pull/72) Fix general wording and grammar diff --git a/src/env.ts b/src/env.ts index 52da8aa09..c30ca3894 100644 --- a/src/env.ts +++ b/src/env.ts @@ -46,6 +46,7 @@ export const MSG_TYPE_URL = { [MsgType.STORE_CODE]: "/cosmwasm.wasm.v1.MsgStoreCode", [MsgType.INSTANTIATE]: "/cosmwasm.wasm.v1.MsgInstantiateContract", [MsgType.EXECUTE]: "/cosmwasm.wasm.v1.MsgExecuteContract", + [MsgType.MIGRATE]: "/cosmwasm.wasm.v1.MsgMigrateContract", }; export const CELATONE_CONSTANTS: CelatoneConstants = { diff --git a/src/lib/app-fns/tx/migrate.tsx b/src/lib/app-fns/tx/migrate.tsx new file mode 100644 index 000000000..d9acb9e77 --- /dev/null +++ b/src/lib/app-fns/tx/migrate.tsx @@ -0,0 +1,83 @@ +import { Icon } from "@chakra-ui/react"; +import type { + MigrateResult, + SigningCosmWasmClient, +} from "@cosmjs/cosmwasm-stargate"; +import type { StdFee } from "@cosmjs/stargate"; +import { pipe } from "@rx-stream/pipe"; +import { MdCheckCircle } from "react-icons/md"; +import type { Observable } from "rxjs"; + +import { ExplorerLink } from "lib/components/ExplorerLink"; +import type { ContractAddr, TxResultRendering } from "lib/types"; +import { TxStreamPhase } from "lib/types"; +import { formatUFee } from "lib/utils"; + +import { catchTxError } from "./common/catchTxError"; +import { postTx } from "./common/post"; +import { sendingTx } from "./common/sending"; + +interface MigrateTxParams { + sender: string; + contractAddress: ContractAddr; + codeId: number; + migrateMsg: object; + fee: StdFee; + client: SigningCosmWasmClient; + onTxSucceed?: (txHash: string) => void; +} + +export const migrateContractTx = ({ + sender, + contractAddress, + codeId, + migrateMsg, + fee, + client, + onTxSucceed, +}: MigrateTxParams): Observable => { + return pipe( + sendingTx(fee), + postTx({ + postFn: () => + client.migrate( + sender, + contractAddress, + codeId, + migrateMsg, + fee, + undefined + ), + }), + ({ value: txInfo }) => { + onTxSucceed?.(txInfo.transactionHash); + return { + value: null, + phase: TxStreamPhase.SUCCEED, + receipts: [ + { + title: "Tx Hash", + value: txInfo.transactionHash, + html: ( + + ), + }, + { + title: "Tx Fee", + value: `${formatUFee( + txInfo.events.find((e) => e.type === "tx")?.attributes[0].value ?? + "0u" + )}`, + }, + ], + receiptInfo: { + header: "Migration Completed", + headerIcon: ( + + ), + }, + actionVariant: "migrate", + } as TxResultRendering; + } + )().pipe(catchTxError()); +}; diff --git a/src/lib/app-provider/tx/migrate.ts b/src/lib/app-provider/tx/migrate.ts new file mode 100644 index 000000000..78a6d45dc --- /dev/null +++ b/src/lib/app-provider/tx/migrate.ts @@ -0,0 +1,44 @@ +import type { StdFee } from "@cosmjs/stargate"; +import { useWallet } from "@cosmos-kit/react"; +import { useCallback } from "react"; + +import { migrateContractTx } from "lib/app-fns/tx/migrate"; +import type { ContractAddr, Option } from "lib/types"; + +export interface MigrateStreamParams { + contractAddress: ContractAddr; + codeId: number; + migrateMsg: object; + estimatedFee: Option; + onTxSucceed?: (txHash: string) => void; +} + +export const useMigrateTx = () => { + const { address, getCosmWasmClient } = useWallet(); + + return useCallback( + async ({ + contractAddress, + codeId, + migrateMsg, + estimatedFee, + onTxSucceed, + }: MigrateStreamParams) => { + const client = await getCosmWasmClient(); + if (!address || !client) + throw new Error("Please check your wallet connection."); + if (!estimatedFee) return null; + + return migrateContractTx({ + sender: address, + contractAddress, + codeId, + migrateMsg, + fee: estimatedFee, + client, + onTxSucceed, + }); + }, + [address, getCosmWasmClient] + ); +}; diff --git a/src/lib/components/CodeSelectSection.tsx b/src/lib/components/CodeSelectSection.tsx new file mode 100644 index 000000000..f6a42a1e4 --- /dev/null +++ b/src/lib/components/CodeSelectSection.tsx @@ -0,0 +1,70 @@ +import { Flex, Radio, RadioGroup } from "@chakra-ui/react"; +import { useState } from "react"; +import type { Control, FieldPath, FieldValues } from "react-hook-form"; + +import { CodeSelect } from "lib/pages/instantiate/component"; +import type { Option } from "lib/types"; + +import { ControllerInput } from "./forms"; + +interface CodeSelectSectionProps { + codeId: string; + name: FieldPath; + control: Control; + error: Option; + onCodeSelect: (codeId: string) => void; +} + +export const CodeSelectSection = ({ + codeId, + name, + control, + error, + onCodeSelect, +}: CodeSelectSectionProps) => { + const [method, setMethod] = useState<"select-existing" | "fill-manually">( + "select-existing" + ); + + return ( + <> + + setMethod(nextVal) + } + value={method} + w="100%" + > + + + Select from your code + + + Fill Code ID manually + + + +
+ {method === "select-existing" ? ( + + ) : ( + + )} + + + ); +}; diff --git a/src/lib/data/constant.ts b/src/lib/data/constant.ts index 530cc25e1..a748cd590 100644 --- a/src/lib/data/constant.ts +++ b/src/lib/data/constant.ts @@ -62,6 +62,7 @@ export const typeUrlDict = { [MsgType.STORE_CODE]: "/cosmwasm.wasm.v1.MsgStoreCode", [MsgType.INSTANTIATE]: "/cosmwasm.wasm.v1.MsgInstantiateContract", [MsgType.EXECUTE]: "/cosmwasm.wasm.v1.MsgExecuteContract", + [MsgType.MIGRATE]: "/cosmwasm.wasm.v1.MsgMigrateContract", }; export const DEFAULT_RPC_ERROR = "Invalid format, or Something went wrong"; diff --git a/src/lib/pages/instantiate/instantiate.tsx b/src/lib/pages/instantiate/instantiate.tsx index 373d51fb0..fd0ee3b92 100644 --- a/src/lib/pages/instantiate/instantiate.tsx +++ b/src/lib/pages/instantiate/instantiate.tsx @@ -1,11 +1,4 @@ -import { - Button, - Flex, - Heading, - Radio, - RadioGroup, - Text, -} from "@chakra-ui/react"; +import { Button, Heading, Text } from "@chakra-ui/react"; import type { InstantiateResult } from "@cosmjs/cosmwasm-stargate"; import { useWallet } from "@cosmos-kit/react"; import Long from "long"; @@ -19,6 +12,7 @@ import { useSimulateFee, } from "lib/app-provider"; import { useInstantiateTx } from "lib/app-provider/tx/instantiate"; +import { CodeSelectSection } from "lib/components/CodeSelectSection"; import { ControllerInput } from "lib/components/forms"; import { AssetInput } from "lib/components/forms/AssetInput"; import JsonInput from "lib/components/json/JsonInput"; @@ -35,7 +29,7 @@ import { microfy, } from "lib/utils"; -import { CodeSelect, FailedModal, Footer } from "./component"; +import { FailedModal, Footer } from "./component"; import type { InstantiateRedoMsg } from "./types"; interface InstantiatePageProps { @@ -59,9 +53,6 @@ const Instantiate = ({ onComplete }: InstantiatePageProps) => { // ------------------------------------------// // ------------------STATES------------------// // ------------------------------------------// - const [method, setMethod] = useState<"select-existing" | "fill-manually">( - "select-existing" - ); const [simulating, setSimulating] = useState(false); // ------------------------------------------// @@ -204,42 +195,15 @@ const Instantiate = ({ onComplete }: InstantiatePageProps) => { Instantiate new contract - - setMethod(nextVal) - } - value={method} - w="100%" - > - - - Select from your code - - - Fill Code ID manually - - - + + setValue("codeId", code)} + codeId={codeId} + />
- {method === "select-existing" ? ( - setValue("codeId", code)} - codeId={codeId} - /> - ) : ( - - )} ; + codeIdParam: string; handleBack: () => void; } export const MigrateContract = ({ - isAdmin, contractAddress, - codeId, + codeIdParam, handleBack, }: MigrateContractProps) => { + const { address } = useWallet(); + const { broadcast } = useTxBroadcast(); + const migrateTx = useMigrateTx(); + const fabricateFee = useFabricateFee(); + + const { + control, + watch, + setValue, + formState: { errors }, + } = useForm({ + defaultValues: { codeId: codeIdParam, migrateMsg: "{}" }, + mode: "all", + }); + const { codeId, migrateMsg } = watch(); + const [composedTxMsg, setComposedTxMsg] = useState([]); + const [estimatedFee, setEstimatedFee] = useState(); + const [simulateError, setSimulateError] = useState(""); + const [processing, setProcessing] = useState(false); + + const enableMigrate = !!( + codeId.length && + migrateMsg.trim().length && + jsonValidate(migrateMsg) === null && + address + ); + + const { isFetching } = useSimulateFeeQuery({ + enabled: composedTxMsg.length > 0, + messages: composedTxMsg, + onSuccess: (gasRes) => { + if (gasRes) setEstimatedFee(fabricateFee(gasRes)); + else setEstimatedFee(undefined); + }, + onError: (e) => { + setSimulateError(e.message); + setEstimatedFee(undefined); + }, + }); + + const proceed = useCallback(async () => { + const stream = await migrateTx({ + contractAddress, + codeId: Number(codeId), + migrateMsg: JSON.parse(migrateMsg), + estimatedFee, + }); + + if (stream) { + setProcessing(true); + broadcast(stream); + } + }, [migrateTx, contractAddress, codeId, migrateMsg, estimatedFee, broadcast]); + + useEffect(() => { + if (enableMigrate) { + setSimulateError(""); + const composedMsg = composeMsg(MsgType.MIGRATE, { + sender: address as HumanAddr, + contract: contractAddress as ContractAddr, + codeId: Long.fromString(codeId), + msg: Buffer.from(migrateMsg), + }); + const timeoutId = setTimeout(() => { + setComposedTxMsg([composedMsg]); + }, 1000); + return () => clearTimeout(timeoutId); + } + return () => {}; + }, [address, codeId, contractAddress, enableMigrate, migrateMsg]); + return ( <> - {isAdmin} - {contractAddress} - {codeId} + + To Code ID + + setValue("codeId", code)} + codeId={codeId} + /> + setValue("migrateMsg", msg)} + height="120px" + /> + {simulateError && ( + + + + {simulateError} + + + )} + + Transaction Fee:{" "} + + - {/* */} + Migrate + ); diff --git a/src/lib/pages/migrate/index.tsx b/src/lib/pages/migrate/index.tsx index e7438dc47..a2a24afe8 100644 --- a/src/lib/pages/migrate/index.tsx +++ b/src/lib/pages/migrate/index.tsx @@ -24,7 +24,7 @@ const defaultValues: MigratePageState = { migrateStep: 1.1, contractAddress: "" as ContractAddr, admin: undefined, - codeId: undefined, + codeId: "", }; const Migrate = () => { @@ -80,7 +80,7 @@ const Migrate = () => { const contractAddressParam = getFirstQueryParam( router.query.contract ) as ContractAddr; - const codeIdParam = Number(getFirstQueryParam(router.query["code-id"])); + const codeIdParam = getFirstQueryParam(router.query["code-id"]); setValue("contractAddress", contractAddressParam); setValue("codeId", codeIdParam); @@ -92,9 +92,8 @@ const Migrate = () => { case 2: return ( ); diff --git a/src/lib/pages/migrate/types.ts b/src/lib/pages/migrate/types.ts index 63d3ebb90..903ffbd39 100644 --- a/src/lib/pages/migrate/types.ts +++ b/src/lib/pages/migrate/types.ts @@ -6,5 +6,5 @@ export interface MigratePageState { migrateStep: MigrateStep; contractAddress: ContractAddr; admin: Option; - codeId: Option; + codeId: string; } diff --git a/src/lib/types/tx/msg.ts b/src/lib/types/tx/msg.ts index 8b2f5e491..8f797eef9 100644 --- a/src/lib/types/tx/msg.ts +++ b/src/lib/types/tx/msg.ts @@ -6,6 +6,7 @@ export enum MsgType { STORE_CODE = "STORE_CODE", INSTANTIATE = "INSTANTIATE", EXECUTE = "EXECUTE", + MIGRATE = "MIGRATE", } export enum AccessType { @@ -43,10 +44,18 @@ export interface MsgExecuteContract { funds: Coin[]; } +export interface MsgMigrateContract { + sender: HumanAddr; + contract: ContractAddr; + codeId: Long; + msg: Uint8Array; +} + export type TxMessage = | MsgStoreCode | MsgInstantiateContract - | MsgExecuteContract; + | MsgExecuteContract + | MsgMigrateContract; export interface ComposedMsg { typeUrl: string; From 97541079867ab69d7e32d97b9a9456cddb2bf7fc Mon Sep 17 00:00:00 2001 From: songwongtp <16089160+songwongtp@users.noreply.github.com> Date: Tue, 24 Jan 2023 17:34:27 +0700 Subject: [PATCH 2/6] feat: add validate code id --- src/lib/components/CodeSelectSection.tsx | 8 +- src/lib/components/forms/ControllerInput.tsx | 28 ++++--- .../component/code-select/CodeSelect.tsx | 78 +++++++++++-------- src/lib/pages/instantiate/instantiate.tsx | 57 +++++++++++++- .../migrate/components/MigrateContract.tsx | 66 +++++++++++++++- 5 files changed, 186 insertions(+), 51 deletions(-) diff --git a/src/lib/components/CodeSelectSection.tsx b/src/lib/components/CodeSelectSection.tsx index f6a42a1e4..f72a5a7bd 100644 --- a/src/lib/components/CodeSelectSection.tsx +++ b/src/lib/components/CodeSelectSection.tsx @@ -5,6 +5,7 @@ import type { Control, FieldPath, FieldValues } from "react-hook-form"; import { CodeSelect } from "lib/pages/instantiate/component"; import type { Option } from "lib/types"; +import type { FormStatus } from "./forms"; import { ControllerInput } from "./forms"; interface CodeSelectSectionProps { @@ -13,6 +14,7 @@ interface CodeSelectSectionProps { control: Control; error: Option; onCodeSelect: (codeId: string) => void; + status: FormStatus; } export const CodeSelectSection = ({ @@ -21,6 +23,7 @@ export const CodeSelectSection = ({ control, error, onCodeSelect, + status, }: CodeSelectSectionProps) => { const [method, setMethod] = useState<"select-existing" | "fill-manually">( "select-existing" @@ -51,12 +54,15 @@ export const CodeSelectSection = ({ mb="32px" onCodeSelect={onCodeSelect} codeId={codeId} + status={status} /> ) : ( @@ -58,7 +60,7 @@ export const ControllerInput = ({ return ( ({ {label} )} - - {/* TODO: add status */} + + + + {status && getStatusIcon(status.state)} + + {isError ? ( {error} ) : ( diff --git a/src/lib/pages/instantiate/component/code-select/CodeSelect.tsx b/src/lib/pages/instantiate/component/code-select/CodeSelect.tsx index 7114012c9..73e7e2f7b 100644 --- a/src/lib/pages/instantiate/component/code-select/CodeSelect.tsx +++ b/src/lib/pages/instantiate/component/code-select/CodeSelect.tsx @@ -1,6 +1,7 @@ import type { FlexProps } from "@chakra-ui/react"; import { Flex, Text } from "@chakra-ui/react"; +import type { FormStatus } from "lib/components/forms"; import { UploadIcon } from "lib/components/icon/UploadIcon"; import { useCodeStore } from "lib/hooks/store"; import { useUserKey } from "lib/hooks/useUserKey"; @@ -10,53 +11,64 @@ import { CodeSelectModalButton } from "./CodeSelectModalButton"; interface CodeSelectProps extends Omit { onCodeSelect: (code: string) => void; codeId: string; + status: FormStatus; } export const CodeSelect = ({ onCodeSelect, codeId, + status, ...componentProps }: CodeSelectProps) => { const { codeInfo } = useCodeStore(); const userKey = useUserKey(); const description = codeInfo?.[userKey]?.[Number(codeId)]?.description; + + const isError = status.state === "error"; return ( - - - {codeId ? ( - - - {description ?? "No description"} - - - Code ID {codeId} + + + + {codeId ? ( + + + {description ?? "No description"} + + + Code ID {codeId} + + + ) : ( + + Please select code - - ) : ( - - Please select code + )} + + + {isError && ( + + {status.message} )} - - ); }; diff --git a/src/lib/pages/instantiate/instantiate.tsx b/src/lib/pages/instantiate/instantiate.tsx index fd0ee3b92..876ed0aa7 100644 --- a/src/lib/pages/instantiate/instantiate.tsx +++ b/src/lib/pages/instantiate/instantiate.tsx @@ -1,6 +1,7 @@ import { Button, Heading, Text } from "@chakra-ui/react"; import type { InstantiateResult } from "@cosmjs/cosmwasm-stargate"; import { useWallet } from "@cosmos-kit/react"; +import { useQuery } from "@tanstack/react-query"; import Long from "long"; import { useRouter } from "next/router"; import { useCallback, useEffect, useMemo, useState } from "react"; @@ -13,12 +14,15 @@ import { } from "lib/app-provider"; import { useInstantiateTx } from "lib/app-provider/tx/instantiate"; import { CodeSelectSection } from "lib/components/CodeSelectSection"; +import type { FormStatus } from "lib/components/forms"; import { ControllerInput } from "lib/components/forms"; import { AssetInput } from "lib/components/forms/AssetInput"; import JsonInput from "lib/components/json/JsonInput"; import { Stepper } from "lib/components/stepper"; import WasmPageContainer from "lib/components/WasmPageContainer"; +import { useEndpoint } from "lib/hooks"; import { useTxBroadcast } from "lib/providers/tx-broadcast"; +import { getCodeIdInfo } from "lib/services/code"; import type { HumanAddr, Token, U } from "lib/types"; import { MsgType } from "lib/types"; import { @@ -44,6 +48,7 @@ const Instantiate = ({ onComplete }: InstantiatePageProps) => { const msgQuery = (router.query.msg as string) ?? ""; const codeIdQuery = (router.query["code-id"] as string) ?? ""; const { address = "" } = useWallet(); + const endpoint = useEndpoint(); const postInstantiateTx = useInstantiateTx(); const { simulate } = useSimulateFee(); const fabricateFee = useFabricateFee(); @@ -86,14 +91,19 @@ const Instantiate = ({ onComplete }: InstantiatePageProps) => { initMsg: watchInitMsg, simulateError, } = watch(); + const [status, setStatus] = useState({ state: "init" }); const selectedAssets = watchAssets.map((asset) => asset.denom); const disableInstantiate = useMemo(() => { return ( - !codeId || !address || !!jsonValidate(watchInitMsg) || !!formErrors.label + !codeId || + !address || + !!jsonValidate(watchInitMsg) || + !!formErrors.label || + status.state !== "success" ); - }, [codeId, address, watchInitMsg, formErrors.label]); + }, [codeId, address, watchInitMsg, formErrors.label, status.state]); const assetOptions = useMemo( () => @@ -105,6 +115,35 @@ const Instantiate = ({ onComplete }: InstantiatePageProps) => { [nativeTokensInfo, selectedAssets] ); + const { refetch } = useQuery( + ["query", endpoint, codeId], + async () => getCodeIdInfo(endpoint, Number(codeId)), + { + enabled: !!address && !!codeId.length, + retry: false, + cacheTime: 0, + onSuccess(data) { + const permission = data.code_info.instantiate_permission; + if ( + address && + (permission.permission === "Everybody" || + permission.addresses.includes(address) || + permission.address === address) + ) + setStatus({ state: "success" }); + else { + setStatus({ + state: "error", + message: "You can migrate to this code through proposal only", + }); + } + }, + onError() { + setStatus({ state: "error", message: "This code ID does not exist" }); + }, + } + ); + // ------------------------------------------// // ----------------FUNCTIONS-----------------// // ------------------------------------------// @@ -159,6 +198,19 @@ const Instantiate = ({ onComplete }: InstantiatePageProps) => { // ------------------------------------------// // --------------SIDE EFFECTS----------------// // ------------------------------------------// + useEffect(() => { + if (codeId.length) { + setStatus({ state: "loading" }); + const timer = setTimeout(() => { + refetch(); + }, 500); + return () => clearTimeout(timer); + } + setStatus({ state: "init" }); + + return () => {}; + }, [address, codeId, refetch]); + useEffect(() => { if (codeIdQuery) setValue("codeId", codeIdQuery); if (msgQuery) { @@ -199,6 +251,7 @@ const Instantiate = ({ onComplete }: InstantiatePageProps) => { setValue("codeId", code)} codeId={codeId} diff --git a/src/lib/pages/migrate/components/MigrateContract.tsx b/src/lib/pages/migrate/components/MigrateContract.tsx index 72ad4cdb6..94f9cea90 100644 --- a/src/lib/pages/migrate/components/MigrateContract.tsx +++ b/src/lib/pages/migrate/components/MigrateContract.tsx @@ -1,6 +1,7 @@ import { Button, Flex, Heading, Icon, Text } from "@chakra-ui/react"; import type { StdFee } from "@cosmjs/stargate"; import { useWallet } from "@cosmos-kit/react"; +import { useQuery } from "@tanstack/react-query"; import Long from "long"; import { useCallback, useEffect, useState } from "react"; import { useForm } from "react-hook-form"; @@ -11,8 +12,11 @@ import { useFabricateFee, useSimulateFeeQuery } from "lib/app-provider"; import { useMigrateTx } from "lib/app-provider/tx/migrate"; import { CodeSelectSection } from "lib/components/CodeSelectSection"; import { EstimatedFeeRender } from "lib/components/EstimatedFeeRender"; +import type { FormStatus } from "lib/components/forms"; import JsonInput from "lib/components/json/JsonInput"; +import { useEndpoint } from "lib/hooks"; import { useTxBroadcast } from "lib/providers/tx-broadcast"; +import { getCodeIdInfo } from "lib/services/code"; import type { ComposedMsg, ContractAddr, HumanAddr } from "lib/types"; import { MsgType } from "lib/types"; import { composeMsg, jsonValidate } from "lib/utils"; @@ -30,6 +34,7 @@ export const MigrateContract = ({ }: MigrateContractProps) => { const { address } = useWallet(); const { broadcast } = useTxBroadcast(); + const endpoint = useEndpoint(); const migrateTx = useMigrateTx(); const fabricateFee = useFabricateFee(); @@ -43,19 +48,21 @@ export const MigrateContract = ({ mode: "all", }); const { codeId, migrateMsg } = watch(); + const [status, setStatus] = useState({ state: "init" }); const [composedTxMsg, setComposedTxMsg] = useState([]); const [estimatedFee, setEstimatedFee] = useState(); const [simulateError, setSimulateError] = useState(""); const [processing, setProcessing] = useState(false); const enableMigrate = !!( + address && codeId.length && migrateMsg.trim().length && jsonValidate(migrateMsg) === null && - address + status.state === "success" ); - const { isFetching } = useSimulateFeeQuery({ + const { isFetching: isSimulating } = useSimulateFeeQuery({ enabled: composedTxMsg.length > 0, messages: composedTxMsg, onSuccess: (gasRes) => { @@ -68,6 +75,37 @@ export const MigrateContract = ({ }, }); + const { refetch } = useQuery( + ["query", endpoint, codeId], + async () => getCodeIdInfo(endpoint, Number(codeId)), + { + enabled: !!address && !!codeId.length, + retry: false, + cacheTime: 0, + onSuccess(data) { + const permission = data.code_info.instantiate_permission; + if ( + address && + (permission.permission === "Everybody" || + permission.addresses.includes(address) || + permission.address === address) + ) + setStatus({ state: "success" }); + else { + setStatus({ + state: "error", + message: "You can migrate to this code through proposal only", + }); + setSimulateError(""); + } + }, + onError() { + setStatus({ state: "error", message: "This code ID does not exist" }); + setSimulateError(""); + }, + } + ); + const proceed = useCallback(async () => { const stream = await migrateTx({ contractAddress, @@ -82,6 +120,19 @@ export const MigrateContract = ({ } }, [migrateTx, contractAddress, codeId, migrateMsg, estimatedFee, broadcast]); + useEffect(() => { + if (codeId.length) { + setStatus({ state: "loading" }); + const timer = setTimeout(() => { + refetch(); + }, 500); + return () => clearTimeout(timer); + } + setStatus({ state: "init" }); + + return () => {}; + }, [address, codeId, refetch]); + useEffect(() => { if (enableMigrate) { setSimulateError(""); @@ -107,10 +158,14 @@ export const MigrateContract = ({ setValue("codeId", code)} codeId={codeId} /> + + Migrate Message + setValue("migrateMsg", msg)} @@ -133,7 +188,10 @@ export const MigrateContract = ({ gap="4px" > Transaction Fee:{" "} - + ); + case "migrate": + return ( + <> + + + + ); case "rejected": case "resend": return ( diff --git a/src/lib/pages/migrate/components/MigrateContract.tsx b/src/lib/pages/migrate/components/MigrateContract.tsx index bb05c813e..3686d5c39 100644 --- a/src/lib/pages/migrate/components/MigrateContract.tsx +++ b/src/lib/pages/migrate/components/MigrateContract.tsx @@ -8,11 +8,7 @@ import { useForm } from "react-hook-form"; import { FiChevronLeft } from "react-icons/fi"; import { IoIosWarning } from "react-icons/io"; -import { - useFabricateFee, - useInternalNavigate, - useSimulateFeeQuery, -} from "lib/app-provider"; +import { useFabricateFee, useSimulateFeeQuery } from "lib/app-provider"; import { useMigrateTx } from "lib/app-provider/tx/migrate"; import { CodeSelectSection } from "lib/components/CodeSelectSection"; import { EstimatedFeeRender } from "lib/components/EstimatedFeeRender"; @@ -39,7 +35,6 @@ export const MigrateContract = ({ const { address } = useWallet(); const { broadcast } = useTxBroadcast(); const endpoint = useLCDEndpoint(); - const navigate = useInternalNavigate(); const migrateTx = useMigrateTx(); const fabricateFee = useFabricateFee(); @@ -117,10 +112,7 @@ export const MigrateContract = ({ codeId: Number(codeId), migrateMsg: JSON.parse(migrateMsg), estimatedFee, - onTxSucceed: () => { - setProcessing(false); - navigate({ pathname: `/contract/${contractAddress}` }); - }, + onTxSucceed: () => setProcessing(false), onTxFailed: () => setProcessing(false), }); @@ -128,15 +120,7 @@ export const MigrateContract = ({ setProcessing(true); broadcast(stream); } - }, [ - migrateTx, - contractAddress, - codeId, - migrateMsg, - estimatedFee, - navigate, - broadcast, - ]); + }, [migrateTx, contractAddress, codeId, migrateMsg, estimatedFee, broadcast]); useEffect(() => { if (codeId.length) {