diff --git a/frontend/public/desmos-logo.png b/frontend/public/desmos-logo.png
new file mode 100644
index 000000000..da3973652
Binary files /dev/null and b/frontend/public/desmos-logo.png differ
diff --git a/frontend/public/no-authz-grants-illustration.png b/frontend/public/no-authz-grants-illustration.png
new file mode 100644
index 000000000..43809bf6e
Binary files /dev/null and b/frontend/public/no-authz-grants-illustration.png differ
diff --git a/frontend/public/revoke-image.png b/frontend/public/revoke-image.png
new file mode 100644
index 000000000..79c5de177
Binary files /dev/null and b/frontend/public/revoke-image.png differ
diff --git a/frontend/src/app/(routes)/authz/AuthzPage.tsx b/frontend/src/app/(routes)/authz/AuthzPage.tsx
index b86d11d7b..ae7d1637e 100644
--- a/frontend/src/app/(routes)/authz/AuthzPage.tsx
+++ b/frontend/src/app/(routes)/authz/AuthzPage.tsx
@@ -4,12 +4,17 @@ import DialogCreateAuthzGrant from './components/DialogCreateAuthzGrant';
const AuthzPage = ({ chainIDs }: { chainIDs: string[] }) => {
const [isGrantsToMe, setIsGrantsToMe] = useState(true);
+
const [dialogGrantOpen, setDialogGrantOpen] = useState(false);
- const hanldeDialogGrantClose = () => {
+ const handleDialogGrantClose = () => {
setDialogGrantOpen(false);
};
+<<<<<<< HEAD
console.log(chainIDs);
// TODO: dispatch and fetch the grants from authz state
+=======
+
+>>>>>>> cc4fb21 (feat: Implement authz overview and revoke (#1093))
return (
@@ -47,14 +52,25 @@ const AuthzPage = ({ chainIDs }: { chainIDs: string[] }) => {
+<<<<<<< HEAD
{/* TODO: Create UI to display grants and render data */}
{isGrantsToMe ? 'Grants to me' : 'Grants by me'}
+=======
+ {isGrantsToMe ? (
+
+ ) : (
+ setDialogGrantOpen(true)}
+ />
+ )}
+>>>>>>> cc4fb21 (feat: Implement authz overview and revoke (#1093))
);
diff --git a/frontend/src/app/(routes)/authz/[...chainNames]/page.tsx b/frontend/src/app/(routes)/authz/[...chainNames]/page.tsx
index 292b168f7..65603c522 100644
--- a/frontend/src/app/(routes)/authz/[...chainNames]/page.tsx
+++ b/frontend/src/app/(routes)/authz/[...chainNames]/page.tsx
@@ -5,10 +5,12 @@ import { RootState } from '@/store/store';
import { useParams } from 'next/navigation';
import React from 'react';
import AuthzPage from '../AuthzPage';
-import '../authz.css'
+import '../authz.css';
+
const Authz = () => {
const params = useParams();
+
const paramChains = params.chainNames;
const chainNames =
typeof paramChains === 'string' ? [paramChains] : paramChains;
@@ -31,6 +33,7 @@ const Authz = () => {
- Chain Not found -
)}
+
>
);
};
diff --git a/frontend/src/app/(routes)/authz/authz.css b/frontend/src/app/(routes)/authz/authz.css
index e7a72ae30..717a9959f 100644
--- a/frontend/src/app/(routes)/authz/authz.css
+++ b/frontend/src/app/(routes)/authz/authz.css
@@ -11,3 +11,50 @@
.grants-type-btn-selected {
@apply primary-gradient;
}
+<<<<<<< HEAD
+=======
+.authz-card {
+ @apply flex flex-col items-start gap-4 backdrop-blur-[2px] p-6 rounded-2xl;
+ background: #0e0b26;
+
+}
+.grant-address {
+ @apply flex items-center gap-2 opacity-80 p-2 rounded-lg;
+ background: rgba(255, 255, 255, 0.1);
+}
+.authz-card-grid {
+ @apply grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6;
+}
+.authz-permission-card {
+ @apply flex flex-col items-start gap-6 backdrop-blur-[2px] p-6 rounded-2xl;
+ background: rgba(255, 255, 255, 0.05);
+}
+.authz-small-text {
+ @apply text-[rgba(255,255,255,0.50)] text-sm font-normal leading-[normal]
+}
+
+
+.divider-line {
+ @apply h-[1px] w-full my-6 bg-[#ffffff35] opacity-50;
+}
+
+.error-box {
+ @apply flex justify-end mt-2 h-[26px];
+}
+
+.error-chip {
+ @apply text-[12px] rounded-lg bg-[#ff00005b] text-white text-center leading-normal max-w-fit py-1 px-2 truncate;
+}
+
+.msg-item {
+ @apply flex-center-center cursor-pointer rounded-2xl px-6 py-4 text-[14px] bg-[#FFFFFF0D];
+}
+
+.grant-authz-form {
+ @apply rounded-2xl bg-[#FFFFFF0D] p-6;
+}
+
+.moniker-name-chip {
+ @apply rounded-2xl font-light text-[14px] bg-[#FFFFFF0D] px-3 py-2 max-w-[120px] truncate;
+}
+>>>>>>> cc4fb21 (feat: Implement authz overview and revoke (#1093))
diff --git a/frontend/src/app/(routes)/authz/components/AuthzCard.tsx b/frontend/src/app/(routes)/authz/components/AuthzCard.tsx
new file mode 100644
index 000000000..b8244062f
--- /dev/null
+++ b/frontend/src/app/(routes)/authz/components/AuthzCard.tsx
@@ -0,0 +1,186 @@
+import React, { useState } from 'react';
+import Image from 'next/image';
+import { AuthorizationInfo } from './DialogAllPermissions';
+import { useAppDispatch, useAppSelector } from '@/custom-hooks/StateHooks';
+import { RootState } from '@/store/store';
+import {
+ getMsgNameFromAuthz,
+ getTypeURLFromAuthorization,
+} from '@/utils/authorizations';
+
+import { copyToClipboard } from '@/utils/copyToClipboard';
+import { setError } from '@/store/features/common/commonSlice';
+import DialogRevoke from './DialogRevoke';
+import useGetChainInfo from '@/custom-hooks/useGetChainInfo';
+import { resetTxStatus } from '@/store/features/authz/authzSlice';
+
+const AuthzCard = ({
+ chainID,
+ address,
+ grants,
+ showCloseIcon = true,
+ grantee,
+ granter,
+ isGrantsByMe = false,
+}: {
+ chainID: string;
+ address: string;
+ grants: Authorization[];
+ showCloseIcon?: boolean;
+ grantee: string;
+ granter: string;
+ isGrantsByMe?: boolean;
+}) => {
+ const networkLogo = useAppSelector(
+ (state: RootState) => state.wallet.networks[chainID]?.network.logos.menu
+ );
+
+ const nameToChainIDs = useAppSelector(
+ (state: RootState) => state.wallet.nameToChainIDs
+ );
+
+ const [dialogAllPermissionsOpen, setDialogAllPermissionsOpen] =
+ useState(false);
+ const handleDialogAllPermissionsClose = () => {
+ setDialogAllPermissionsOpen(false);
+ };
+
+ const getChainName = (chainID: string) => {
+ let chain: string = '';
+ Object.keys(nameToChainIDs).forEach((chainName) => {
+ if (nameToChainIDs[chainName] === chainID) chain = chainName;
+ });
+ return chain;
+ };
+ // const revoke = useAppSelector((state) => state.authz.txAuthzRes);
+ const dispatch = useAppDispatch();
+ const { getDenomInfo } = useGetChainInfo();
+
+ const { decimals } = getDenomInfo(chainID);
+ const { displayDenom } = getDenomInfo(chainID);
+
+ return (
+
+
+
+
{getChainName(chainID)}
+
+
+ {isGrantsByMe ? 'Grantee' : 'Granter'}
+
+
+
{address}
+
{
+ copyToClipboard(address);
+ dispatch(
+ setError({
+ type: 'success',
+ message: 'Copied',
+ })
+ );
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ src="/copy.svg"
+ width={24}
+ height={24}
+ alt="copy"
+ draggable={false}
+ className="cursor-pointer"
+ />
+
+
Permissions
+
+ {grants.map((permission, permissionIndex) => (
+
+ {permissionIndex > 2 ? null : (
+
+ )}
+
+ ))}
+
+
+
+
+
+
+ );
+};
+
+export default AuthzCard;
+
+const MessageChip = ({
+ permission,
+ granter,
+ grantee,
+ chainID,
+ showCloseIcon,
+}: {
+ permission: Authorization;
+ granter: string;
+ grantee: string;
+ chainID: string;
+ showCloseIcon: boolean;
+}) => {
+ const [dialogRevokeOpen, setDialogRevokeOpen] = useState(false);
+ const dispatch = useAppDispatch();
+ const handleDialogRevokeClose = () => {
+ setDialogRevokeOpen(false);
+ };
+ return (
+
+
+ {getMsgNameFromAuthz(permission)}
+ {showCloseIcon && (
+ {
+ setDialogRevokeOpen(true);
+ dispatch(resetTxStatus({ chainID: chainID }));
+ }}
+ />
+ )}
+
+
+
+ );
+};
diff --git a/frontend/src/app/(routes)/authz/components/DialogAllPermissions.tsx b/frontend/src/app/(routes)/authz/components/DialogAllPermissions.tsx
new file mode 100644
index 000000000..ed4b35156
--- /dev/null
+++ b/frontend/src/app/(routes)/authz/components/DialogAllPermissions.tsx
@@ -0,0 +1,340 @@
+import { useAppDispatch, useAppSelector } from '@/custom-hooks/StateHooks';
+import useGetAuthzRevokeMsgs from '@/custom-hooks/useGetAuthzRevokeMsgs';
+import { txAuthzRevoke } from '@/store/features/authz/authzSlice';
+import { RootState } from '@/store/store';
+import { TxStatus } from '@/types/enums';
+import {
+ getAllTypeURLsFromAuthorization,
+ getTypeURLFromAuthorization,
+ getTypeURLName,
+} from '@/utils/authorizations';
+import { dialogBoxPaperPropStyles } from '@/utils/commonStyles';
+import { CLOSE_ICON_PATH } from '@/utils/constants';
+import { getLocalTime } from '@/utils/dataTime';
+import { parseSpendLimit } from '@/utils/denom';
+import { CircularProgress, Dialog, DialogContent } from '@mui/material';
+import Image from 'next/image';
+import React, { useState } from 'react';
+
+interface AuthorizationInfoProps {
+ open: boolean;
+ onClose: () => void;
+ authorization: Authorization[];
+ displayDenom: string;
+ decimal: number;
+ chainID: string;
+ granter: string;
+ grantee: string;
+}
+
+interface RenderAuthorizationProps {
+ authz: Authorization;
+ displayDenom: string;
+ decimal: number;
+ chainID: string;
+ granter: string;
+ grantee: string;
+ permissionIndex: number;
+ changeMsgIndex: (index: number) => void;
+ selectedMsgIndex: number;
+ setRevokeAllMsgs: () => void;
+}
+
+const RenderAuthorization = ({
+ authz,
+ chainID,
+ changeMsgIndex,
+ decimal,
+ displayDenom,
+ grantee,
+ granter,
+ permissionIndex,
+ selectedMsgIndex,
+ setRevokeAllMsgs,
+}: RenderAuthorizationProps) => {
+ const { authorization, expiration } = authz;
+ const stakeAuthzs = {
+ AUTHORIZATION_TYPE_REDELEGATE: 'Redelegate',
+ AUTHORIZATION_TYPE_DELEGATE: 'Delegate',
+ AUTHORIZATION_TYPE_UNDELEGATE: 'Undelegate',
+ };
+
+ const dispatch = useAppDispatch();
+ const { txRevokeAuthzInputs } = useGetAuthzRevokeMsgs({
+ granter,
+ grantee,
+ chainID,
+ typeURLs: [getTypeURLFromAuthorization(authz)],
+ });
+ const txRevoke = (permissionIndex: number) => {
+ setRevokeAllMsgs();
+ changeMsgIndex(permissionIndex);
+ const { basicChainInfo, denom, feeAmount, feegranter, msgs } =
+ txRevokeAuthzInputs;
+ dispatch(
+ txAuthzRevoke({
+ basicChainInfo,
+ denom,
+ feeAmount,
+ feegranter,
+ msgs,
+ })
+ );
+ };
+ const loading = useAppSelector(
+ (state: RootState) => state.authz.chains?.[chainID].tx.status
+ );
+ const isSelected = selectedMsgIndex === permissionIndex;
+
+ switch (authorization['@type']) {
+ case '/cosmos.bank.v1beta1.SendAuthorization':
+ return (
+
+
+
+ Send
+
+
+
+
+
+
+ Spend Limit
+
+
+ {parseSpendLimit(authorization.spend_limit, decimal)}
+
+ {displayDenom}{' '}
+
+
+
+
+
Expiry
+
+ {expiration ? getLocalTime(expiration) : '-'}
+
+
+
+
+ );
+ case '/cosmos.authz.v1beta1.GenericAuthorization':
+ return (
+
+
+
+ {getTypeURLName(authorization.msg)}
+
+
+
+
+
Expiry
+
+ {expiration ? getLocalTime(expiration) : '-'}
+
+
+
+ );
+ case '/cosmos.staking.v1beta1.StakeAuthorization':
+ return (
+
+
+
+ {stakeAuthzs[authorization.authorization_type]}
+
+
+
+
+
+
Spend Limit
+
+ {authorization.max_tokens !== null && (
+
+ {parseSpendLimit([authorization.max_tokens], decimal)}
+
+ {displayDenom}{' '}
+
+ )}
+
+
+
+
Expiry
+
+ {expiration ? getLocalTime(expiration) : '-'}
+
+
+
+
+
+ {authorization.allow_list && (
+
+
Allow List:
+
+ {authorization.allow_list?.address.map((addr, index) => (
+
+ {addr.slice(0, 15)}
+
+ ))}
+
+
+ )}
+ {authorization.deny_list && (
+
+
Deny List:
+
+ {authorization.deny_list?.address.map((addr, index) => (
+
+ {addr.slice(0, 15)}
+
+ ))}
+
+
+ )}
+
+
+ );
+
+ default:
+ return <>Not Supported>;
+ }
+};
+
+export function AuthorizationInfo(props: AuthorizationInfoProps) {
+ const {
+ onClose,
+ open,
+ displayDenom,
+ decimal,
+ chainID,
+ authorization,
+ grantee,
+ granter,
+ } = props;
+
+ const dispatch = useAppDispatch();
+ const handleClose = () => {
+ onClose();
+ };
+ const [selectedMsgIndex, setSelectedMsgIndex] = useState(NaN);
+ const [revokeAllMsgs, setRevokeAllMsgs] = useState(false);
+
+ const changeMsgIndex = (index: number) => {
+ setSelectedMsgIndex(index);
+ };
+
+ const { txRevokeAuthzInputs } = useGetAuthzRevokeMsgs({
+ granter,
+ grantee,
+ chainID,
+ typeURLs: getAllTypeURLsFromAuthorization(authorization),
+ });
+ const { basicChainInfo, denom, feeAmount, feegranter, msgs } =
+ txRevokeAuthzInputs;
+ const txRevoke = () => {
+ setSelectedMsgIndex(NaN);
+ setRevokeAllMsgs(true);
+ dispatch(
+ txAuthzRevoke({
+ basicChainInfo,
+ denom,
+ feeAmount,
+ feegranter,
+ msgs,
+ })
+ );
+ };
+
+ const loading = useAppSelector(
+ (state: RootState) => state.authz.chains?.[chainID].tx.status
+ );
+
+ return (
+
+ );
+}
diff --git a/frontend/src/app/(routes)/authz/components/DialogRevoke.tsx b/frontend/src/app/(routes)/authz/components/DialogRevoke.tsx
new file mode 100644
index 000000000..76bd926c5
--- /dev/null
+++ b/frontend/src/app/(routes)/authz/components/DialogRevoke.tsx
@@ -0,0 +1,123 @@
+import { useAppDispatch, useAppSelector } from '@/custom-hooks/StateHooks';
+import useGetAuthzRevokeMsgs from '@/custom-hooks/useGetAuthzRevokeMsgs';
+
+import { txAuthzRevoke } from '@/store/features/authz/authzSlice';
+import { RootState } from '@/store/store';
+import { TxStatus } from '@/types/enums';
+import { dialogBoxPaperPropStyles } from '@/utils/commonStyles';
+import { CLOSE_ICON_PATH } from '@/utils/constants';
+import { CircularProgress, Dialog, DialogContent } from '@mui/material';
+
+import Image from 'next/image';
+import React, { useEffect } from 'react';
+
+interface DialogRevokeProps {
+ open: boolean;
+ onClose: () => void;
+ chainID: string;
+ grantee: string;
+ granter: string;
+ typeURL: string;
+}
+
+const DialogRevoke: React.FC = (props) => {
+ const { open, onClose, chainID, grantee, granter, typeURL } = props;
+ const dispatch = useAppDispatch();
+ const { txRevokeAuthzInputs } = useGetAuthzRevokeMsgs({
+ granter,
+ grantee,
+ chainID,
+ typeURLs: [typeURL],
+ });
+ const { basicChainInfo, denom, feeAmount, feegranter, msgs } =
+ txRevokeAuthzInputs;
+ const txRevoke = () => {
+ dispatch(
+ txAuthzRevoke({
+ basicChainInfo,
+ denom,
+ feeAmount,
+ feegranter,
+ msgs,
+ })
+ );
+ };
+ const loading = useAppSelector(
+ (state: RootState) => state.authz.chains?.[chainID].tx.status
+ );
+
+ useEffect(() => {
+ if (loading === TxStatus.IDLE) {
+ onClose();
+ }
+ }, [loading]);
+ return (
+
+ );
+};
+
+export default DialogRevoke;
diff --git a/frontend/src/app/(routes)/authz/components/GrantsByMe.tsx b/frontend/src/app/(routes)/authz/components/GrantsByMe.tsx
new file mode 100644
index 000000000..44473e408
--- /dev/null
+++ b/frontend/src/app/(routes)/authz/components/GrantsByMe.tsx
@@ -0,0 +1,65 @@
+import { useAppSelector } from '@/custom-hooks/StateHooks';
+import useAuthzGrants from '@/custom-hooks/useAuthzGrants';
+import AuthzCard from './AuthzCard';
+import { CircularProgress } from '@mui/material';
+import Image from 'next/image';
+import { NO_GRANTS_BY_ME_TEXT } from '@/utils/constants';
+
+const GrantsByMe = ({
+ chainIDs,
+ handleGrantDialogOpen,
+}: {
+ chainIDs: string[];
+ handleGrantDialogOpen: () => void;
+}) => {
+ const { getGrantsByMe } = useAuthzGrants();
+ const addressGrants = getGrantsByMe(chainIDs);
+ const loading = useAppSelector((state) => state.authz.getGrantsByMeLoading);
+
+ return addressGrants.length ? (
+ <>
+
+ {addressGrants.map((addressGrant) => (
+ <>
+ {!!addressGrant.grants.length && (
+
+ )}
+ >
+ ))}
+
+ >
+ ) : !!loading ? (
+
+
+
+ ) : (
+
+
+
+ {NO_GRANTS_BY_ME_TEXT}
+
+
+
+ );
+};
+
+export default GrantsByMe;
diff --git a/frontend/src/app/(routes)/authz/components/GrantsToMe.tsx b/frontend/src/app/(routes)/authz/components/GrantsToMe.tsx
new file mode 100644
index 000000000..018fc99df
--- /dev/null
+++ b/frontend/src/app/(routes)/authz/components/GrantsToMe.tsx
@@ -0,0 +1,55 @@
+import { useAppSelector } from '@/custom-hooks/StateHooks';
+import useAuthzGrants from '@/custom-hooks/useAuthzGrants';
+import AuthzCard from './AuthzCard';
+import { CircularProgress } from '@mui/material';
+import Image from 'next/image';
+import { NO_GRANTS_TO_ME_TEXT } from '@/utils/constants';
+
+const GrantsToMe = ({ chainIDs }: { chainIDs: string[] }) => {
+ const { getGrantsToMe } = useAuthzGrants();
+ const addressGrants = getGrantsToMe(chainIDs);
+ const loading = useAppSelector((state) => state.authz.getGrantsToMeLoading);
+
+ return addressGrants.length ? (
+ <>
+
+ {addressGrants.map((addressGrant) => (
+
+ ))}
+
+ >
+ ) : !!loading ? (
+
+
+
+ ) : (
+
+
+
+
+
+
+ {NO_GRANTS_TO_ME_TEXT}
+
+
+
+
+
+ );
+};
+
+export default GrantsToMe;
diff --git a/frontend/src/app/(routes)/authz/page.tsx b/frontend/src/app/(routes)/authz/page.tsx
index 539eefc6b..15bc09db5 100644
--- a/frontend/src/app/(routes)/authz/page.tsx
+++ b/frontend/src/app/(routes)/authz/page.tsx
@@ -4,7 +4,7 @@ import React from 'react';
import { useAppSelector } from '@/custom-hooks/StateHooks';
import { RootState } from '@/store/store';
import AuthzPage from './AuthzPage';
-import './authz.css'
+import './authz.css';
const Authz = () => {
const nameToChainIDs = useAppSelector(
diff --git a/frontend/src/custom-hooks/useGetAuthzRevokeMsgs.ts b/frontend/src/custom-hooks/useGetAuthzRevokeMsgs.ts
new file mode 100644
index 000000000..b45e97c6f
--- /dev/null
+++ b/frontend/src/custom-hooks/useGetAuthzRevokeMsgs.ts
@@ -0,0 +1,38 @@
+import useGetChainInfo from './useGetChainInfo';
+import { AuthzRevokeMsg } from '@/txns/authz';
+
+const useGetAuthzRevokeMsgs = ({
+ granter,
+ grantee,
+ chainID,
+ typeURLs,
+}: {
+ granter: string;
+ grantee: string;
+ chainID: string;
+ typeURLs: string[];
+}) => {
+ const { getChainInfo, getDenomInfo } = useGetChainInfo();
+ const basicChainInfo = getChainInfo(chainID);
+ const { decimals, minimalDenom } = getDenomInfo(chainID);
+ const { feeAmount: avgFeeAmount } = basicChainInfo;
+ const feeAmount = avgFeeAmount * 10 ** decimals;
+
+ const revokeAuthzMsgs: Msg[] = [];
+ typeURLs.forEach((typeURL) => {
+ const msg = AuthzRevokeMsg(granter, grantee, typeURL);
+ revokeAuthzMsgs.push(msg);
+ });
+ const txRevokeAuthzInputs = {
+ basicChainInfo: basicChainInfo,
+ denom: minimalDenom,
+ feeAmount: feeAmount,
+ feegranter: '',
+ msgs: revokeAuthzMsgs,
+ };
+ return {
+ txRevokeAuthzInputs,
+ };
+};
+
+export default useGetAuthzRevokeMsgs;
diff --git a/frontend/src/store/features/authz/authzSlice.ts b/frontend/src/store/features/authz/authzSlice.ts
new file mode 100644
index 000000000..9e7906744
--- /dev/null
+++ b/frontend/src/store/features/authz/authzSlice.ts
@@ -0,0 +1,545 @@
+'use client';
+
+import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
+import authzService from './service';
+import { TxStatus } from '../../../types/enums';
+import { cloneDeep } from 'lodash';
+import { getAddressByPrefix } from '@/utils/address';
+import { signAndBroadcast } from '@/utils/signing';
+import { setError, setTxAndHash } from '../common/commonSlice';
+import { NewTransaction } from '@/utils/transaction';
+import { addTransactions } from '../transactionHistory/transactionHistorySlice';
+import { GAS_FEE } from '@/utils/constants';
+import { ERR_UNKNOWN } from '@/utils/errors';
+import { AxiosError } from 'axios';
+
+interface ChainAuthz {
+ grantsToMe: Authorization[];
+ grantsByMe: Authorization[];
+ getGrantsToMeLoading: {
+ status: TxStatus;
+ errMsg: string;
+ };
+ getGrantsByMeLoading: {
+ status: TxStatus;
+ errMsg: string;
+ };
+
+ /*
+ this is mapping of address to list of authorizations (chain level)
+ example : {
+ "pasg1..." : ["stakeAuthorization...", "sendAuthorization..."]
+ }
+ */
+
+ GrantsToMeAddressMapping: Record;
+ GrantsByMeAddressMapping: Record;
+ tx: {
+ status: TxStatus;
+ errMsg: string;
+ };
+}
+
+interface GetAuthRevokeInputs {
+ basicChainInfo: BasicChainInfo;
+ feegranter: string;
+ denom: string;
+ msgs: Msg[];
+ feeAmount: number;
+}
+const defaultState: ChainAuthz = {
+ grantsToMe: [],
+ grantsByMe: [],
+ getGrantsByMeLoading: {
+ status: TxStatus.INIT,
+ errMsg: '',
+ },
+ getGrantsToMeLoading: {
+ status: TxStatus.INIT,
+ errMsg: '',
+ },
+ GrantsByMeAddressMapping: {},
+ GrantsToMeAddressMapping: {},
+ tx: {
+ status: TxStatus.INIT,
+ errMsg: '',
+ },
+};
+
+interface AuthzState {
+ authzModeEnabled: boolean;
+ authzAddress: string;
+ chains: Record;
+ getGrantsToMeLoading: number;
+ getGrantsByMeLoading: number;
+ /*
+ this is mapping of address to chain id to list of authorizations (inter chain level)
+ example : {
+ "cosmos1..." : {
+ "cosmoshub-4": ["stakeAuthorization...", "sendAuthorization..."]
+ }
+ }
+ */
+ AddressToChainAuthz: Record>;
+ multiChainAuthzGrantTx: {
+ status: TxStatus;
+ };
+}
+
+const initialState: AuthzState = {
+ authzModeEnabled: false,
+ authzAddress: '',
+ chains: {},
+ getGrantsByMeLoading: 0,
+ getGrantsToMeLoading: 0,
+ AddressToChainAuthz: {},
+ multiChainAuthzGrantTx: {
+ status: TxStatus.INIT,
+ },
+};
+
+export const getGrantsToMe = createAsyncThunk(
+ 'authz/grantsToMe',
+ async (data: GetGrantsInputs) => {
+ const response = await authzService.grantsToMe(
+ data.baseURL,
+ data.address,
+ data.pagination
+ );
+
+ return {
+ data: response.data,
+ };
+ }
+);
+
+export const getGrantsByMe = createAsyncThunk(
+ 'authz/grantsByMe',
+ async (data: GetGrantsInputs) => {
+ const response = await authzService.grantsByMe(
+ data.baseURL,
+ data.address,
+ data.pagination
+ );
+ return {
+ data: response.data,
+ };
+ }
+);
+
+export const txCreateMultiChainAuthzGrant = createAsyncThunk(
+ 'authz/create-multichain-grant',
+ async (data: TxGrantMultiChainAuthzInputs, { rejectWithValue, dispatch }) => {
+ try {
+ const promises = data.data.map((chainGrant) => {
+ return dispatch(txCreateAuthzGrant(chainGrant));
+ });
+ await Promise.all(promises);
+ data.data.forEach((chainGrant) => {
+ dispatch(txCreateAuthzGrant(chainGrant));
+ });
+ } catch (error) {
+ if (error instanceof AxiosError) return rejectWithValue(error.response);
+ }
+ }
+);
+
+export const txCreateAuthzGrant = createAsyncThunk(
+ 'authz/create-grant',
+ async (
+ data: TxGrantAuthzInputs,
+ { rejectWithValue, fulfillWithValue, dispatch }
+ ) => {
+ try {
+ const result = await signAndBroadcast(
+ data.basicChainInfo.chainID,
+ data.basicChainInfo.aminoConfig,
+ data.basicChainInfo.prefix,
+ data.msgs,
+ GAS_FEE,
+ '',
+ `${data.feeAmount}${data.denom}`,
+ data.basicChainInfo.rest,
+ data.feegranter?.length > 0 ? data.feegranter : undefined
+ );
+
+ // TODO: Store txn, (This is throwing error because of BigInt in message)
+ // const tx = NewTransaction(
+ // result,
+ // data.msgs,
+ // data.basicChainInfo.chainID,
+ // data.basicChainInfo.address
+ // );
+ // dispatch(
+ // addTransactions({
+ // chainID: data.basicChainInfo.chainID,
+ // address: data.basicChainInfo.cosmosAddress,
+ // transactions: [tx],
+ // })
+ // );
+
+ // dispatch(
+ // setTxAndHash({
+ // tx: undefined,
+ // hash: tx.transactionHash,
+ // })
+ // );
+
+ if (result?.code === 0) {
+ dispatch(
+ getGrantsByMe({
+ baseURL: data.basicChainInfo.baseURL,
+ address: data.basicChainInfo.address,
+ chainID: data.basicChainInfo.chainID,
+ })
+ );
+ return fulfillWithValue({ txHash: result?.transactionHash });
+ } else {
+ return rejectWithValue(result?.rawLog);
+ }
+ /* eslint-disable @typescript-eslint/no-explicit-any */
+ } catch (error: any) {
+ return rejectWithValue(error?.message || ERR_UNKNOWN);
+ }
+ }
+);
+
+export const txAuthzExec = createAsyncThunk(
+ 'authz/tx-exec',
+ async (
+ data: txAuthzExecInputs,
+ { rejectWithValue, fulfillWithValue, dispatch }
+ ) => {
+ try {
+ const result = await signAndBroadcast(
+ data.basicChainInfo.chainID,
+ data.basicChainInfo.aminoConfig,
+ data.basicChainInfo.prefix,
+ data.msgs,
+ GAS_FEE,
+ data.metaData,
+ `${data.basicChainInfo.feeAmount}${data.feeDenom}`,
+ data.basicChainInfo.rest,
+ data.feeGranter
+ );
+ if (result?.code === 0) {
+ const tx = NewTransaction(
+ result,
+ data.msgs,
+ data.basicChainInfo.chainID,
+ data.basicChainInfo.cosmosAddress
+ );
+ dispatch(
+ addTransactions({
+ transactions: [tx],
+ chainID: data.basicChainInfo.chainID,
+ address: data.basicChainInfo.cosmosAddress,
+ })
+ );
+ dispatch(
+ setTxAndHash({
+ hash: result?.transactionHash,
+ tx,
+ })
+ );
+ return fulfillWithValue({ txHash: result?.transactionHash });
+ } else {
+ dispatch(
+ setError({
+ type: 'error',
+ message: result?.rawLog || 'transaction Failed',
+ })
+ );
+ return rejectWithValue(result?.rawLog);
+ }
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
+ } catch (error: any) {
+ dispatch(
+ setError({
+ type: 'error',
+ message: error.message,
+ })
+ );
+ return rejectWithValue(error.message);
+ }
+ }
+);
+
+export const txAuthzRevoke = createAsyncThunk(
+ 'authz/tx-revoke',
+ async (
+ data: GetAuthRevokeInputs,
+ { rejectWithValue, fulfillWithValue, dispatch }
+ ) => {
+ try {
+ const result = await signAndBroadcast(
+ data.basicChainInfo.chainID,
+ data.basicChainInfo.aminoConfig,
+ data.basicChainInfo.prefix,
+ data.msgs,
+ GAS_FEE,
+ '',
+ `${data.feeAmount}${data.denom}`,
+ data.basicChainInfo.rest
+ // data.feegranter?.length > 0 ? data.feegranter : undefined
+ );
+ if (result?.code === 0) {
+ const tx = NewTransaction(
+ result,
+ data.msgs,
+ data.basicChainInfo.chainID,
+ data.basicChainInfo.cosmosAddress
+ );
+ dispatch(
+ addTransactions({
+ transactions: [tx],
+ chainID: data.basicChainInfo.chainID,
+ address: data.basicChainInfo.cosmosAddress,
+ })
+ );
+ dispatch(
+ setTxAndHash({
+ hash: result?.transactionHash,
+ tx,
+ })
+ );
+ dispatch(
+ setTxAndHash({
+ tx: tx,
+ hash: result?.transactionHash,
+ })
+ );
+ dispatch(
+ getGrantsByMe({
+ baseURL: data.basicChainInfo.baseURL,
+ address: data.basicChainInfo.address,
+ chainID: data.basicChainInfo.chainID,
+ })
+ );
+ return fulfillWithValue({ txHash: result?.transactionHash });
+ } else {
+ dispatch(
+ setError({
+ type: 'error',
+ message: result?.rawLog || '',
+ })
+ );
+ return rejectWithValue(result?.rawLog);
+ }
+ } catch (error) {
+ dispatch(
+ setError({
+ type: 'error',
+ message: ERR_UNKNOWN,
+ })
+ );
+ return rejectWithValue(ERR_UNKNOWN);
+ }
+ }
+);
+
+export const authzSlice = createSlice({
+ name: 'authz',
+ initialState,
+ reducers: {
+ enableAuthzMode: (state, action: PayloadAction<{ address: string }>) => {
+ state.authzModeEnabled = true;
+ state.authzAddress = action.payload.address;
+ },
+ exitAuthzMode: (state) => {
+ state.authzModeEnabled = false;
+ state.authzAddress = '';
+ },
+ resetState: (state) => {
+ /* eslint-disable @typescript-eslint/no-unused-vars */
+ state = cloneDeep(initialState);
+ },
+ resetTxStatus: (state, action: PayloadAction<{ chainID: string }>) => {
+ const { chainID } = action.payload;
+ state.chains[chainID].tx = {
+ errMsg: '',
+ status: TxStatus.INIT,
+ };
+ },
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(getGrantsToMe.pending, (state, action) => {
+ state.getGrantsToMeLoading++;
+ const chainID = action.meta.arg.chainID;
+ if (!state.chains[chainID])
+ state.chains[chainID] = cloneDeep(defaultState);
+ state.chains[chainID].getGrantsToMeLoading.status = TxStatus.PENDING;
+ state.chains[chainID].grantsToMe = [];
+ state.chains[chainID].GrantsToMeAddressMapping = {};
+ const allAddressToAuthz = state.AddressToChainAuthz;
+ const addresses = Object.keys(allAddressToAuthz);
+ addresses.forEach((address) => {
+ allAddressToAuthz[address][chainID] = [];
+ });
+
+ state.AddressToChainAuthz = allAddressToAuthz;
+ })
+ .addCase(getGrantsToMe.fulfilled, (state, action) => {
+ const chainID = action.meta.arg.chainID;
+ const allAddressToAuthz = state.AddressToChainAuthz;
+ const addresses = Object.keys(allAddressToAuthz);
+ addresses.forEach((address) => {
+ allAddressToAuthz[address][chainID] = [];
+ });
+
+ state.AddressToChainAuthz = allAddressToAuthz;
+
+ state.getGrantsToMeLoading--;
+
+ const grants = action.payload.data.grants;
+ state.chains[chainID].grantsToMe = grants;
+ const addressMapping: Record = {};
+ const allChainsAddressToGrants = state.AddressToChainAuthz;
+
+ grants.forEach((grant: Authorization) => {
+ const granter = grant.granter;
+ const cosmosAddress = getAddressByPrefix(granter, 'cosmos');
+ if (!addressMapping[granter]) addressMapping[granter] = [];
+ if (!allChainsAddressToGrants[cosmosAddress])
+ allChainsAddressToGrants[cosmosAddress] = {};
+ if (!allChainsAddressToGrants[cosmosAddress][chainID])
+ allChainsAddressToGrants[cosmosAddress][chainID] = [];
+ allChainsAddressToGrants[cosmosAddress][chainID] = [
+ ...allChainsAddressToGrants[cosmosAddress][chainID],
+ grant,
+ ];
+ addressMapping[granter] = [...addressMapping[granter], grant];
+ });
+ state.AddressToChainAuthz = allChainsAddressToGrants;
+ state.chains[chainID].GrantsToMeAddressMapping = addressMapping;
+ state.chains[chainID].getGrantsToMeLoading = {
+ status: TxStatus.IDLE,
+ errMsg: '',
+ };
+ })
+ .addCase(getGrantsToMe.rejected, (state, action) => {
+ state.getGrantsToMeLoading--;
+ const chainID = action.meta.arg.chainID;
+
+ state.chains[chainID].getGrantsToMeLoading = {
+ status: TxStatus.REJECTED,
+ errMsg:
+ action.error.message ||
+ 'An error occurred while fetching authz grants to me',
+ };
+ });
+ builder
+ .addCase(getGrantsByMe.pending, (state, action) => {
+ state.getGrantsByMeLoading++;
+ const chainID = action.meta.arg.chainID;
+ if (!state.chains[chainID])
+ state.chains[chainID] = cloneDeep(defaultState);
+ state.chains[chainID].getGrantsByMeLoading.status = TxStatus.PENDING;
+ state.chains[chainID].grantsByMe = [];
+ state.chains[chainID].GrantsByMeAddressMapping = {};
+ })
+ .addCase(getGrantsByMe.fulfilled, (state, action) => {
+ state.getGrantsByMeLoading--;
+ const chainID = action.meta.arg.chainID;
+ const grants = action.payload.data.grants;
+ state.chains[chainID].grantsByMe = grants;
+ const addressMapping: Record = {};
+ grants.forEach((grant: Authorization) => {
+ const granter = grant.grantee;
+ if (!addressMapping[granter]) addressMapping[granter] = [];
+ addressMapping[granter] = [...addressMapping[granter], grant];
+ });
+ state.chains[chainID].GrantsByMeAddressMapping = addressMapping;
+ state.chains[chainID].getGrantsByMeLoading = {
+ status: TxStatus.IDLE,
+ errMsg: '',
+ };
+ })
+ .addCase(getGrantsByMe.rejected, (state, action) => {
+ state.getGrantsByMeLoading--;
+ const chainID = action.meta.arg.chainID;
+
+ state.chains[chainID].getGrantsByMeLoading = {
+ status: TxStatus.REJECTED,
+ errMsg:
+ action.error.message ||
+ 'An error occurred while fetching authz grants by me',
+ };
+ });
+ builder
+ .addCase(txAuthzExec.pending, (state, action) => {
+ const chainID = action.meta.arg.basicChainInfo.chainID;
+ state.chains[chainID].tx.status = TxStatus.PENDING;
+ state.chains[chainID].tx.errMsg = '';
+ })
+ .addCase(txAuthzExec.fulfilled, (state, action) => {
+ const chainID = action.meta.arg.basicChainInfo.chainID;
+ state.chains[chainID].tx.status = TxStatus.IDLE;
+ })
+ .addCase(txAuthzExec.rejected, (state, action) => {
+ const chainID = action.meta.arg.basicChainInfo.chainID;
+ state.chains[chainID].tx.status = TxStatus.REJECTED;
+ state.chains[chainID].tx.errMsg = action.error.message || 'rejected';
+ });
+ builder
+ .addCase(txCreateAuthzGrant.pending, (state, action) => {
+ const { chainID } = action.meta.arg.basicChainInfo;
+ state.chains[chainID].tx.status = TxStatus.PENDING;
+ state.chains[chainID].tx.errMsg = '';
+ })
+ .addCase(txCreateAuthzGrant.fulfilled, (state, action) => {
+ const { chainID } = action.meta.arg.basicChainInfo;
+ const { txHash } = action.payload;
+ state.chains[chainID].tx.status = TxStatus.IDLE;
+ state.chains[chainID].tx.errMsg = '';
+ action.meta.arg.onTxComplete?.({
+ isTxSuccess: true,
+ txHash: txHash,
+ });
+ })
+ .addCase(txCreateAuthzGrant.rejected, (state, action) => {
+ const { chainID } = action.meta.arg.basicChainInfo;
+ state.chains[chainID].tx.status = TxStatus.REJECTED;
+ state.chains[chainID].tx.errMsg =
+ typeof action.payload === 'string' ? action.payload : '';
+ action.meta.arg.onTxComplete?.({
+ isTxSuccess: false,
+ error:
+ typeof action.payload === 'string' ? action.payload : ERR_UNKNOWN,
+ });
+ });
+
+ builder
+ .addCase(txCreateMultiChainAuthzGrant.pending, (state) => {
+ state.multiChainAuthzGrantTx.status = TxStatus.PENDING;
+ })
+ .addCase(txCreateMultiChainAuthzGrant.fulfilled, (state) => {
+ state.multiChainAuthzGrantTx.status = TxStatus.IDLE;
+ })
+ .addCase(txCreateMultiChainAuthzGrant.rejected, (state) => {
+ state.multiChainAuthzGrantTx.status = TxStatus.REJECTED;
+ });
+
+ builder
+ .addCase(txAuthzRevoke.pending, (state, action) => {
+ const chainID = action.meta.arg.basicChainInfo.chainID;
+ state.chains[chainID].tx.status = TxStatus.PENDING;
+ state.chains[chainID].tx.errMsg = '';
+ })
+ .addCase(txAuthzRevoke.fulfilled, (state, action) => {
+ const chainID = action.meta.arg.basicChainInfo.chainID;
+ state.chains[chainID].tx.status = TxStatus.IDLE;
+ })
+ .addCase(txAuthzRevoke.rejected, (state, action) => {
+ const chainID = action.meta.arg.basicChainInfo.chainID;
+ state.chains[chainID].tx.status = TxStatus.REJECTED;
+ state.chains[chainID].tx.errMsg = action.error.message || 'rejected';
+ });
+ },
+});
+
+export const { enableAuthzMode, exitAuthzMode, resetState, resetTxStatus } =
+ authzSlice.actions;
+
+export default authzSlice.reducer;
diff --git a/frontend/src/txns/authz/index.ts b/frontend/src/txns/authz/index.ts
new file mode 100644
index 000000000..fb2a78cfc
--- /dev/null
+++ b/frontend/src/txns/authz/index.ts
@@ -0,0 +1,10 @@
+export { AuthzGenericGrantMsg, AuthzSendGrantMsg } from "./grant";
+export {
+ AuthzExecDelegateMsg,
+ AuthzExecReDelegateMsg,
+ AuthzExecSendMsg,
+ AuthzExecUnDelegateMsg,
+ AuthzExecVoteMsg,
+ AuthzExecWithdrawRewardsMsg,
+} from './exec';
+export { AuthzRevokeMsg } from './revoke';
diff --git a/frontend/src/txns/authz/revoke.ts b/frontend/src/txns/authz/revoke.ts
new file mode 100644
index 000000000..66d598c71
--- /dev/null
+++ b/frontend/src/txns/authz/revoke.ts
@@ -0,0 +1,18 @@
+import { MsgRevoke } from 'cosmjs-types/cosmos/authz/v1beta1/tx';
+
+const msgAuthzRevokeTypeUrl = '/cosmos.authz.v1beta1.MsgRevoke';
+
+export function AuthzRevokeMsg(
+ granter: string,
+ grantee: string,
+ typeURL: string
+): Msg {
+ return {
+ typeUrl: msgAuthzRevokeTypeUrl,
+ value: MsgRevoke.fromPartial({
+ msgTypeUrl: typeURL,
+ grantee: grantee,
+ granter: granter,
+ }),
+ };
+}
diff --git a/frontend/src/types/authz.d.ts b/frontend/src/types/authz.d.ts
new file mode 100644
index 000000000..5a510651c
--- /dev/null
+++ b/frontend/src/types/authz.d.ts
@@ -0,0 +1,105 @@
+interface Authorization {
+ granter: string;
+ grantee: string;
+ expiration: string | null;
+ authorization: GenericAuthorization | SendAuthorization | StakeAuthorization;
+}
+
+interface GenericAuthorization {
+ spend_limit: Coin[];
+ '@type': '/cosmos.authz.v1beta1.GenericAuthorization';
+ msg: string;
+}
+
+interface SendAuthorization {
+ msg: ReactNode;
+ '@type': '/cosmos.bank.v1beta1.SendAuthorization';
+ spend_limit: Coin[];
+ allow_list?: string[];
+}
+
+interface StakeAuthorization {
+ msg: ReactNode;
+ spend_limit: Coin[];
+
+ '@type': '/cosmos.staking.v1beta1.StakeAuthorization';
+
+ max_tokens: null | Coin;
+ allow_list: undefined | AddressList;
+ deny_list: undefined | AddressList;
+ authorization_type: AuthzDelegateType | AuthzReDelegateType | AuthzUnBondType;
+}
+
+interface AddressList {
+ address: string[];
+}
+
+type AuthzUnBondType = 'AUTHORIZATION_TYPE_UNDELEGATE';
+type AuthzDelegateType = 'AUTHORIZATION_TYPE_DELEGATE';
+type AuthzReDelegateType = 'AUTHORIZATION_TYPE_REDELEGATE';
+
+interface GetGrantsInputs {
+ baseURL: string;
+ address: string;
+ pagination?: KeyLimitPagination;
+ chainID: string;
+}
+
+interface GetGrantsResponse {
+ grants: Authorization[];
+ pagination: Pagination;
+}
+
+interface AddressGrants {
+ address: string;
+ chainID: string;
+ grants: Authorization[];
+}
+
+interface Grant {
+ msg: string;
+ expiration: Date;
+ spend_limit?: string;
+ max_tokens?: string;
+ isDenyList?: boolean;
+ validators_list?: string[];
+}
+
+interface TxGrantAuthzInputs {
+ basicChainInfo: BasicChainInfo;
+ msgs: Msg[];
+ denom: string;
+ feeAmount: number;
+ feegranter: string;
+ onTxComplete?: ({ isTxSuccess, error, txHash }: OnTxnCompleteInputs) => void;
+}
+
+interface TxGrantMultiChainAuthzInputs {
+ data: TxGrantAuthzInputs[];
+}
+
+interface MultiChainTx {
+ ChainID: string;
+ txInputs: TxGrantAuthzInputs;
+}
+
+interface OnTxnCompleteInputs {
+ isTxSuccess: boolean;
+ error?: string;
+ txHash?: string;
+}
+
+interface ChainStatus {
+ isTxSuccess?: boolean;
+ txStatus: string;
+ error: string;
+ txHash: string;
+}
+
+interface txAuthzExecInputs {
+ basicChainInfo: BasicChainInfo;
+ feeDenom: string;
+ metaData: string;
+ msgs: Msg[];
+ feeGranter?: string;
+}
diff --git a/frontend/src/utils/authorizations.ts b/frontend/src/utils/authorizations.ts
new file mode 100644
index 000000000..a1d1d868f
--- /dev/null
+++ b/frontend/src/utils/authorizations.ts
@@ -0,0 +1,187 @@
+interface AuthzMenuItem {
+ txn: string;
+ typeURL: string;
+}
+
+export function authzMsgTypes(): AuthzMenuItem[] {
+ return [
+ {
+ txn: 'Send',
+ typeURL: '/cosmos.bank.v1beta1.MsgSend',
+ },
+ {
+ txn: 'Grant Authz',
+ typeURL: '/cosmos.authz.v1beta1.MsgGrant',
+ },
+ {
+ txn: 'Revoke Authz',
+ typeURL: '/cosmos.authz.v1beta1.MsgRevoke',
+ },
+ {
+ txn: 'Grant Feegrant',
+ typeURL: '/cosmos.feegrant.v1beta1.MsgGrantAllowance',
+ },
+ {
+ txn: 'Revoke Feegrant',
+ typeURL: '/cosmos.feegrant.v1beta1.MsgRevokeAllowance',
+ },
+ {
+ txn: 'Submit Proposal',
+ typeURL: '/cosmos.gov.v1beta1.MsgSubmitProposal',
+ },
+ {
+ txn: 'Vote',
+ typeURL: '/cosmos.gov.v1beta1.MsgVote',
+ },
+ {
+ txn: 'Deposit',
+ typeURL: '/cosmos.gov.v1beta1.MsgDeposit',
+ },
+ {
+ txn: 'Withdraw Rewards',
+ typeURL: '/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward',
+ },
+ {
+ txn: 'Redelegate',
+ typeURL: '/cosmos.staking.v1beta1.MsgBeginRedelegate',
+ },
+ {
+ txn: 'Delegate',
+ typeURL: '/cosmos.staking.v1beta1.MsgDelegate',
+ },
+ {
+ txn: 'Undelegate',
+ typeURL: '/cosmos.staking.v1beta1.MsgUndelegate',
+ },
+ {
+ txn: 'Withdraw Commission',
+ typeURL: '/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission',
+ },
+ {
+ txn: 'Unjail',
+ typeURL: '/cosmos.slashing.v1beta1.MsgUnjail',
+ },
+ ];
+}
+
+export const MAP_TXN_MSG_TYPES: Record = {
+ send: '/cosmos.bank.v1beta1.MsgSend',
+ grant_authz: '/cosmos.authz.v1beta1.MsgGrant',
+ revoke_authz: '/cosmos.authz.v1beta1.MsgRevoke',
+ grant_feegrant: '/cosmos.feegrant.v1beta1.MsgGrantAllowance',
+ revoke_feegrant: '/cosmos.feegrant.v1beta1.MsgRevokeAllowance',
+ submit_proposal: '/cosmos.gov.v1beta1.MsgSubmitProposal',
+ vote: '/cosmos.gov.v1beta1.MsgVote',
+ deposit: '/cosmos.gov.v1beta1.MsgDeposit',
+ withdraw_rewards: '/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward',
+ redelegate: '/cosmos.staking.v1beta1.MsgBeginRedelegate',
+ delegate: '/cosmos.staking.v1beta1.MsgDelegate',
+ undelegate: '/cosmos.staking.v1beta1.MsgUndelegate',
+ withdraw_commission:
+ '/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission',
+ unjail: '/cosmos.slashing.v1beta1.MsgUnjail',
+};
+
+export const grantAuthzFormDefaultValues = () => {
+ const date = new Date();
+ const expiration = new Date(date.setTime(date.getTime() + 365 * 86400000));
+ return {
+ grant_authz: { expiration: expiration },
+ revoke_authz: { expiration: expiration },
+ grant_feegrant: { expiration: expiration },
+ revoke_feegrant: { expiration: expiration },
+ submit_proposal: { expiration: expiration },
+ vote: { expiration: expiration },
+ deposit: { expiration: expiration },
+ withdraw_rewards: { expiration: expiration },
+ withdraw_commission: { expiration: expiration },
+ unjail: { expiration: expiration },
+ send: { expiration: expiration, spend_limit: '' },
+ delegate: { expiration: expiration, max_tokens: '' },
+ undelegate: { expiration: expiration, max_tokens: '' },
+ redelegate: { expiration: expiration, max_tokens: '' },
+ };
+};
+export function getTypeURLName(url: string) {
+ if (!url) {
+ return '-';
+ }
+ const temp = url.split('.');
+ if (temp?.length > 0) {
+ const msg = temp[temp?.length - 1];
+ return msg.slice(3, msg.length);
+ }
+ return '-';
+}
+
+function getStakeAuthzType(type: string): string {
+ switch (type) {
+ case 'AUTHORIZATION_TYPE_DELEGATE':
+ return '/cosmos.staking.v1beta1.MsgDelegate';
+ case 'AUTHORIZATION_TYPE_UNDELEGATE':
+ return '/cosmos.staking.v1beta1.MsgUndelegate';
+ case 'AUTHORIZATION_TYPE_REDELEGATE':
+ return '/cosmos.staking.v1beta1.MsgBeginRedelegate';
+ default:
+ throw new Error('unsupported stake authorization type');
+ }
+}
+
+export function getMsgNameFromAuthz(authorization: Authorization): string {
+ switch (authorization.authorization['@type']) {
+ case '/cosmos.bank.v1beta1.SendAuthorization':
+ return 'Send';
+ case '/cosmos.authz.v1beta1.GenericAuthorization':
+ return getTypeURLName(authorization.authorization.msg);
+ case '/cosmos.staking.v1beta1.StakeAuthorization':
+ const temp = getStakeAuthzType(
+ authorization?.authorization.authorization_type
+ ).split('.');
+ if (temp.length === 0) {
+ return 'Unknown';
+ }
+ return temp[temp.length - 1];
+ default:
+ return 'Unknown';
+ }
+}
+
+export function getTypeURLFromAuthorization(
+ authorization: Authorization
+): string {
+ switch (authorization.authorization['@type']) {
+ case '/cosmos.bank.v1beta1.SendAuthorization':
+ return '/cosmos.bank.v1beta1.MsgSend';
+ case '/cosmos.authz.v1beta1.GenericAuthorization':
+ return authorization.authorization.msg;
+ case '/cosmos.staking.v1beta1.StakeAuthorization':
+ return getStakeAuthzType(authorization?.authorization.authorization_type);
+ default:
+ throw new Error('unsupported authorization');
+ }
+}
+
+export const getAllTypeURLsFromAuthorization = (
+ authorizations: Authorization[]
+): string[] => {
+ const typeURLs: string[] = [];
+ authorizations.forEach((authorization) => {
+ typeURLs.push(getTypeURLFromAuthorization(authorization));
+ });
+ return typeURLs;
+};
+
+export const GENRIC_GRANTS = [
+ 'grant_authz',
+ 'revoke_authz',
+ 'grant_feegrant',
+ 'revoke_feegrant',
+ 'submit_proposal',
+ 'vote',
+ 'deposit',
+ 'withdraw_rewards',
+ 'withdraw_commission',
+ 'unjail',
+];
+
+export const STAKE_GRANTS = ['delegate', 'undelegate', 'redelegate'];
diff --git a/frontend/src/utils/constants.ts b/frontend/src/utils/constants.ts
index dc348b2e7..9416f578a 100644
--- a/frontend/src/utils/constants.ts
+++ b/frontend/src/utils/constants.ts
@@ -161,3 +161,5 @@ export const TWITTER_ICON = '/twitter-icon.png';
export const TWITTER_LINK = 'https://twitter.com/vitwit_';
export const MIN_SALT_VALUE = 99999;
export const MAX_SALT_VALUE = 99999999;
+export const NO_GRANTS_BY_ME_TEXT = "You haven't granted any permission yet";
+export const NO_GRANTS_TO_ME_TEXT = "You don't have any grants";