+<<<<<<< HEAD
Staking
+=======
+
+>>>>>>> a885705 (feat: integrate authz with staking and overview (#1092))
{
- const stakedBalance = useAppSelector(
- (state: RootState) =>
- state.staking.chains?.[chainID]?.delegations.totalStaked || 0
- );
- const totalRewards = useAppSelector(
- (state: RootState) =>
- state.distribution.chains?.[chainID]?.delegatorRewards.totalRewards || 0
+ const isAuthzMode = useAppSelector((state) => state.authz.authzModeEnabled);
+ const authzAddress = useAppSelector((state) => state.authz.authzAddress);
+
+ const staked = useAppSelector((state) => state.staking.chains);
+ const authzStaked = useAppSelector((state) => state.staking.authz.chains);
+ const rewards = useAppSelector((state) => state.distribution.chains);
+ const authzRewards = useAppSelector(
+ (state) => state.distribution.authzChains
);
+
+ const { txAuthzClaim, txAuthzRestake } = useAuthzStakingExecHelper();
+ const stakedBalance = isAuthzMode
+ ? authzStaked[chainID]?.delegations.totalStaked || 0
+ : staked[chainID]?.delegations.totalStaked || 0;
+
+ const totalRewards = isAuthzMode
+ ? authzRewards[chainID]?.delegatorRewards.totalRewards || 0
+ : rewards[chainID]?.delegatorRewards.totalRewards || 0;
+
const isClaimAll = useAppSelector(
(state) => state?.distribution?.chains?.[chainID]?.isTxAll || false
);
@@ -58,22 +72,54 @@ const StakingSidebar = ({
const txClaimStatus = useAppSelector(
(state: RootState) => state.distribution.chains[chainID]?.tx.status
);
+ const txAuthzStatus = useAppSelector(
+ (state) => state.authz.chains[chainID]?.tx?.status || TxStatus.INIT
+ );
const txRestakeStatus = useAppSelector(
(state: RootState) => state.staking.chains[chainID]?.reStakeTxStatus
);
const validatorsStatus = useAppSelector(
(state: RootState) => state.staking.chains[chainID]?.validators.status
);
- const delegations = useAppSelector(
- (state: RootState) =>
- state.staking.chains[chainID]?.delegations?.delegations
- ?.delegation_responses
- );
+ const delegations = isAuthzMode
+ ? authzStaked[chainID]?.delegations?.delegations?.delegation_responses
+ : staked[chainID]?.delegations?.delegations?.delegation_responses;
const dispatch = useAppDispatch();
- const { txWithdrawAllRewardsInputs, txRestakeInputs } = useGetTxInputs();
+ const { getChainInfo } = useGetChainInfo();
+ const { txWithdrawAllRewardsInputs, txRestakeInputs, txAuthzRestakeMsgs } =
+ useGetTxInputs();
const claim = (chainID: string) => {
+ if (isAuthzMode) {
+ if (txAuthzStatus === TxStatus.PENDING) {
+ dispatch(
+ setError({
+ type: 'error',
+ message: TXN_PENDING_ERROR('Authz'),
+ })
+ );
+ return;
+ }
+ const { address } = getChainInfo(chainID);
+ const pairs: DelegationsPairs[] = (
+ authzRewards[chainID]?.delegatorRewards?.list || []
+ ).map((reward) => {
+ const pair = {
+ delegator: authzAddress,
+ validator: reward.validator_address,
+ };
+ return pair;
+ });
+ txAuthzClaim({
+ grantee: address,
+ granter: authzAddress,
+ pairs: pairs,
+ chainID: chainID,
+ });
+ return;
+ }
+
if (txClaimStatus === TxStatus.PENDING) {
dispatch(
setError({
@@ -108,6 +154,17 @@ const StakingSidebar = ({
}
const txInputs = txRestakeInputs(chainID);
txInputs.isTxAll = true;
+ if (isAuthzMode) {
+ const { address } = getChainInfo(chainID);
+ const msgs = txAuthzRestakeMsgs(chainID);
+ txAuthzRestake({
+ grantee: address,
+ granter: authzAddress,
+ msgs: msgs,
+ chainID: chainID,
+ });
+ return;
+ }
if (txInputs.msgs.length) dispatch(txRestake(txInputs));
else {
dispatch(
@@ -139,7 +196,7 @@ const StakingSidebar = ({
className="staking-sidebar-actions-btn"
onClick={() => claim(chainID)}
>
- {txClaimStatus === TxStatus.PENDING && isClaimAll? (
+ {txClaimStatus === TxStatus.PENDING && isClaimAll ? (
) : (
'Claim All'
diff --git a/frontend/src/app/(routes)/staking/components/UnbondingCard.tsx b/frontend/src/app/(routes)/staking/components/UnbondingCard.tsx
index 6ec9f0273..4c1aa76d2 100644
--- a/frontend/src/app/(routes)/staking/components/UnbondingCard.tsx
+++ b/frontend/src/app/(routes)/staking/components/UnbondingCard.tsx
@@ -24,6 +24,9 @@ import {
import useGetChainInfo from '@/custom-hooks/useGetChainInfo';
import { TxStatus } from '@/types/enums';
import { dialogBoxPaperPropStyles } from '@/utils/commonStyles';
+import useAuthzStakingExecHelper from '@/custom-hooks/useAuthzStakingExecHelper';
+import { UnbondingEncode } from '@/txns/staking/unbonding';
+import useAddressConverter from '@/custom-hooks/useAddressConverter';
const UnbondingCard = ({
moniker,
@@ -50,13 +53,36 @@ const UnbondingCard = ({
const loading = useAppSelector(
(state: RootState) => state.staking.chains[chainID].cancelUnbondingTxStatus
);
+ const isAuthzMode = useAppSelector((state) => state.authz.authzModeEnabled);
+ const authzAddress = useAppSelector((state) => state.authz.authzAddress);
+ const { txAuthzCancelUnbond } = useAuthzStakingExecHelper();
+ const { convertAddress } = useAddressConverter();
+
useEffect(() => {
dispatch(resetCancelUnbondingTx({ chainID: chainID }));
}, []);
const onCancelUnbondingTx = () => {
+ const basicChainInfo = getChainInfo(chainID);
+ const delegator = convertAddress(chainID, authzAddress);
+ const msg = UnbondingEncode(
+ delegator,
+ validatorAddress,
+ amount * 10 ** currency.coinDecimals,
+ currency.coinMinimalDenom,
+ creationHeight
+ );
+ if (isAuthzMode) {
+ txAuthzCancelUnbond({
+ grantee: address,
+ granter: authzAddress,
+ chainID: chainID,
+ msg: msg,
+ });
+ return;
+ }
dispatch(
txCancelUnbonding({
- basicChainInfo: getChainInfo(chainID),
+ basicChainInfo: basicChainInfo,
delegator: address,
validator: validatorAddress,
amount: amount * 10 ** currency.coinDecimals,
diff --git a/frontend/src/custom-hooks/useAuthzStakingExecHelper.tsx b/frontend/src/custom-hooks/useAuthzStakingExecHelper.tsx
new file mode 100644
index 000000000..53ea10d2a
--- /dev/null
+++ b/frontend/src/custom-hooks/useAuthzStakingExecHelper.tsx
@@ -0,0 +1,334 @@
+import useAddressConverter from './useAddressConverter';
+import { useAppDispatch, useAppSelector } from './StateHooks';
+import { setError } from '@/store/features/common/commonSlice';
+import useGetChainInfo from './useGetChainInfo';
+import { txAuthzExec } from '@/store/features/authz/authzSlice';
+import { capitalizeFirstLetter } from '@/utils/util';
+import {
+ AuthzExecDelegateMsg,
+ AuthzExecMsgCancelUnbond,
+ AuthzExecMsgRestake,
+ AuthzExecReDelegateMsg,
+ AuthzExecUnDelegateMsg,
+ AuthzExecWithdrawRewardsMsg,
+} from '@/txns/authz/exec';
+import { msgDelegate } from '@/txns/staking/delegate';
+import { msgReDelegate } from '@/txns/staking/redelegate';
+import { DelegationsPairs } from '@/types/distribution';
+import { msgUnbonding } from '@/txns/staking/unbonding';
+
+export interface AuthzExecHelpDelegate {
+ grantee: string;
+ granter: string;
+ validator: string;
+ amount: number;
+ denom: string;
+ chainID: string;
+}
+
+export interface AuthzExecHelpReDelegate {
+ grantee: string;
+ granter: string;
+ srcValidator: string;
+ validator: string;
+ amount: number;
+ denom: string;
+ chainID: string;
+}
+
+export interface AuthzExecHelpWithdrawRewards {
+ grantee: string;
+ granter: string;
+ pairs: DelegationsPairs[];
+ chainID: string;
+}
+
+export interface AuthzExecHelpCancelUnbond {
+ grantee: string;
+ granter: string;
+ msg: Msg;
+ chainID: string;
+}
+
+export interface AuthzExecHelpRestake {
+ grantee: string;
+ granter: string;
+ msgs: Msg[];
+ chainID: string;
+}
+
+export const AUTHZ_VOTE_MSG = '/cosmos.gov.v1beta1.MsgVote';
+export const AUTHZ_DEPOSIT_MSG = '/cosmos.gov.v1beta1.MsgDeposit';
+const AUTHZ_WITHDRAW_MSG =
+ '/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward';
+
+const useAuthzStakingExecHelper = () => {
+ const { convertAddress } = useAddressConverter();
+ const dispatch = useAppDispatch();
+ const authzChains = useAppSelector((state) => state.authz.chains);
+ const { getChainInfo, getDenomInfo } = useGetChainInfo();
+
+ const txAuthzDelegate = (data: AuthzExecHelpDelegate) => {
+ const basicChainInfo = getChainInfo(data.chainID);
+ const address = convertAddress(data.chainID, data.granter);
+ const grants: Authorization[] =
+ authzChains?.[data.chainID]?.GrantsToMeAddressMapping?.[address] || [];
+ const haveGrant = grants.some((grant) => {
+ return (
+ // todo: stake Authorization
+ (grant.authorization['@type'] ===
+ '/cosmos.authz.v1beta1.GenericAuthorization' &&
+ grant.authorization.msg === msgReDelegate) ||
+ (grant.authorization['@type'] ===
+ '/cosmos.staking.v1beta1.StakeAuthorization' &&
+ grant.authorization.authorization_type ===
+ 'AUTHORIZATION_TYPE_DELEGATE')
+ );
+ });
+ if (!haveGrant) {
+ dispatch(
+ setError({
+ type: 'error',
+ message: `You don't have permission to Delegate on ${capitalizeFirstLetter(
+ basicChainInfo.chainName
+ )} from this account`,
+ })
+ );
+ } else {
+ const { minimalDenom } = getDenomInfo(data.chainID);
+ const msg = AuthzExecDelegateMsg(
+ data.grantee,
+ address,
+ data.validator,
+ data.amount,
+ data.denom
+ );
+ dispatch(
+ txAuthzExec({
+ basicChainInfo,
+ msgs: [msg],
+ metaData: '',
+ feeDenom: minimalDenom,
+ })
+ );
+ }
+ };
+
+ const txAuthzUnDelegate = (data: AuthzExecHelpDelegate) => {
+ const basicChainInfo = getChainInfo(data.chainID);
+ const address = convertAddress(data.chainID, data.granter);
+ const grants: Authorization[] =
+ authzChains?.[data.chainID]?.GrantsToMeAddressMapping?.[address] || [];
+ const haveGrant = grants.some((grant) => {
+ return (
+ // todo: stake Authorization
+ (grant.authorization['@type'] ===
+ '/cosmos.authz.v1beta1.GenericAuthorization' &&
+ grant.authorization.msg === msgReDelegate) ||
+ (grant.authorization['@type'] ===
+ '/cosmos.staking.v1beta1.StakeAuthorization' &&
+ grant.authorization.authorization_type ===
+ 'AUTHORIZATION_TYPE_UNDELEGATE')
+ );
+ });
+ if (!haveGrant) {
+ dispatch(
+ setError({
+ type: 'error',
+ message: `You don't have permission to UnDelegate on ${capitalizeFirstLetter(
+ basicChainInfo.chainName
+ )} from this account`,
+ })
+ );
+ } else {
+ const { minimalDenom } = getDenomInfo(data.chainID);
+ const msg = AuthzExecUnDelegateMsg(
+ data.grantee,
+ address,
+ data.validator,
+ data.amount,
+ data.denom
+ );
+ dispatch(
+ txAuthzExec({
+ basicChainInfo,
+ msgs: [msg],
+ metaData: '',
+ feeDenom: minimalDenom,
+ })
+ );
+ }
+ };
+
+ const txAuthzReDelegate = (data: AuthzExecHelpReDelegate) => {
+ const basicChainInfo = getChainInfo(data.chainID);
+ const address = convertAddress(data.chainID, data.granter);
+ const grants: Authorization[] =
+ authzChains?.[data.chainID]?.GrantsToMeAddressMapping?.[address] || [];
+ const haveGrant = grants.some((grant) => {
+ return (
+ // todo: stake Authorization
+ (grant.authorization['@type'] ===
+ '/cosmos.authz.v1beta1.GenericAuthorization' &&
+ grant.authorization.msg === msgReDelegate) ||
+ (grant.authorization['@type'] ===
+ '/cosmos.staking.v1beta1.StakeAuthorization' &&
+ grant.authorization.authorization_type ===
+ 'AUTHORIZATION_TYPE_REDELEGATE')
+ );
+ });
+ if (!haveGrant) {
+ dispatch(
+ setError({
+ type: 'error',
+ message: `You don't have permission to ReDelegate on ${capitalizeFirstLetter(
+ basicChainInfo.chainName
+ )} from this account`,
+ })
+ );
+ } else {
+ const { minimalDenom } = getDenomInfo(data.chainID);
+ const msg = AuthzExecReDelegateMsg(
+ data.grantee,
+ address,
+ data.srcValidator,
+ data.validator,
+ data.amount,
+ data.denom
+ );
+ dispatch(
+ txAuthzExec({
+ basicChainInfo,
+ msgs: [msg],
+ metaData: '',
+ feeDenom: minimalDenom,
+ })
+ );
+ }
+ };
+
+ const txAuthzClaim = (data: AuthzExecHelpWithdrawRewards) => {
+ const basicChainInfo = getChainInfo(data.chainID);
+ const address = convertAddress(data.chainID, data.granter);
+ const grants: Authorization[] =
+ authzChains?.[data.chainID]?.GrantsToMeAddressMapping?.[address] || [];
+ const haveGrant = grants.some((grant) => {
+ return (
+ // todo: stake Authorization
+ grant.authorization['@type'] ===
+ '/cosmos.authz.v1beta1.GenericAuthorization' &&
+ grant.authorization.msg === AUTHZ_WITHDRAW_MSG
+ );
+ });
+ if (!haveGrant) {
+ dispatch(
+ setError({
+ type: 'error',
+ message: `You don't have required permissions to do this action on ${capitalizeFirstLetter(
+ basicChainInfo.chainName
+ )} from this account`,
+ })
+ );
+ } else {
+ const { minimalDenom } = getDenomInfo(data.chainID);
+ const pairs = data.pairs.map((pair) => {
+ pair.delegator = address;
+ return pair;
+ });
+ const msg = AuthzExecWithdrawRewardsMsg(data.grantee, pairs);
+ dispatch(
+ txAuthzExec({
+ basicChainInfo,
+ msgs: [msg],
+ metaData: '',
+ feeDenom: minimalDenom,
+ })
+ );
+ }
+ };
+
+ const txAuthzCancelUnbond = (data: AuthzExecHelpCancelUnbond) => {
+ const basicChainInfo = getChainInfo(data.chainID);
+ const address = convertAddress(data.chainID, data.granter);
+ const grants: Authorization[] =
+ authzChains?.[data.chainID]?.GrantsToMeAddressMapping?.[address] || [];
+ const haveGrant = grants.some((grant) => {
+ return (
+ grant.authorization['@type'] ===
+ '/cosmos.authz.v1beta1.GenericAuthorization' &&
+ grant.authorization.msg === msgUnbonding
+ );
+ });
+ if (!haveGrant) {
+ dispatch(
+ setError({
+ type: 'error',
+ message: `You don't have required permissions to do this action on ${capitalizeFirstLetter(
+ basicChainInfo.chainName
+ )} from this account`,
+ })
+ );
+ } else {
+ const { minimalDenom } = getDenomInfo(data.chainID);
+ const msg = AuthzExecMsgCancelUnbond(data.msg, data.grantee);
+ dispatch(
+ txAuthzExec({
+ basicChainInfo,
+ msgs: [msg],
+ metaData: '',
+ feeDenom: minimalDenom,
+ })
+ );
+ }
+ };
+
+ const txAuthzRestake = (data: AuthzExecHelpRestake) => {
+ const basicChainInfo = getChainInfo(data.chainID);
+ const address = convertAddress(data.chainID, data.granter);
+ const grants: Authorization[] =
+ authzChains?.[data.chainID]?.GrantsToMeAddressMapping?.[address] || [];
+ const haveGrant = grants.some((grant) => {
+ return (
+ (grant.authorization['@type'] ===
+ '/cosmos.authz.v1beta1.GenericAuthorization' &&
+ grant.authorization.msg === msgDelegate) ||
+ (grant.authorization['@type'] ===
+ '/cosmos.staking.v1beta1.StakeAuthorization' &&
+ grant.authorization.authorization_type ===
+ 'AUTHORIZATION_TYPE_DELEGATE')
+ );
+ });
+ if (!haveGrant) {
+ dispatch(
+ setError({
+ type: 'error',
+ message: `You don't have required permissions to do this action on ${capitalizeFirstLetter(
+ basicChainInfo.chainName
+ )} from this account`,
+ })
+ );
+ } else {
+ const { minimalDenom } = getDenomInfo(data.chainID);
+ const msg = AuthzExecMsgRestake(data.msgs, data.grantee);
+ dispatch(
+ txAuthzExec({
+ basicChainInfo,
+ msgs: [msg],
+ metaData: '',
+ feeDenom: minimalDenom,
+ })
+ );
+ }
+ };
+
+ return {
+ txAuthzDelegate,
+ txAuthzUnDelegate,
+ txAuthzReDelegate,
+ txAuthzClaim,
+ txAuthzCancelUnbond,
+ txAuthzRestake,
+ };
+};
+
+export default useAuthzStakingExecHelper;
\ No newline at end of file
diff --git a/frontend/src/custom-hooks/useGetTxInputs.ts b/frontend/src/custom-hooks/useGetTxInputs.ts
index ada399b89..baa460df7 100644
--- a/frontend/src/custom-hooks/useGetTxInputs.ts
+++ b/frontend/src/custom-hooks/useGetTxInputs.ts
@@ -7,6 +7,8 @@ import {
import useGetChainInfo from './useGetChainInfo';
import { TxReStakeInputs } from '@/types/staking';
import { Delegate } from '@/txns/staking';
+import useAddressConverter from './useAddressConverter';
+import { EncodeDelegate } from '@/txns/staking/delegate';
const useGetTxInputs = () => {
const stakingChains = useAppSelector(
@@ -15,6 +17,12 @@ const useGetTxInputs = () => {
const rewardsChains = useAppSelector(
(state: RootState) => state.distribution.chains
);
+ const authzRewardsChains = useAppSelector(
+ (state) => state.distribution.authzChains
+ );
+ const { convertAddress } = useAddressConverter();
+ const isAuthzMode = useAppSelector((state) => state.authz.authzModeEnabled);
+ const authzAddress = useAppSelector((state) => state.authz.authzAddress);
const { getDenomInfo, getChainInfo } = useGetChainInfo();
const txWithdrawAllRewardsInputs = (
@@ -225,6 +233,58 @@ const useGetTxInputs = () => {
return transfersRequestInputs;
};
+ const txAuthzRestakeMsgs = (chainID: string): Msg[] => {
+ const { minimalDenom } = getDenomInfo(chainID);
+ const rewards = authzRewardsChains[chainID]?.delegatorRewards;
+ const msgs: Msg[] = [];
+ if (!isAuthzMode) return [];
+ const delegator = convertAddress(chainID, authzAddress);
+
+ for (const delegation of rewards?.list || []) {
+ for (const reward of delegation.reward || []) {
+ if (reward.denom === minimalDenom) {
+ const amount = parseInt(reward.amount);
+ if (amount < 1) continue;
+ const msg = EncodeDelegate(
+ delegator,
+ delegation.validator_address,
+ amount,
+ minimalDenom
+ );
+ msgs.push(msg);
+ }
+ }
+ }
+ return msgs;
+ };
+
+ const txAuthzRestakeValidatorMsgs = (
+ chainID: string,
+ validatorAddress: string
+ ): Msg[] => {
+ if (!isAuthzMode) return [];
+ const { minimalDenom } = getDenomInfo(chainID);
+ const rewards = authzRewardsChains[chainID]?.delegatorRewards;
+ const msgs: Msg[] = [];
+ const delegator = convertAddress(chainID, authzAddress);
+
+ for (const delegation of rewards.list) {
+ if (delegation?.validator_address === validatorAddress) {
+ for (const reward of delegation.reward || []) {
+ if (reward.denom === minimalDenom) {
+ const amount = +reward.amount;
+ if (amount < 1) continue;
+ msgs.push(
+ EncodeDelegate(delegator, validatorAddress, amount, minimalDenom)
+ );
+ }
+ }
+ }
+ }
+
+ return msgs;
+ };
+
return {
txWithdrawAllRewardsInputs,
txRestakeInputs,
@@ -233,6 +293,8 @@ const useGetTxInputs = () => {
txSendInputs,
getVoteTxInputs,
txTransferInputs,
+ txAuthzRestakeMsgs,
+ txAuthzRestakeValidatorMsgs,
};
};
diff --git a/frontend/src/custom-hooks/useInitAuthzForOverview.tsx b/frontend/src/custom-hooks/useInitAuthzForOverview.tsx
new file mode 100644
index 000000000..2e81aa088
--- /dev/null
+++ b/frontend/src/custom-hooks/useInitAuthzForOverview.tsx
@@ -0,0 +1,53 @@
+import { useEffect } from 'react';
+import { useAppDispatch, useAppSelector } from './StateHooks';
+import useAddressConverter from './useAddressConverter';
+import { getAuthzBalances } from '@/store/features/bank/bankSlice';
+import {
+ getAuthzDelegations,
+ getAuthzUnbonding,
+} from '@/store/features/staking/stakeSlice';
+import { getAuthzDelegatorTotalRewards } from '@/store/features/distribution/distributionSlice';
+
+const useInitAuthzForOverview = (chainIDs: string[]) => {
+ const authzAddress = useAppSelector((state) => state.authz.authzAddress);
+ const networks = useAppSelector((state) => state.wallet.networks);
+ const { convertAddress } = useAddressConverter();
+ const dispatch = useAppDispatch();
+
+ useEffect(() => {
+ if (authzAddress) {
+ chainIDs.forEach((chainID) => {
+ const allChainInfo = networks[chainID];
+ const chainInfo = allChainInfo.network;
+ const address = convertAddress(chainID, authzAddress);
+ const minimalDenom =
+ allChainInfo.network.config.stakeCurrency.coinMinimalDenom;
+ const basicChainInputs = {
+ baseURL: chainInfo.config.rest,
+ address,
+ chainID,
+ };
+
+ dispatch(getAuthzBalances(basicChainInputs));
+ dispatch(getAuthzDelegations(basicChainInputs));
+ dispatch(
+ getAuthzDelegatorTotalRewards({
+ baseURL: chainInfo.config.rest,
+ address: address,
+ chainID: chainID,
+ denom: minimalDenom,
+ })
+ );
+ dispatch(
+ getAuthzUnbonding({
+ baseURL: chainInfo.config.rest,
+ address: address,
+ chainID,
+ })
+ );
+ });
+ }
+ }, [authzAddress]);
+};
+
+export default useInitAuthzForOverview;
diff --git a/frontend/src/custom-hooks/useInitAuthzStaking.tsx b/frontend/src/custom-hooks/useInitAuthzStaking.tsx
new file mode 100644
index 000000000..e7bbb7767
--- /dev/null
+++ b/frontend/src/custom-hooks/useInitAuthzStaking.tsx
@@ -0,0 +1,59 @@
+import { useEffect } from 'react';
+import { useAppDispatch, useAppSelector } from './StateHooks';
+import useAddressConverter from './useAddressConverter';
+import useGetChainInfo from './useGetChainInfo';
+import {
+ getAuthzDelegations,
+ getAuthzUnbonding,
+} from '@/store/features/staking/stakeSlice';
+import { getAuthzDelegatorTotalRewards } from '@/store/features/distribution/distributionSlice';
+import { getAuthzBalances } from '@/store/features/bank/bankSlice';
+
+const useInitAuthzStaking = (chainIDs: string[]) => {
+ const dispatch = useAppDispatch();
+ const authzAddress = useAppSelector((state) => state.authz.authzAddress);
+
+ const { convertAddress } = useAddressConverter();
+ const { getChainInfo, getDenomInfo } = useGetChainInfo();
+
+ useEffect(() => {
+ if (authzAddress) {
+ chainIDs.forEach((chainID) => {
+ const { baseURL } = getChainInfo(chainID);
+ const { minimalDenom } = getDenomInfo(chainID);
+ const address = convertAddress(chainID, authzAddress);
+ dispatch(
+ getAuthzDelegations({
+ baseURL,
+ address,
+ chainID,
+ })
+ );
+ dispatch(
+ getAuthzUnbonding({
+ baseURL,
+ address,
+ chainID,
+ })
+ );
+ dispatch(
+ getAuthzDelegatorTotalRewards({
+ baseURL,
+ address,
+ chainID,
+ denom: minimalDenom,
+ })
+ );
+ dispatch(
+ getAuthzBalances({
+ baseURL,
+ address,
+ chainID,
+ })
+ );
+ });
+ }
+ }, [authzAddress]);
+};
+
+export default useInitAuthzStaking;
diff --git a/frontend/src/store/features/authz/authzSlice.ts b/frontend/src/store/features/authz/authzSlice.ts
new file mode 100644
index 000000000..c2049f8c3
--- /dev/null
+++ b/frontend/src/store/features/authz/authzSlice.ts
@@ -0,0 +1,433 @@
+'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;
+ };
+}
+
+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 }) => {
+ 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) {
+ 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 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);
+ },
+ },
+ 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) => {
+ 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) => {
+ 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;
+ });
+ },
+});
+
+export const { enableAuthzMode, exitAuthzMode, resetState } =
+ authzSlice.actions;
+
+
+export default authzSlice.reducer;
\ No newline at end of file
diff --git a/frontend/src/store/features/distribution/distributionSlice.ts b/frontend/src/store/features/distribution/distributionSlice.ts
index e9d0be115..d86342c44 100644
--- a/frontend/src/store/features/distribution/distributionSlice.ts
+++ b/frontend/src/store/features/distribution/distributionSlice.ts
@@ -195,6 +195,45 @@ export const distSlice = createSlice({
action.error.message || '';
}
});
+<<<<<<< HEAD
+=======
+
+ builder
+ .addCase(getAuthzDelegatorTotalRewards.pending, (state, action) => {
+ const chainID = action.meta?.arg?.chainID;
+ if (!state.authzChains[chainID])
+ state.authzChains[chainID] = cloneDeep(initialState.defaultState);
+ state.authzChains[chainID].delegatorRewards.status = TxStatus.PENDING;
+ state.authzChains[chainID].delegatorRewards.errMsg = '';
+ state.authzChains[chainID].delegatorRewards.totalRewards = 0;
+ state.authzChains[chainID].delegatorRewards.list = [];
+ state.authzChains[chainID].delegatorRewards.pagination = {};
+ })
+ .addCase(getAuthzDelegatorTotalRewards.fulfilled, (state, action) => {
+ const chainID = action.meta?.arg?.chainID;
+ const denom = action.meta.arg.denom;
+ if (state.authzChains[chainID]) {
+ state.authzChains[chainID].delegatorRewards.status = TxStatus.IDLE;
+ state.authzChains[chainID].delegatorRewards.list =
+ action.payload.data.rewards;
+ const totalRewardsList = action?.payload?.data?.total;
+ state.authzChains[chainID].delegatorRewards.totalRewards =
+ getDenomBalance(totalRewardsList, denom);
+ state.authzChains[chainID].delegatorRewards.pagination =
+ action.payload.data.pagination;
+ state.authzChains[chainID].delegatorRewards.errMsg = '';
+ }
+ })
+ .addCase(getAuthzDelegatorTotalRewards.rejected, (state, action) => {
+ const chainID = action.meta?.arg?.chainID;
+ if (state.authzChains[chainID]) {
+ state.authzChains[chainID].delegatorRewards.status =
+ TxStatus.REJECTED;
+ state.authzChains[chainID].delegatorRewards.errMsg =
+ action.error.message || '';
+ }
+ });
+>>>>>>> a885705 (feat: integrate authz with staking and overview (#1092))
builder
.addCase(txWithdrawAllRewards.pending, (state, action) => {
const chainID = action.meta?.arg?.chainID;
diff --git a/frontend/src/store/features/staking/stakeSlice.ts b/frontend/src/store/features/staking/stakeSlice.ts
index 757370893..49acaa27d 100644
--- a/frontend/src/store/features/staking/stakeSlice.ts
+++ b/frontend/src/store/features/staking/stakeSlice.ts
@@ -962,6 +962,47 @@ export const stakeSlice = createSlice({
});
builder
+<<<<<<< HEAD
+=======
+ .addCase(getAuthzUnbonding.pending, (state, action) => {
+ const { chainID } = action.meta.arg;
+ if (!state.authz.chains[chainID])
+ state.authz.chains[chainID] = cloneDeep(state.defaultState);
+ state.authz.chains[chainID].unbonding.status = TxStatus.PENDING;
+ state.authz.chains[chainID].unbonding.errMsg = '';
+ })
+ .addCase(getAuthzUnbonding.fulfilled, (state, action) => {
+ const { chainID } = action.meta.arg;
+ const unbonding_responses = action.payload.data.unbonding_responses;
+ let totalUnbonded = 0.0;
+ if (unbonding_responses?.length) {
+ unbonding_responses.forEach((unbondingEntries) => {
+ unbondingEntries.entries.forEach((unbondingEntry) => {
+ totalUnbonded += +unbondingEntry.balance;
+ });
+ });
+ state.authz.chains[chainID].unbonding.totalUnbonded = totalUnbonded;
+ if (unbonding_responses[0].entries.length) {
+ state.authz.chains[chainID].unbonding.hasUnbonding = true;
+ state.authz.hasUnbonding = true;
+ }
+ }
+ state.authz.chains[chainID].unbonding.status = TxStatus.IDLE;
+ state.authz.chains[chainID].unbonding.unbonding.unbonding_responses =
+ unbonding_responses;
+ state.authz.chains[chainID].unbonding.pagination =
+ action.payload.data.pagination;
+ state.authz.chains[chainID].unbonding.errMsg = '';
+ })
+ .addCase(getAuthzUnbonding.rejected, (state, action) => {
+ const { chainID } = action.meta.arg;
+ state.authz.chains[chainID].unbonding.status = TxStatus.REJECTED;
+ state.authz.chains[chainID].unbonding.errMsg =
+ action.error.message || '';
+ });
+
+ builder
+>>>>>>> a885705 (feat: integrate authz with staking and overview (#1092))
.addCase(txDelegate.pending, (state, action) => {
const { chainID } = action.meta.arg.basicChainInfo;
state.chains[chainID].tx.status = TxStatus.PENDING;
diff --git a/frontend/src/txns/authz/exec.ts b/frontend/src/txns/authz/exec.ts
new file mode 100644
index 000000000..861943391
--- /dev/null
+++ b/frontend/src/txns/authz/exec.ts
@@ -0,0 +1,258 @@
+import { DelegationsPairs } from '@/types/distribution';
+import { MsgExec } from 'cosmjs-types/cosmos/authz/v1beta1/tx';
+import { MsgSend } from 'cosmjs-types/cosmos/bank/v1beta1/tx';
+import { Coin } from 'cosmjs-types/cosmos/base/v1beta1/coin';
+import { MsgWithdrawDelegatorReward } from 'cosmjs-types/cosmos/distribution/v1beta1/tx';
+import { VoteOption } from 'cosmjs-types/cosmos/gov/v1beta1/gov';
+import { MsgDeposit, MsgVote } from 'cosmjs-types/cosmos/gov/v1beta1/tx';
+import {
+ MsgBeginRedelegate,
+ MsgDelegate,
+ MsgUndelegate,
+} from 'cosmjs-types/cosmos/staking/v1beta1/tx';
+
+
+const msgSendTypeUrl = '/cosmos.bank.v1beta1.MsgSend';
+export const msgAuthzExecypeUrl = '/cosmos.authz.v1beta1.MsgExec';
+const msgVote = '/cosmos.gov.v1beta1.MsgVote';
+const msgDeposit = '/cosmos.gov.v1beta1.MsgDeposit';
+const msgWithdrawRewards =
+ '/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward';
+const msgDelegate = '/cosmos.staking.v1beta1.MsgDelegate';
+const msgUnDelegate = '/cosmos.staking.v1beta1.MsgUndelegate';
+const msgReDelegate = '/cosmos.staking.v1beta1.MsgBeginRedelegate';
+
+export const serialize = () => {
+ return 'Executed an authz permission';
+};
+
+export function AuthzExecSendMsg(
+ grantee: string,
+ from: string,
+ to: string,
+ amount: number,
+ denom: string
+): Msg {
+ return {
+ typeUrl: msgAuthzExecypeUrl,
+ value: MsgExec.fromPartial({
+ grantee: grantee,
+ msgs: [
+ {
+ typeUrl: msgSendTypeUrl,
+ value: MsgSend.encode({
+ fromAddress: from,
+ toAddress: to,
+ amount: [
+ {
+ denom: denom,
+ amount: String(amount),
+ },
+ ],
+ }).finish(),
+ },
+ ],
+ }),
+ };
+}
+
+export function AuthzExecVoteMsg(
+ grantee: string,
+ proposalId: number,
+ option: VoteOption,
+ granter: string
+): Msg {
+ return {
+ typeUrl: msgAuthzExecypeUrl,
+ value: MsgExec.fromPartial({
+ grantee: grantee,
+ msgs: [
+ {
+ typeUrl: msgVote,
+ value: MsgVote.encode({
+ option: option,
+ proposalId: BigInt(proposalId),
+ voter: granter,
+ }).finish(),
+ },
+ ],
+ }),
+ };
+}
+
+export function AuthzExecDepositMsg(
+ grantee: string,
+ proposalId: number,
+ granter: string,
+ amount: number,
+ denom: string
+): Msg {
+ return {
+ typeUrl: msgAuthzExecypeUrl,
+ value: MsgExec.fromPartial({
+ grantee: grantee,
+ msgs: [
+ {
+ typeUrl: msgDeposit,
+ value: MsgDeposit.encode({
+ proposalId: BigInt(proposalId),
+ depositor: granter,
+ amount: [{ amount: '' + amount, denom }],
+ }).finish(),
+ },
+ ],
+ }),
+ };
+}
+
+export function AuthzExecWithdrawRewardsMsg(
+ grantee: string,
+
+ payload: DelegationsPairs[]
+): Msg {
+ const msgs = [];
+ for (let i = 0; i < payload.length; i++) {
+ msgs.push({
+ typeUrl: msgWithdrawRewards,
+ value: MsgWithdrawDelegatorReward.encode({
+ delegatorAddress: payload[i].delegator,
+ validatorAddress: payload[i].validator,
+ }).finish(),
+ });
+ }
+ return {
+ typeUrl: msgAuthzExecypeUrl,
+ value: MsgExec.fromPartial({
+ grantee: grantee,
+ msgs: msgs,
+ }),
+ };
+}
+
+export function AuthzExecDelegateMsg(
+ grantee: string,
+ granter: string,
+ validator: string,
+ amount: number,
+ denom: string
+): Msg {
+ return {
+ typeUrl: msgAuthzExecypeUrl,
+ value: MsgExec.fromPartial({
+ grantee: grantee,
+ msgs: [
+ {
+ typeUrl: msgDelegate,
+ value: MsgDelegate.encode({
+ delegatorAddress: granter,
+ validatorAddress: validator,
+ amount: Coin.fromPartial({
+ amount: String(amount),
+ denom: denom,
+ }),
+ }).finish(),
+ },
+ ],
+ }),
+ };
+}
+
+export function AuthzExecReDelegateMsg(
+ grantee: string,
+ granter: string,
+ src: string,
+ dest: string,
+ amount: number,
+ denom: string
+): Msg {
+ return {
+ typeUrl: msgAuthzExecypeUrl,
+ value: MsgExec.fromPartial({
+ grantee: grantee,
+ msgs: [
+ {
+ typeUrl: msgReDelegate,
+ value: MsgBeginRedelegate.encode({
+ validatorDstAddress: dest,
+ delegatorAddress: granter,
+ validatorSrcAddress: src,
+ amount: Coin.fromPartial({
+ amount: String(amount),
+ denom: denom,
+ }),
+ }).finish(),
+ },
+ ],
+ }),
+ };
+}
+
+export function AuthzExecUnDelegateMsg(
+ grantee: string,
+ granter: string,
+ validator: string,
+ amount: number,
+ denom: string
+): Msg {
+ return {
+ typeUrl: msgAuthzExecypeUrl,
+ value: MsgExec.fromPartial({
+ grantee: grantee,
+ msgs: [
+ {
+ typeUrl: msgUnDelegate,
+ value: MsgUndelegate.encode({
+ validatorAddress: validator,
+ delegatorAddress: granter,
+ amount: Coin.fromPartial({
+ amount: String(amount),
+ denom: denom,
+ }),
+ }).finish(),
+ },
+ ],
+ }),
+ };
+}
+
+export function AuthzExecMsgRevoke(feegrant: Msg, grantee: string): Msg {
+ return {
+ typeUrl: msgAuthzExecypeUrl,
+ value: MsgExec.fromPartial({
+ grantee: grantee,
+ msgs: [feegrant],
+ }),
+ };
+}
+
+export function AuthzExecMsgFeegrant(feegrant: Msg, grantee: string): Msg {
+ return {
+ typeUrl: msgAuthzExecypeUrl,
+ value: MsgExec.fromPartial({
+ grantee: grantee,
+ msgs: [feegrant],
+ }),
+ };
+}
+
+export function AuthzExecMsgCancelUnbond(unbond: Msg, grantee: string): Msg {
+ return {
+ typeUrl: msgAuthzExecypeUrl,
+ value: MsgExec.fromPartial({
+ grantee: grantee,
+ msgs: [unbond],
+ }),
+ };
+}
+
+// delegate written again in a different way
+export function AuthzExecMsgRestake(delegations: Msg[], grantee: string): Msg {
+
+ return {
+ typeUrl: msgAuthzExecypeUrl,
+ value: MsgExec.fromPartial({
+ grantee: grantee,
+ msgs: delegations,
+ }),
+ };
+}
diff --git a/frontend/src/txns/staking/delegate.ts b/frontend/src/txns/staking/delegate.ts
index aa80c7167..c26467082 100644
--- a/frontend/src/txns/staking/delegate.ts
+++ b/frontend/src/txns/staking/delegate.ts
@@ -23,6 +23,25 @@ export function Delegate(
};
}
+export function EncodeDelegate(
+ delegator: string,
+ validator: string,
+ amount: number,
+ denom: string
+): Msg {
+ return {
+ typeUrl: msgDelegate,
+ value: MsgDelegate.encode({
+ delegatorAddress: delegator,
+ validatorAddress: validator,
+ amount: Coin.fromPartial({
+ amount: String(amount),
+ denom: denom,
+ }),
+ }).finish(),
+ };
+}
+
export function serialize(msg: Msg): string {
const delegatorAddress = msg.value.delegatorAddress;
const validatorAddress = msg.value.validatorAddress;
diff --git a/frontend/src/txns/staking/unbonding.ts b/frontend/src/txns/staking/unbonding.ts
index 15bde0857..35348132a 100644
--- a/frontend/src/txns/staking/unbonding.ts
+++ b/frontend/src/txns/staking/unbonding.ts
@@ -1,14 +1,15 @@
import { Coin } from 'cosmjs-types/cosmos/base/v1beta1/coin';
import { MsgCancelUnbondingDelegation } from 'cosmjs-types/cosmos/staking/v1beta1/tx';
-export const msgUnbonding = '/cosmos.staking.v1beta1.MsgCancelUnbondingDelegation';
+export const msgUnbonding =
+ '/cosmos.staking.v1beta1.MsgCancelUnbondingDelegation';
export function Unbonding(
delegator: string,
validator: string,
amount: number,
denom: string,
- creationHeight: string,
+ creationHeight: string
): Msg {
return {
typeUrl: msgUnbonding,
@@ -23,3 +24,24 @@ export function Unbonding(
}),
};
}
+
+export function UnbondingEncode(
+ delegator: string,
+ validator: string,
+ amount: number,
+ denom: string,
+ creationHeight: string
+): Msg {
+ return {
+ typeUrl: msgUnbonding,
+ value: MsgCancelUnbondingDelegation.encode({
+ delegatorAddress: delegator,
+ validatorAddress: validator,
+ amount: Coin.fromPartial({
+ amount: String(amount),
+ denom: denom,
+ }),
+ creationHeight: BigInt(creationHeight),
+ }).finish(),
+ };
+}
diff --git a/frontend/src/utils/transaction.ts b/frontend/src/utils/transaction.ts
index 272c19669..f1491a605 100644
--- a/frontend/src/utils/transaction.ts
+++ b/frontend/src/utils/transaction.ts
@@ -23,6 +23,8 @@ import {
msgTransfer,
serialize as serializeMsgTransfer,
} from '@/txns/ibc/transfer';
+import { serialize as serializeMsgExec } from '@/txns/authz/exec';
+import { msgAuthzExecypeUrl } from '@/txns/authz/exec';
export function NewTransaction(
txResponse: ParsedTxResponse,
@@ -65,6 +67,8 @@ export const MsgType = (msg: string): string => {
return 'Claim';
case msgTransfer:
return 'IBC';
+ case msgAuthzExecypeUrl:
+ return 'Authz-permission';
default:
return 'Todo: add type';
}
@@ -85,6 +89,8 @@ export const serializeMsg = (msg: Msg): string => {
return serializeMsgClaim(msg);
case msgTransfer:
return serializeMsgTransfer(msg);
+ case msgAuthzExecypeUrl:
+ return serializeMsgExec();
default:
return `Todo: serialize message ${msg.typeUrl}`;
}