Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add clear admin menuitem #98

Merged
merged 13 commits into from
Jan 26, 2023
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Features

- [#98](https://github.com/alleslabs/celatone-frontend/pull/98) Add migrate, update admin, clear admin menu on contract list and detail
- [#121](https://github.com/alleslabs/celatone-frontend/pull/121) Fix code snippet for query axios
- [#102](https://github.com/alleslabs/celatone-frontend/pull/102) Add quick menu in overview and add highlighted in left sidebar
- [#125](https://github.com/alleslabs/celatone-frontend/pull/125) Add connect wallet alert in instantiate page
- [#126](https://github.com/alleslabs/celatone-frontend/pull/126) Add port id copier for IBC port id
Expand Down
72 changes: 72 additions & 0 deletions src/lib/app-fns/tx/clearAdmin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Icon } from "@chakra-ui/react";
import type {
SigningCosmWasmClient,
ChangeAdminResult,
} 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 { TxStreamPhase } from "lib/types";
import type { TxResultRendering, ContractAddr } from "lib/types";
import { formatUFee } from "lib/utils";

import { catchTxError } from "./common/catchTxError";
import { postTx } from "./common/post";
import { sendingTx } from "./common/sending";

interface ClearAdminTxParams {
address: string;
contractAddress: ContractAddr;
fee: StdFee;
memo?: string;
client: SigningCosmWasmClient;
onTxSucceed?: (txHash: string) => void;
}

export const clearAdminTx = ({
address,
contractAddress,
fee,
memo,
client,
onTxSucceed,
}: ClearAdminTxParams): Observable<TxResultRendering> => {
return pipe(
sendingTx(fee),
postTx<ChangeAdminResult>({
postFn: () => client.clearAdmin(address, contractAddress, fee, memo),
}),
({ value: txInfo }) => {
onTxSucceed?.(txInfo.transactionHash);
return {
value: null,
phase: TxStreamPhase.SUCCEED,
receipts: [
{
title: "Tx Hash",
value: txInfo.transactionHash,
html: (
<ExplorerLink type="tx_hash" value={txInfo.transactionHash} />
),
},
{
title: "Tx Fee",
value: `${formatUFee(
txInfo.events.find((e) => e.type === "tx")?.attributes[0].value ??
"0u"
)}`,
},
],
receiptInfo: {
header: "Transaction Complete",
headerIcon: (
<Icon as={MdCheckCircle} color="success.main" boxSize={6} />
),
},
} as TxResultRendering;
}
)().pipe(catchTxError());
};
34 changes: 34 additions & 0 deletions src/lib/app-provider/tx/clearAdmin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useWallet } from "@cosmos-kit/react";
import { useCallback } from "react";

import { useFabricateFee } from "../hooks";
import { clearAdminTx } from "lib/app-fns/tx/clearAdmin";
import { CLEAR_ADMIN_GAS } from "lib/data";
import type { ContractAddr } from "lib/types";

export interface ClearAdminStreamParams {
onTxSucceed?: (txHash: string) => void;
}

export const useClearAdminTx = (contractAddress: ContractAddr) => {
const { address, getCosmWasmClient } = useWallet();
const fabricateFee = useFabricateFee();
const clearAdminFee = fabricateFee(CLEAR_ADMIN_GAS);

return useCallback(
async ({ onTxSucceed }: ClearAdminStreamParams) => {
const client = await getCosmWasmClient();
if (!address || !client)
throw new Error("Please check your wallet connection.");

return clearAdminTx({
address,
contractAddress,
fee: clearAdminFee,
client,
onTxSucceed,
});
},
[address, clearAdminFee, contractAddress, getCosmWasmClient]
);
};
3 changes: 3 additions & 0 deletions src/lib/app-provider/tx/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
export * from "./upload";
export * from "./resend";
export * from "./clearAdmin";
export * from "./execute";
export * from "./instantiate";
102 changes: 102 additions & 0 deletions src/lib/components/button/AdminButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import {
Button,
chakra,
Icon,
Menu,
MenuButton,
MenuItem,
MenuList,
Tooltip,
} from "@chakra-ui/react";
import { useWallet } from "@cosmos-kit/react";
import {
MdKeyboardArrowDown,
MdPerson,
MdPersonRemove,
MdReadMore,
} from "react-icons/md";

import { ClearAdminContract } from "../modal/contract/ClearAdminContract";
import { useInternalNavigate } from "lib/app-provider";
import type { ContractAddr, HumanAddr, Option } from "lib/types";

const StyledMenuItem = chakra(MenuItem, {
baseStyle: {
fontSize: "14px",
},
});

const StyledIcon = chakra(Icon, {
baseStyle: {
boxSize: "4",
display: "flex",
alignItems: "center",
},
});

interface AdminButtonProps {
contractAddress: ContractAddr;
admin: Option<HumanAddr | ContractAddr>;
}

export const AdminButton = ({ contractAddress, admin }: AdminButtonProps) => {
const { address } = useWallet();
const navigate = useInternalNavigate();

const isAdmin = !!address && address === admin;
return (
<Menu>
<Tooltip
hasArrow
label="You don't have admin access to this contract."
placement="top"
bg="primary.dark"
arrowSize={8}
isDisabled={isAdmin}
>
<MenuButton
variant="outline-gray"
as={Button}
isDisabled={!isAdmin}
rightIcon={<Icon as={MdKeyboardArrowDown} boxSize="18px" />}
>
Admin
</MenuButton>
</Tooltip>
<MenuList>
<StyledMenuItem
icon={<StyledIcon as={MdReadMore} color="gray.600" />}
onClick={() => {
navigate({
pathname: "/migrate",
query: { contract: contractAddress },
});
}}
>
Migrate
</StyledMenuItem>
<StyledMenuItem
icon={<StyledIcon as={MdPerson} color="gray.600" />}
onClick={() => {
navigate({
pathname: "/admin",
query: { contract: contractAddress },
});
}}
>
Update Admin
</StyledMenuItem>
<ClearAdminContract
contractAddress={contractAddress}
triggerElement={
<StyledMenuItem
icon={<StyledIcon as={MdPersonRemove} color="gray.600" />}
>
Clear Admin
</StyledMenuItem>
}
/>
</MenuList>
</Menu>
);
};
2 changes: 2 additions & 0 deletions src/lib/components/button/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from "./BackButton";
export * from "./ConnectWallet";
export * from "./InstantiateButton";
export * from "./ShowMoreButton";
export * from "./AdminButton";
11 changes: 6 additions & 5 deletions src/lib/components/modal/ActionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ export interface ActionModalProps {
children?: ReactNode;
mainBtnTitle?: string;
mainAction: () => void;
mainVariant?: string;
disabledMain?: boolean;
otherBtnTitle?: string;
otherAction?: () => void;
otherVariant?: string;
noHeaderBorder?: boolean;
noCloseButton?: boolean;
}
Expand All @@ -44,9 +46,11 @@ export function ActionModal({
children,
mainBtnTitle = "Proceed",
mainAction,
mainVariant = "primary",
disabledMain = false,
otherBtnTitle = "Cancel",
otherAction,
otherVariant = "outline-primary",
noHeaderBorder = false,
noCloseButton = false,
}: ActionModalProps) {
Expand Down Expand Up @@ -93,15 +97,12 @@ export function ActionModal({
<Button
w="200px"
onClick={handleOnMain}
variant={mainVariant}
isDisabled={disabledMain}
>
{mainBtnTitle}
</Button>
<Button
w="200px"
variant="outline-primary"
onClick={handleOnOther}
>
<Button w="200px" variant={otherVariant} onClick={handleOnOther}>
{otherBtnTitle}
</Button>
</Flex>
Expand Down
45 changes: 45 additions & 0 deletions src/lib/components/modal/contract/ClearAdminContract.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Text } from "@chakra-ui/react";
import { useCallback } from "react";
import { MdDeleteForever } from "react-icons/md";

import { ActionModal } from "../ActionModal";
import { useClearAdminTx } from "lib/app-provider";
import { useTxBroadcast } from "lib/providers/tx-broadcast";
import type { ContractAddr } from "lib/types";

interface ClearAdminContractProps {
contractAddress: ContractAddr;
triggerElement: JSX.Element;
}

export const ClearAdminContract = ({
contractAddress,
triggerElement,
}: ClearAdminContractProps) => {
const { broadcast } = useTxBroadcast();
const clearAdminTx = useClearAdminTx(contractAddress);

const proceed = useCallback(async () => {
const stream = await clearAdminTx({ onTxSucceed: () => {} });
if (stream) broadcast(stream);
}, [broadcast, clearAdminTx]);

return (
<ActionModal
title="You'll no longer have admin access"
icon={MdDeleteForever}
iconColor="error.light"
trigger={triggerElement}
mainBtnTitle="Yes, clear it"
mainAction={proceed}
mainVariant="error"
otherBtnTitle="No, keep it"
otherVariant="ghost-primary"
>
<Text>
Clearing the admin is a permanent action. You&apos;ll not be able to
reassign an admin and migrations will no longer be possible.
</Text>
</ActionModal>
);
};
4 changes: 3 additions & 1 deletion src/lib/data/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ export const DEFAULT_ADDRESS = "default-address";

export const MAX_FILE_SIZE = 800_000;

export const MICRO = 1000000;
export const CLEAR_ADMIN_GAS = 50_000;

export const MICRO = 1_000_000;

export const typeUrlDict = {
[MsgType.STORE_CODE]: "/cosmwasm.wasm.v1.MsgStoreCode",
Expand Down
16 changes: 16 additions & 0 deletions src/lib/data/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,19 @@ export const getInstantiateDetailByContractQueryDocument = graphql(`
}
`);

export const getAdminByContractAddressesQueryDocument = graphql(`
query getAdminByContractAddressesQueryDocument(
$contractAddresses: [String!]!
) {
contracts(where: { address: { _in: $contractAddresses } }) {
address
admin: account {
address
}
}
}
`);

export const getExecuteTxsByContractAddress = graphql(`
query getExecuteTxsByContractAddress(
$contractAddress: String!
Expand Down Expand Up @@ -278,6 +291,9 @@ export const getContractListByCodeId = graphql(`
) {
address
label
admin: account {
address
}
init_by: contract_histories(
order_by: { block: { timestamp: asc } }
limit: 1
Expand Down
Loading