diff --git a/frontend/src/app/(routes)/(overview)/overview-components/Asset.tsx b/frontend/src/app/(routes)/(overview)/overview-components/Asset.tsx index daaf7d01a..19bd2f6a0 100644 --- a/frontend/src/app/(routes)/(overview)/overview-components/Asset.tsx +++ b/frontend/src/app/(routes)/(overview)/overview-components/Asset.tsx @@ -11,6 +11,9 @@ import { RootState } from '@/store/store'; import { CircularProgress, Tooltip } from '@mui/material'; import { setError } from '@/store/features/common/commonSlice'; import { capitalize } from 'lodash'; +import useGetChainInfo from '@/custom-hooks/useGetChainInfo'; +import { DelegationsPairs } from '@/types/distribution'; +import useAuthzStakingExecHelper from '@/custom-hooks/useAuthzStakingExecHelper'; const Asset = ({ asset, @@ -27,11 +30,38 @@ const Asset = ({ (state: RootState) => state.staking.chains[asset.chainID]?.reStakeTxStatus || TxStatus.IDLE ); + const authzRewards = useAppSelector( + (state) => state.distribution.authzChains + ); + const isAuthzMode = useAppSelector((state) => state.authz.authzModeEnabled); + const authzAddress = useAppSelector((state) => state.authz.authzAddress); + const { getChainInfo } = useGetChainInfo(); const dispatch = useAppDispatch(); - const { txWithdrawAllRewardsInputs, txRestakeInputs } = useGetTxInputs(); + const { txWithdrawAllRewardsInputs, txRestakeInputs, txAuthzRestakeMsgs } = + useGetTxInputs(); + const { txAuthzClaim, txAuthzRestake } = useAuthzStakingExecHelper(); const claim = (chainID: string) => { + if (isAuthzMode) { + 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({ @@ -58,6 +88,17 @@ const Asset = ({ }; const claimAndStake = (chainID: string) => { + if (isAuthzMode) { + const { address } = getChainInfo(chainID); + const msgs = txAuthzRestakeMsgs(chainID); + txAuthzRestake({ + grantee: address, + granter: authzAddress, + msgs: msgs, + chainID: chainID, + }); + return; + } if (txRestakeStatus === TxStatus.PENDING) { dispatch( setError({ diff --git a/frontend/src/app/(routes)/(overview)/overview-components/AssetsTable.tsx b/frontend/src/app/(routes)/(overview)/overview-components/AssetsTable.tsx index b352756b4..68f0dab04 100644 --- a/frontend/src/app/(routes)/(overview)/overview-components/AssetsTable.tsx +++ b/frontend/src/app/(routes)/(overview)/overview-components/AssetsTable.tsx @@ -6,23 +6,37 @@ import { CircularProgress } from '@mui/material'; import NoAssets from '@/components/illustrations/NoAssets'; const AssetsTable = ({ chainIDs }: { chainIDs: string[] }) => { - const [sortedAssets] = useSortedAssets(chainIDs, { + const isAuthzMode = useAppSelector((state) => state.authz.authzModeEnabled); + const [sortedAssets, authzSortedAssets] = useSortedAssets(chainIDs, { showAvailable: true, showRewards: true, showStaked: true, }); + + const assets = isAuthzMode ? authzSortedAssets : sortedAssets; + const balancesLoading = useAppSelector( (state) => state.bank.balancesLoading > 0 ); const delegationsLoading = useAppSelector( (state) => state.staking.delegationsLoading > 0 ); + const authzBalanceLoading = useAppSelector( + (state) => state.bank.authz.balancesLoading > 0 + ); + const authzDelegationsLoading = useAppSelector( + (state) => state.staking.authz.delegationsLoading > 0 + ); + + const loading = !isAuthzMode && (balancesLoading || delegationsLoading); + const authzLoading = + isAuthzMode && (authzBalanceLoading || authzDelegationsLoading); return (
- {sortedAssets.length ? ( + {assets.length ? (
@@ -60,7 +74,7 @@ const AssetsTable = ({ chainIDs }: { chainIDs: string[] }) => { - {sortedAssets.map((asset) => ( + {assets.map((asset) => ( { ) : (
- {balancesLoading || delegationsLoading ? ( + {loading || authzLoading ? ( ) : ( diff --git a/frontend/src/app/(routes)/(overview)/overview-components/History.tsx b/frontend/src/app/(routes)/(overview)/overview-components/History.tsx index d35a2e6a1..4d1a40a16 100644 --- a/frontend/src/app/(routes)/(overview)/overview-components/History.tsx +++ b/frontend/src/app/(routes)/(overview)/overview-components/History.tsx @@ -8,6 +8,7 @@ import { useAppSelector } from '@/custom-hooks/StateHooks'; import { RootState } from '@/store/store'; import { useRouter } from 'next/navigation'; import NoTransactions from '@/components/illustrations/NoTransactions'; +import useGetAuthzAssetsAmount from '../../../../custom-hooks/useGetAuthzAssetsAmount'; const History = ({ chainIDs }: { chainIDs: string[] }) => { return ( @@ -32,6 +33,7 @@ export default History; const Balance = ({ chainIDs }: { chainIDs: string[] }) => { const router = useRouter(); const nameToChainIDs = useAppSelector((state) => state.wallet.nameToChainIDs); + const isAuthzMode = useAppSelector((state) => state.authz.authzModeEnabled); const getPath = (chainIDs: string[], module: string) => { if (chainIDs.length !== 1) { return '/' + module; @@ -42,7 +44,14 @@ const Balance = ({ chainIDs }: { chainIDs: string[] }) => { }); return '/' + module + '/' + curChainName; }; - const [staked, available, rewards] = useGetAssetsAmount(chainIDs); + + const [myStaked, myAvailable, myRewards] = useGetAssetsAmount(chainIDs); + const [authzStaked, authzAvailable, authzRewards] = + useGetAuthzAssetsAmount(chainIDs); + const staked = isAuthzMode ? authzStaked : myStaked; + const available = isAuthzMode ? authzAvailable : myAvailable; + const rewards = isAuthzMode ? authzRewards : myRewards; + return (
diff --git a/frontend/src/app/(routes)/(overview)/overview-components/OverviewPage.tsx b/frontend/src/app/(routes)/(overview)/overview-components/OverviewPage.tsx index 1ccee3411..453adad52 100644 --- a/frontend/src/app/(routes)/(overview)/overview-components/OverviewPage.tsx +++ b/frontend/src/app/(routes)/(overview)/overview-components/OverviewPage.tsx @@ -17,11 +17,15 @@ import AssetsTable from './AssetsTable'; import AccountSummery from './AccountSummary'; import { getAccountInfo } from '@/store/features/auth/authSlice'; import { getDelegatorTotalRewards } from '@/store/features/distribution/distributionSlice'; +import useInitAuthzForOverview from '@/custom-hooks/useInitAuthzForOverview'; +import AuthzToast from '@/components/AuthzToast'; const OverviewPage = ({ chainIDs }: { chainIDs: string[] }) => { const dispatch = useAppDispatch(); const networks = useAppSelector((state: RootState) => state.wallet.networks); + const isAuthzMode = useAppSelector((state) => state.authz.authzModeEnabled); + useInitAuthzForOverview(chainIDs); useEffect(() => { chainIDs.forEach((chainID) => { const allChainInfo = networks[chainID]; @@ -66,8 +70,17 @@ const OverviewPage = ({ chainIDs }: { chainIDs: string[] }) => {
+ {isAuthzMode && ( +
+
+ +
+
+ )} - {chainIDs.length === 1 && } + {!isAuthzMode && chainIDs.length === 1 && ( + + )}

diff --git a/frontend/src/app/(routes)/(overview)/overview-components/Profile.tsx b/frontend/src/app/(routes)/(overview)/overview-components/Profile.tsx index 526b66a3d..b78453626 100644 --- a/frontend/src/app/(routes)/(overview)/overview-components/Profile.tsx +++ b/frontend/src/app/(routes)/(overview)/overview-components/Profile.tsx @@ -9,9 +9,19 @@ import { resetError, resetTxAndHash, } from '@/store/features/common/commonSlice'; +<<<<<<< HEAD +======= +import { resetState as bankReset } from '@/store/features/bank/bankSlice'; +import { resetState as rewardsReset } from '@/store/features/distribution/distributionSlice'; +import { resetCompleteState as stakingReset } from '@/store/features/staking/stakeSlice'; +import { resetState as authzReset } from '@/store/features/authz/authzSlice'; +import useAuthzGrants from '@/custom-hooks/useAuthzGrants'; + +>>>>>>> a885705 (feat: integrate authz with staking and overview (#1092)) const Profile = () => { const profileName = useAppSelector((state) => state.wallet.name); const dispatch = useAppDispatch(); + const { disableAuthzMode } = useAuthzGrants(); return (
@@ -34,6 +44,14 @@ const Profile = () => { dispatch(resetWallet()); dispatch(resetError()); dispatch(resetTxAndHash()); +<<<<<<< HEAD +======= + dispatch(bankReset()); + dispatch(rewardsReset()); + dispatch(stakingReset()); + dispatch(authzReset()); + disableAuthzMode(); +>>>>>>> a885705 (feat: integrate authz with staking and overview (#1092)) logout(); }} className="cursor-pointer" diff --git a/frontend/src/app/(routes)/(overview)/overview-components/WalletSummery.tsx b/frontend/src/app/(routes)/(overview)/overview-components/WalletSummery.tsx index 0bdb939b9..42bf8f3b7 100644 --- a/frontend/src/app/(routes)/(overview)/overview-components/WalletSummery.tsx +++ b/frontend/src/app/(routes)/(overview)/overview-components/WalletSummery.tsx @@ -1,12 +1,20 @@ +import { useAppSelector } from '@/custom-hooks/StateHooks'; import useGetAssetsAmount from '@/custom-hooks/useGetAssetsAmount'; import { formatDollarAmount } from '@/utils/util'; import Image from 'next/image'; import React from 'react'; +import useGetAuthzAssetsAmount from '../../../../custom-hooks/useGetAuthzAssetsAmount'; type AssetSummary = { icon: string; alt: string; type: string; amount: string }; const WalletSummery = ({ chainIDs }: { chainIDs: string[] }) => { - const [stakedAmount, availableAmount, rewardsAmount] = - useGetAssetsAmount(chainIDs); + const isAuthzMode = useAppSelector((state) => state.authz.authzModeEnabled); + const [myStaked, myAvailable, myRewards] = useGetAssetsAmount(chainIDs); + const [authzStaked, authzAvailable, authzRewards] = + useGetAuthzAssetsAmount(chainIDs); + const stakedAmount = isAuthzMode ? authzStaked : myStaked; + const availableAmount = isAuthzMode ? authzAvailable : myAvailable; + const rewardsAmount = isAuthzMode ? authzRewards : myRewards; + const available = formatDollarAmount(availableAmount); const staked = formatDollarAmount(stakedAmount); const rewards = formatDollarAmount(rewardsAmount); diff --git a/frontend/src/app/(routes)/governance/DepositPopup.tsx b/frontend/src/app/(routes)/governance/DepositPopup.tsx index 2adfbe247..6e8433b79 100644 --- a/frontend/src/app/(routes)/governance/DepositPopup.tsx +++ b/frontend/src/app/(routes)/governance/DepositPopup.tsx @@ -66,7 +66,18 @@ const DepositPopup = ({ const handleDeposit = (data: { amount: number }) => { const { aminoConfig, prefix, rest, feeAmount, address, rpc, minimalDenom } = getVoteTxInputs(chainID); - console.log(data); + + if (isAuthzMode) { + txAuthzDeposit({ + grantee: address, + proposalId: proposalId, + amount: Number(data.amount) * 10 ** currency.coinDecimals, + granter: authzGranter, + chainID: chainID, + metaData: '', + }); + return; + } dispatch( txDeposit({ diff --git a/frontend/src/app/(routes)/staking/components/ChainDelegations.tsx b/frontend/src/app/(routes)/staking/components/ChainDelegations.tsx index 6742eb721..a93d00068 100644 --- a/frontend/src/app/(routes)/staking/components/ChainDelegations.tsx +++ b/frontend/src/app/(routes)/staking/components/ChainDelegations.tsx @@ -23,6 +23,7 @@ import DialogRedelegate from './DialogRedelegate'; import useGetChainInfo from '@/custom-hooks/useGetChainInfo'; import { parseDenomAmount } from '@/utils/util'; import { TxStatus } from '@/types/enums'; +import useAuthzStakingExecHelper from '@/custom-hooks/useAuthzStakingExecHelper'; const ChainDelegations = ({ chainID, @@ -38,6 +39,8 @@ const ChainDelegations = ({ const router = useRouter(); const dispatch = useAppDispatch(); const { getChainInfo } = useGetChainInfo(); + const { txAuthzDelegate, txAuthzReDelegate, txAuthzUnDelegate } = + useAuthzStakingExecHelper(); const networks = useAppSelector((state: RootState) => state.wallet.networks); const networkLogo = networks[chainID]?.network.logos.menu; @@ -55,6 +58,8 @@ const ChainDelegations = ({ const stakingParams = useAppSelector( (state: RootState) => state.staking.chains[chainID]?.params ); + const authzAddress = useAppSelector((state) => state.authz.authzAddress); + const isAuthzMode = useAppSelector((state) => state.authz.authzModeEnabled); const [availableBalance, setAvailableBalance] = useState(0); const [delegateOpen, setDelegateOpen] = useState(false); @@ -108,9 +113,21 @@ const ChainDelegations = ({ ); const onDelegateTx = (data: DelegateTxInputs) => { + const basicChainInfo = getChainInfo(chainID); + if (isAuthzMode) { + txAuthzDelegate({ + grantee: address, + granter: authzAddress, + validator: data.validator, + amount: data.amount * 10 ** currency.coinDecimals, + denom: currency.coinMinimalDenom, + chainID: basicChainInfo.chainID, + }); + return; + } dispatch( txDelegate({ - basicChainInfo: getChainInfo(chainID), + basicChainInfo: basicChainInfo, delegator: address, validator: data.validator, amount: data.amount * 10 ** currency.coinDecimals, @@ -122,6 +139,18 @@ const ChainDelegations = ({ }; const onUndelegateTx = (data: UndelegateTxInputs) => { + const basicChainInfo = getChainInfo(chainID); + if (isAuthzMode) { + txAuthzUnDelegate({ + grantee: address, + granter: authzAddress, + validator: data.validator, + amount: data.amount * 10 ** currency.coinDecimals, + denom: currency.coinMinimalDenom, + chainID: basicChainInfo.chainID, + }); + return; + } dispatch( txUnDelegate({ basicChainInfo: getChainInfo(chainID), @@ -136,6 +165,19 @@ const ChainDelegations = ({ }; const onRedelegateTx = (data: RedelegateTxInputs) => { + const basicChainInfo = getChainInfo(chainID); + if (isAuthzMode) { + txAuthzReDelegate({ + grantee: address, + granter: authzAddress, + srcValidator: data.src, + validator: data.dest, + amount: data.amount * 10 ** currency.coinDecimals, + denom: currency.coinMinimalDenom, + chainID: basicChainInfo.chainID, + }); + return; + } dispatch( txReDelegate({ basicChainInfo: getChainInfo(chainID), diff --git a/frontend/src/app/(routes)/staking/components/StakingCard.tsx b/frontend/src/app/(routes)/staking/components/StakingCard.tsx index 0d236c4f5..b533af017 100644 --- a/frontend/src/app/(routes)/staking/components/StakingCard.tsx +++ b/frontend/src/app/(routes)/staking/components/StakingCard.tsx @@ -26,6 +26,8 @@ import { NO_REWARDS_ERROR, TXN_PENDING_ERROR, } from '@/utils/errors'; +import useAuthzStakingExecHelper from '@/custom-hooks/useAuthzStakingExecHelper'; +import useGetChainInfo from '@/custom-hooks/useGetChainInfo'; const StakingCard = ({ processingValAddr, @@ -171,10 +173,17 @@ const StakingCardActions = ({ const isReStakeAll = useAppSelector( (state) => state?.staking?.chains?.[chainID]?.isTxAll || false ); + const isAuthzMode = useAppSelector((state) => state.authz.authzModeEnabled); + const authzAddress = useAppSelector((state) => state.authz.authzAddress); const dispatch = useAppDispatch(); - const { txWithdrawValidatorRewardsInputs, txRestakeValidatorInputs } = - useGetTxInputs(); + const { + txWithdrawValidatorRewardsInputs, + txRestakeValidatorInputs, + txAuthzRestakeValidatorMsgs, + } = useGetTxInputs(); + const { txAuthzRestake, txAuthzClaim } = useAuthzStakingExecHelper(); + const { getChainInfo } = useGetChainInfo(); const claim = () => { if (txClaimStatus === TxStatus.PENDING) { @@ -191,7 +200,17 @@ const StakingCardActions = ({ validatorAddress, delegatorAddress ); + handleCardClick(validatorAddress); + if (isAuthzMode) { + txAuthzClaim({ + grantee: txInputs.address, + granter: authzAddress, + pairs: [{ validator: validatorAddress, delegator: authzAddress }], + chainID: chainID, + }); + return; + } if (txInputs.msgs.length) dispatch(txWithdrawAllRewards(txInputs)); else { dispatch( @@ -204,6 +223,17 @@ const StakingCardActions = ({ }; const claimAndStake = () => { + if (isAuthzMode) { + const { address } = getChainInfo(chainID); + const msgs = txAuthzRestakeValidatorMsgs(chainID, validatorAddress); + txAuthzRestake({ + grantee: address, + granter: authzAddress, + msgs: msgs, + chainID: chainID, + }); + return; + } if (txRestakeStatus === TxStatus.PENDING) { dispatch( setError({ diff --git a/frontend/src/app/(routes)/staking/components/StakingOverview.tsx b/frontend/src/app/(routes)/staking/components/StakingOverview.tsx index c9b4d28b3..f7f51dfe3 100644 --- a/frontend/src/app/(routes)/staking/components/StakingOverview.tsx +++ b/frontend/src/app/(routes)/staking/components/StakingOverview.tsx @@ -21,6 +21,12 @@ import { NO_MESSAGES_ILLUSTRATION, } from '@/utils/constants'; import { CircularProgress } from '@mui/material'; +<<<<<<< HEAD +======= +import MainTopNav from '@/components/MainTopNav'; +import useInitAuthzStaking from '@/custom-hooks/useInitAuthzStaking'; +import AuthzToast from '@/components/AuthzToast'; +>>>>>>> a885705 (feat: integrate authz with staking and overview (#1092)) const StakingOverview = () => { const dispatch = useAppDispatch(); @@ -32,22 +38,42 @@ const StakingOverview = () => { (chainName) => nameToChainIDs[chainName] ); + const isAuthzMode = useAppSelector((state) => state.authz.authzModeEnabled); + const stakingData = useAppSelector( (state: RootState) => state.staking.chains ); + const authzStakingData = useAppSelector( + (state: RootState) => state.staking.authz.chains + ); const rewardsData = useAppSelector( (state: RootState) => state.distribution.chains ); + const authzRewardsData = useAppSelector( + (state: RootState) => state.distribution.authzChains + ); const hasDelegations = useAppSelector( (state: RootState) => state.staking.hasDelegations ); + const hasAuthzDelegations = useAppSelector( + (state: RootState) => state.staking.authz.hasDelegations + ); const hasUnbonding = useAppSelector( (state: RootState) => state.staking.hasUnbonding ); + const hasAuthzUnbonding = useAppSelector( + (state: RootState) => state.staking.authz.hasUnbonding + ); const delegationsLoading = useAppSelector( (state: RootState) => state.staking.delegationsLoading ); + const authzDelegationsLoading = useAppSelector( + (state: RootState) => state.staking.authz.delegationsLoading + ); const { getChainInfo, getDenomInfo } = useGetChainInfo(); + + useInitAuthzStaking(chainIDs); + useEffect(() => { if (chainIDs) { chainIDs.forEach((chainID) => { @@ -96,14 +122,25 @@ const StakingOverview = () => { return (
+<<<<<<< HEAD

Staking

+======= +
+ + +
+>>>>>>> a885705 (feat: integrate authz with staking and overview (#1092))
{chainIDs.map((chainID) => { - const delegations = stakingData[chainID]?.delegations.delegations; + const delegations = ( + isAuthzMode ? authzStakingData[chainID] : stakingData[chainID] + )?.delegations.delegations; const validators = stakingData[chainID]?.validators; const currency = networks[chainID]?.network?.config?.currencies[0]; const chainName = networks[chainID]?.network?.config?.chainName; - const rewards = rewardsData[chainID]?.delegatorRewards?.list; + const rewards = ( + isAuthzMode ? authzRewardsData[chainID] : rewardsData[chainID] + )?.delegatorRewards?.list; return ( { })}
- {delegationsLoading === 0 && !hasDelegations ? ( + {(!isAuthzMode && delegationsLoading === 0 && !hasDelegations) || + (isAuthzMode && authzDelegationsLoading && !hasAuthzDelegations) ? (
{
) : null} - {hasUnbonding ? ( + {(!isAuthzMode && hasUnbonding) || (isAuthzMode && hasAuthzUnbonding) ? (

Unbonding

{chainIDs.map((chainID) => { - const unbondingDelegations = - stakingData[chainID]?.unbonding.unbonding; + const unbondingDelegations = ( + isAuthzMode ? authzStakingData[chainID] : stakingData[chainID] + )?.unbonding.unbonding; const validators = stakingData[chainID]?.validators; const { chainName, currencies } = networks[chainID]?.network?.config; diff --git a/frontend/src/app/(routes)/staking/components/StakingOverviewSidebar.tsx b/frontend/src/app/(routes)/staking/components/StakingOverviewSidebar.tsx index a39a24b22..976420e1c 100644 --- a/frontend/src/app/(routes)/staking/components/StakingOverviewSidebar.tsx +++ b/frontend/src/app/(routes)/staking/components/StakingOverviewSidebar.tsx @@ -7,19 +7,27 @@ import { useAppSelector } from '@/custom-hooks/StateHooks'; import { RootState } from '@/store/store'; import StakingSideBarAds from './StakingSideBarAds'; import { Tooltip } from '@mui/material'; +import useGetAuthzAssetsAmount from '@/custom-hooks/useGetAuthzAssetsAmount'; const StakingOverviewSidebar = () => { const nameToChainIDs = useAppSelector( (state: RootState) => state.wallet.nameToChainIDs ); const chainIDs = Object.values(nameToChainIDs); + const isAuthzMode = useAppSelector((state) => state.authz.authzModeEnabled); + + const [stakedAmount, , rewardsAmount] = useGetAssetsAmount(chainIDs); + const [authzStakedAmount, , authzRewardsAmount] = + useGetAuthzAssetsAmount(chainIDs); + + const totalStakedAmount = isAuthzMode ? authzStakedAmount : stakedAmount; + const rewards = isAuthzMode ? authzRewardsAmount : rewardsAmount; - const [totalStakedAmount, , rewards] = useGetAssetsAmount(chainIDs); return (
-
+
>>>>>> a885705 (feat: integrate authz with staking and overview (#1092)) const StakingPage = ({ chainName, @@ -46,12 +52,35 @@ const StakingPage = ({ (state: RootState) => state.wallet.nameToChainIDs ); const chainID = nameToChainIDs[chainName]; - const delegations = useAppSelector( - (state: RootState) => state.staking.chains[chainID]?.delegations.delegations + const isAuthzMode = useAppSelector((state) => state.authz.authzModeEnabled); + const stakingData = useAppSelector( + (state: RootState) => state.staking.chains ); - const unbondingDelegations = useAppSelector( - (state: RootState) => state.staking.chains[chainID]?.unbonding.unbonding + const authzStakingData = useAppSelector( + (state: RootState) => state.staking.authz.chains ); + const rewardsData = useAppSelector( + (state: RootState) => state.distribution.chains + ); + const authzRewardsData = useAppSelector( + (state: RootState) => state.distribution.authzChains + ); + + const allDelegationsLoading = useAppSelector( + (state: RootState) => state.staking.delegationsLoading + ); + const allAuthzDelegationsLoading = useAppSelector( + (state: RootState) => state.staking.authz.delegationsLoading + ); + + const delegations = isAuthzMode + ? authzStakingData[chainID]?.delegations.delegations + : stakingData[chainID]?.delegations.delegations; + + const unbondingDelegations = isAuthzMode + ? authzStakingData[chainID]?.unbonding.unbonding + : stakingData[chainID]?.unbonding.unbonding; + const validators = useAppSelector( (state: RootState) => state.staking.chains[chainID]?.validators ); @@ -59,20 +88,23 @@ const StakingPage = ({ (state: RootState) => state.wallet.networks[chainID]?.network?.config?.currencies[0] ); - const rewards = useAppSelector( - (state: RootState) => - state.distribution.chains?.[chainID]?.delegatorRewards.list - ); - const hasUnbondings = useAppSelector( - (state: RootState) => state.staking.chains[chainID]?.unbonding?.hasUnbonding - ); - const hasDelegations = useAppSelector( - (state: RootState) => - state.staking.chains[chainID]?.delegations?.hasDelegations - ); - const delegationsLoading = useAppSelector( - (state: RootState) => state.staking.delegationsLoading - ); + + const rewards = isAuthzMode + ? authzRewardsData[chainID]?.delegatorRewards.list + : rewardsData[chainID]?.delegatorRewards.list; + + const hasUnbondings = isAuthzMode + ? authzStakingData[chainID]?.unbonding?.hasUnbonding + : stakingData[chainID]?.unbonding.hasUnbonding; + + const hasDelegations = isAuthzMode + ? authzStakingData[chainID]?.delegations?.hasDelegations + : stakingData[chainID]?.delegations?.hasDelegations; + + const delegationsLoading = isAuthzMode + ? allAuthzDelegationsLoading + : allDelegationsLoading; + const txStatus = useAppSelector( (state: RootState) => state.staking.chains[chainID]?.tx ); @@ -80,6 +112,8 @@ const StakingPage = ({ const { getChainInfo } = useGetChainInfo(); const { address, baseURL } = getChainInfo(chainID); + useInitAuthzStaking([chainID]); + useEffect(() => { dispatch( getDelegations({ @@ -135,7 +169,14 @@ const StakingPage = ({ return (
+<<<<<<< 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}`; }