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 @@ -38,6 +38,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased

### 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
- [#76](https://github.com/alleslabs/celatone-frontend/pull/76) Add Public projects page
- [#116](https://github.com/alleslabs/celatone-frontend/pull/116) Support Terra2.0 mainnet and testnet
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]
);
};
101 changes: 101 additions & 0 deletions src/lib/components/button/AdminButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
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();

return (
<Menu>
<Tooltip
hasArrow
label="You don't have admin access to this contract."
placement="top"
bg="primary.dark"
arrowSize={8}
isDisabled={!!address && address === admin}
songwongtp marked this conversation as resolved.
Show resolved Hide resolved
>
<MenuButton
variant="outline-gray"
as={Button}
isDisabled={!address || address !== admin}
songwongtp marked this conversation as resolved.
Show resolved Hide resolved
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/tx/clearAdmin";
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!]
songwongtp marked this conversation as resolved.
Show resolved Hide resolved
) {
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
account {
songwongtp marked this conversation as resolved.
Show resolved Hide resolved
address
}
init_by: contract_histories(
order_by: { block: { timestamp: asc } }
limit: 1
Expand Down
Loading