diff --git a/src/app/store.js b/src/app/store.js
index bf7b2eefa..5a6e8a888 100644
--- a/src/app/store.js
+++ b/src/app/store.js
@@ -1,6 +1,7 @@
import { configureStore } from "@reduxjs/toolkit";
import stakeReducer from "../features/staking/stakeSlice";
import proposalsReducer from "../features/gov/govSlice";
+import nodeReducer from "../features/node/nodeSlice";
import feegrantReducer from "../features/feegrant/feegrantSlice";
import authzReducer from "../features/authz/authzSlice";
import bankReducer from "../features/bank/bankSlice";
@@ -27,6 +28,7 @@ export const store = configureStore({
group: groupSlice,
multisig: multiSlice,
slashing: slashingSlice,
+ node: nodeReducer,
auth: authReducer,
},
middleware: (getDefaultMiddleware) =>
diff --git a/src/components/AppDrawer.jsx b/src/components/AppDrawer.jsx
index 48cfbbaac..03e59f4c1 100644
--- a/src/components/AppDrawer.jsx
+++ b/src/components/AppDrawer.jsx
@@ -10,7 +10,7 @@ import LogoutOutlinedIcon from "@mui/icons-material/LogoutOutlined";
import ContentCopyOutlined from "@mui/icons-material/ContentCopyOutlined";
import Button from "@mui/material/Button";
import PropTypes from "prop-types";
-import { drawerListItems } from "./drawerListItems";
+import { DrawerListItems } from "./DrawerListItems";
import { parseBalance } from "../utils/denom";
import { shortenAddress } from "../utils/util";
import { useLocation } from "react-router-dom";
@@ -191,13 +191,13 @@ export default function AppDrawer(props) {
- {drawerListItems(
- location.pathname,
- (path) => {
- onNavigate(path);
- },
- selectedNetwork?.showAirdrop
- )}
+ {
+ onNavigate(path)
+ }}
+ showAirdrop={selectedNetwork?.showAirdrop}
+ />
);
diff --git a/src/components/drawerListItems.js b/src/components/DrawerListItems.jsx
similarity index 73%
rename from src/components/drawerListItems.js
rename to src/components/DrawerListItems.jsx
index afde9fac7..0c2c9bf60 100644
--- a/src/components/drawerListItems.js
+++ b/src/components/DrawerListItems.jsx
@@ -8,8 +8,34 @@ import LayersIcon from "@mui/icons-material/Layers";
import BarChartOutlinedIcon from "@mui/icons-material/BarChartOutlined";
import DocumentScannerOutlinedIcon from "@mui/icons-material/DocumentScannerOutlined";
import GroupsOutlinedIcon from "@mui/icons-material/GroupsOutlined";
+import { useDispatch, useSelector } from "react-redux";
+import { getNodeInfo } from "../features/node/nodeSlice";
+
+export function DrawerListItems({ currentPath, onNavigate, showAirdrop }) {
+ const dispatch = useDispatch();
+ const [nodeDataInfo, setNodeDataInfo] = React.useState(false);
+
+ const wallet = useSelector(state => state?.wallet);
+ const { chainInfo } = wallet;
+ const nodeInfo = useSelector(state => state?.node)
+
+ React.useEffect(() => {
+ dispatch(getNodeInfo({ baseURL: chainInfo?.config?.rest }))
+ }, [])
+
+ React.useEffect(() => {
+ if (nodeInfo?.nodeInfo?.status === 'idle') {
+ if (nodeInfo?.nodeInfo?.data?.application_version) {
+ let version = nodeInfo?.nodeInfo?.data?.application_version?.version;
+
+ if (version?.indexOf('46') >= 0) {
+ setNodeDataInfo(true);
+ } else setNodeDataInfo(false);
+ }
+ }
+ }, [nodeInfo?.status])
+
-export function drawerListItems(currentPath, onNavigate, showAirdrop) {
return (
<>
onNavigate("/group")}
sx={{ pb: 0.5, pt: 0.5 }}
selected={currentPath === "/group"}
@@ -82,7 +108,12 @@ export function drawerListItems(currentPath, onNavigate, showAirdrop) {
-
+
{showAirdrop ? (
diff --git a/src/components/group/AlertMsg.tsx b/src/components/group/AlertMsg.tsx
new file mode 100644
index 000000000..ad81c3459
--- /dev/null
+++ b/src/components/group/AlertMsg.tsx
@@ -0,0 +1,30 @@
+import { Alert, Card, Typography } from '@mui/material'
+import React from 'react'
+import { gpStyles } from './groupCmpStyles'
+
+interface AlertMsgProps {
+ text: string,
+ type: string
+}
+
+const AlertMsg = ({ text, type = 'error' }: AlertMsgProps) => {
+ return (
+
+ {
+ type === 'info' &&
+ {text}
+
+ }
+
+ {
+ type === 'error' &&
+ {text}
+
+ }
+
+ )
+}
+
+export default AlertMsg
\ No newline at end of file
diff --git a/src/components/group/CardSkeleton.tsx b/src/components/group/CardSkeleton.tsx
new file mode 100644
index 000000000..46d36993f
--- /dev/null
+++ b/src/components/group/CardSkeleton.tsx
@@ -0,0 +1,42 @@
+import { Grid, Paper, Skeleton } from '@mui/material'
+import Typography, { TypographyProps } from '@mui/material/Typography';
+import { experimentalStyled as styled } from '@mui/material/styles';
+import React from 'react'
+
+const Item = styled(Paper)(({ theme }) => ({
+ backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
+ ...theme.typography.body2,
+ padding: theme.spacing(2),
+ textAlign: 'center',
+ color: theme.palette.text.secondary,
+}));
+
+function CardSkeleton() {
+ const variant = [
+ 'h5',
+ 'h6',
+ 'h1',
+ ] as readonly TypographyProps['variant'][];
+ const variants = [{ variant }, { variant }, { variant }]
+
+ return (
+
+ {variants.map((variant) => (
+
+ -
+ {
+ variant?.variant.map(v => (
+
+
+
+ ))
+ }
+
+
+ ))}
+
+ )
+}
+
+export default CardSkeleton
\ No newline at end of file
diff --git a/src/components/group/DialogVote.tsx b/src/components/group/DialogVote.tsx
new file mode 100644
index 000000000..8623a0711
--- /dev/null
+++ b/src/components/group/DialogVote.tsx
@@ -0,0 +1,88 @@
+import * as React from 'react';
+import Button from '@mui/material/Button';
+import Avatar from '@mui/material/Avatar';
+import List from '@mui/material/List';
+import ListItem from '@mui/material/ListItem';
+import ListItemText from '@mui/material/ListItemText';
+import DialogTitle from '@mui/material/DialogTitle';
+import Dialog from '@mui/material/Dialog';
+import { ListItemButton } from '@mui/material';
+
+const options = [{
+ label: 'Yes',
+ value: 1,
+ active: '#6d70fe'
+}, {
+ label: 'No',
+ value: 3,
+ active: '#e87d91'
+}, {
+ label: 'No with Veto',
+ value: 4,
+ active: 'red'
+}, {
+ label: 'Abstain',
+ value: 2,
+ active: '#6e81cb'
+}]
+
+interface voteprops {
+ vote: number, proposalId: string
+}
+export interface SimpleDialogProps {
+ open: boolean;
+ proposalId: string;
+ selectedValue: string;
+ voteRes: any;
+ onConfirm: (obj: voteprops) => void;
+ onClose: (value: string) => void;
+}
+
+export default function DailogVote(props: SimpleDialogProps) {
+ const { onClose, voteRes, proposalId, onConfirm, selectedValue, open } = props;
+ const [vote, setVote] = React.useState(0);
+
+ const handleClose = () => {
+ onClose(selectedValue);
+ };
+
+ const handleListItemClick = (value: number) => {
+ setVote(value);
+ };
+
+ return (
+
+ );
+}
+
diff --git a/src/components/group/FileProposalOptions.tsx b/src/components/group/FileProposalOptions.tsx
new file mode 100644
index 000000000..fcd5baef7
--- /dev/null
+++ b/src/components/group/FileProposalOptions.tsx
@@ -0,0 +1,125 @@
+import { Button, Grid } from '@mui/material';
+import { Box } from '@mui/system';
+import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined";
+import FileUploadOutlinedIcon from "@mui/icons-material/FileUploadOutlined";
+import React from 'react';
+
+
+const MULTISIG_SEND_TEMPLATE = "https://resolute.witval.com/_static/send.csv";
+const MULTISIG_DELEGATE_TEMPLATE =
+ "https://resolute.witval.com/_static/delegate.csv";
+const MULTISIG_UNDELEGATE_TEMPLATE =
+ "https://resolute.witval.com/_static/undelegate.csv";
+const MULTISIG_REDELEGATE_TEMPLATE =
+ "https://resolute.witval.com/_static/redelegate.csv";
+
+
+const TYPE_SEND = "SEND";
+const TYPE_DELEGATE = "DELEGATE";
+const TYPE_UNDELEGATE = "UNDELEGATE";
+const TYPE_REDELEGATE = "REDELEGATE";
+
+interface FileProposalOptionsProps {
+ txType: string,
+ onFileContents: any
+}
+
+function FileProposalOptions({
+ txType,
+ onFileContents
+}: FileProposalOptionsProps) {
+
+ return (
+
+ }
+ sx={{
+ textTransform: "none",
+ }}
+ onClick={() => {
+ switch (txType) {
+ case TYPE_SEND:
+ window.open(
+ MULTISIG_SEND_TEMPLATE,
+ "_blank",
+ "noopener,noreferrer"
+ );
+ break;
+ case TYPE_DELEGATE:
+ window.open(
+ MULTISIG_DELEGATE_TEMPLATE,
+ "_blank",
+ "noopener,noreferrer"
+ );
+ break;
+ case TYPE_UNDELEGATE:
+ window.open(
+ MULTISIG_UNDELEGATE_TEMPLATE,
+ "_blank",
+ "noopener,noreferrer"
+ );
+ break;
+ case TYPE_REDELEGATE:
+ window.open(
+ MULTISIG_REDELEGATE_TEMPLATE,
+ "_blank",
+ "noopener,noreferrer"
+ );
+ break;
+ default:
+ alert("unknown message type");
+ }
+ }}
+ >
+ Download template
+
+ }
+ sx={{
+ mb: 2,
+ mt: 2,
+ ml: 1,
+ textTransform: "none",
+ }}
+ onClick={() => {
+ document.getElementById("multisig_file")!.click();
+ }}
+ >
+ {
+ const file = e.target.files[0];
+ if (!file) {
+ return;
+ }
+
+ const reader = new FileReader();
+ reader.onload = (e: any) => {
+ const contents = e.target.result;
+ onFileContents(contents, txType);
+ };
+ reader.onerror = (e) => {
+ alert(e);
+ };
+ reader.readAsText(file);
+ e.target.value = null;
+ }}
+ />
+ Upload csv file
+
+
+
+ )
+}
+
+export default FileProposalOptions
\ No newline at end of file
diff --git a/src/components/group/GroupCard.tsx b/src/components/group/GroupCard.tsx
new file mode 100644
index 000000000..f48e1aa74
--- /dev/null
+++ b/src/components/group/GroupCard.tsx
@@ -0,0 +1,72 @@
+import * as React from 'react';
+import Card from '@mui/material/Card';
+import CardContent from '@mui/material/CardContent';
+import Typography from '@mui/material/Typography';
+import { Box } from '@mui/system';
+import { shortenAddress } from '../../utils/util';
+import { getLocalTime } from '../../utils/datetime';
+import ReadMoreIcon from '@mui/icons-material/ReadMore';
+import { IconButton, Paper } from '@mui/material';
+import { useNavigate } from 'react-router-dom';
+
+interface GroupCardProps {
+ group: any
+}
+
+interface BoxTextProps {
+ label: any,
+ text: any
+}
+
+const BoxText = ({ label, text }: BoxTextProps) => {
+ return (
+
+
+ {label}
+
+
+ {text}
+
+
+ )
+}
+
+export default function GroupCard({ group }: GroupCardProps) {
+ const navigate = useNavigate();
+ const [showFullText, setShowFullText] = React.useState(false);
+
+ return (
+
+
+ navigate(`/groups/${group?.id}`)}
+ color='primary'
+ sx={{ float: 'right' }}
+ >
+
+
+
+ # {group?.id}
+
+
+ ##
+ {!showFullText && group?.metadata?.substring(0, 30)}
+
+ {
+ showFullText && group?.metadata
+ }
+
+ {group?.metadata?.length > 40 ? setShowFullText(!showFullText)}
+ href='javascript:void(0);'
+ > {showFullText? ' ...show less': ' ...more'} : null}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/group/GroupList.tsx b/src/components/group/GroupList.tsx
new file mode 100644
index 000000000..c9b46140b
--- /dev/null
+++ b/src/components/group/GroupList.tsx
@@ -0,0 +1,56 @@
+import * as React from "react";
+import { Box, CircularProgress, Grid } from "@mui/material";
+import AlertMsg from "./AlertMsg";
+import GroupCard from "./GroupCard";
+import PaginationElement from "./PaginationElement";
+
+export interface GroupsByAdminProps {
+ groups: any;
+ status: string;
+ total: number;
+ handlePagination: (key: number) => void;
+ onAction: (group: any) => void;
+ paginationKey: string;
+}
+
+export default function GroupList(props: GroupsByAdminProps) {
+ const { groups, onAction,
+ paginationKey,
+ handlePagination, total, status } = props;
+
+ return (
+ <>
+
+
+ {
+ status === 'pending'?
+ : null
+ }
+
+ {
+ status !== 'pending' && !groups.length ?
+
+ : null
+ }
+
+ {status !== 'pending' && groups?.map((group: any, index: any,) => (
+
+
+
+ ))}
+
+ {
+ total > 9 &&
+
+
+ }
+
+
+
+ >
+ )
+}
diff --git a/src/components/group/GroupMemberCount.jsx b/src/components/group/GroupMemberCount.jsx
new file mode 100644
index 000000000..9db6f5203
--- /dev/null
+++ b/src/components/group/GroupMemberCount.jsx
@@ -0,0 +1,37 @@
+import { useDispatch, useSelector } from "react-redux";
+import * as React from "react";
+import { getGroupMembers } from "../../features/group/groupSlice";
+import { CircularProgress } from "@mui/material";
+
+function GroupMemberCount(props) {
+ const dispatch = useDispatch();
+ const [memberCount, setMemberCount] = React.useState(0);
+
+ const wallet = useSelector((state) => state.wallet);
+ const members = useSelector(state => state.group.members);
+
+ const { groupId } = props;
+ console.log('memberssssssss', groupId, members, wallet?.chainInfo)
+
+ React.useEffect(() => {
+ // const { chainInfo } = wallet;
+
+ // const data = {
+ // baseURL: chainInfo?.config?.rest,
+ // groupId
+ // }
+
+ // dispatch(getGroupMembers(data))
+ }, [])
+
+ return (
+
+ {
+ members?.status === 'loading' ? :
+ members?.pagination?.total || 0
+ }
+
+ )
+}
+
+export default GroupMemberCount
\ No newline at end of file
diff --git a/src/components/group/GroupTab.tsx b/src/components/group/GroupTab.tsx
new file mode 100644
index 000000000..5d65b02f5
--- /dev/null
+++ b/src/components/group/GroupTab.tsx
@@ -0,0 +1,81 @@
+import * as React from 'react';
+import Tabs from '@mui/material/Tabs';
+import Tab from '@mui/material/Tab';
+import Typography from '@mui/material/Typography';
+import Box from '@mui/material/Box';
+import { Button, Paper } from '@mui/material';
+
+interface TabPanelProps {
+ children?: React.ReactNode;
+ index: number;
+ value: number;
+
+}
+
+export function TabPanel(props: TabPanelProps) {
+ const { children, value, index, ...other } = props;
+
+ return (
+
+ {value === index && (
+
+ {children}
+
+ )}
+
+ );
+}
+
+function a11yProps(index: number) {
+ return {
+ id: `simple-tab-${index}`,
+ 'aria-controls': `simple-tabpanel-${index}`,
+ };
+}
+
+interface GroupTabinterface {
+ handleTabChange: (newValue: number) => number;
+ tabs: Array<[]>;
+}
+
+export default function GroupTab({ handleTabChange, tabs }: GroupTabinterface) {
+ const [value, setValue] = React.useState(0);
+
+ const handleChange = (event: React.SyntheticEvent, newValue: number) => {
+ setValue(newValue);
+ handleTabChange(newValue)
+ };
+
+ return (
+
+
+
+
+ {
+ tabs.map((t, i) => (
+
+ ))
+ }
+ {/*
+ */}
+
+
+
+ );
+}
diff --git a/src/components/group/MembersTable.js b/src/components/group/MembersTable.js
new file mode 100644
index 000000000..27872ffe5
--- /dev/null
+++ b/src/components/group/MembersTable.js
@@ -0,0 +1,112 @@
+import * as React from 'react';
+import { styled } from '@mui/material/styles';
+import Table from '@mui/material/Table';
+import TableBody from '@mui/material/TableBody';
+import TableCell, { tableCellClasses } from '@mui/material/TableCell';
+import TableContainer from '@mui/material/TableContainer';
+import TableHead from '@mui/material/TableHead';
+import TableRow from '@mui/material/TableRow';
+import Paper from '@mui/material/Paper';
+import TablePagination from '@mui/material/TablePagination';
+import { IconButton, Tooltip, Typography } from '@mui/material';
+import DeleteOutline from '@mui/icons-material/DeleteOutline';
+
+const StyledTableCell = styled(TableCell)(({ theme }) => ({
+ [`&.${tableCellClasses.head}`]: {
+ backgroundColor: theme.palette.common.black,
+ color: theme.palette.common.white,
+ textAlign: 'center',
+ },
+ [`&.${tableCellClasses.body}`]: {
+ fontSize: 16,
+ textAlign: 'center'
+ },
+}));
+
+const StyledTableRow = styled(TableRow)(({ theme }) => ({
+ '&:nth-of-type(odd)': {
+ backgroundColor: theme.palette.action.hover,
+ },
+ // hide last border
+ '&:last-child td, &:last-child th': {
+ border: 0,
+ },
+}));
+
+export default function MembersTable({ rows, total,
+ pageNumber = 0, limit, handleMembersPagination, handleDeleteMember }) {
+ const [page, setPage] = React.useState(pageNumber);
+ const [rowsPerPage, setRowsPerPage] = React.useState(limit);
+ const handleChangePage = (event, newPage) => {
+ setPage(newPage);
+
+ handleMembersPagination(Number(newPage), rowsPerPage, rows?.pagination?.next_key)
+ };
+
+ const handleChangeRowsPerPage = (event) => {
+ setRowsPerPage(+event.target.value);
+ setPage(+event.target.value);
+
+ handleMembersPagination(Number(page), +event.target.value, '')
+ };
+
+ return (
+
+ Group Members
+
+
+
+ Address
+ Weight
+ Metadata
+
+
+
+
+ {rows?.members?.map((row) => (
+
+
+ {row?.member?.address || '-'}
+
+
+ {row?.member?.weight || '-'}
+
+
+ {row?.member?.metadata || '-'}
+
+
+
+ {
+ handleDeleteMember({
+ address: row?.member?.address,
+ weight: '0',
+ metadata: row?.member?.metadata
+ })
+ }} color='error'>
+
+
+
+
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/components/group/PaginationElement.tsx b/src/components/group/PaginationElement.tsx
new file mode 100644
index 000000000..7cdced770
--- /dev/null
+++ b/src/components/group/PaginationElement.tsx
@@ -0,0 +1,29 @@
+import * as React from 'react';
+import Typography from '@mui/material/Typography';
+import Pagination from '@mui/material/Pagination';
+import Stack from '@mui/material/Stack';
+
+interface PaginationElementProps {
+ total: number;
+ handlePagination: (key: number) => void;
+ paginationKey: string;
+}
+
+export default function PaginationElement({ total,
+ paginationKey,
+ handlePagination }:
+ PaginationElementProps) {
+ const [page, setPage] = React.useState(1);
+ const handleChange = (event: React.ChangeEvent, value: number) => {
+
+ setPage(value);
+ handlePagination(value-1)
+ };
+
+ return (
+
+
+
+ );
+}
diff --git a/src/components/group/PolicyCard.jsx b/src/components/group/PolicyCard.jsx
new file mode 100644
index 000000000..f9b471565
--- /dev/null
+++ b/src/components/group/PolicyCard.jsx
@@ -0,0 +1,90 @@
+import * as React from 'react';
+import { styled } from '@mui/material/styles';
+import Card from '@mui/material/Card';
+import CardHeader from '@mui/material/CardHeader';
+import CardMedia from '@mui/material/CardMedia';
+import CardContent from '@mui/material/CardContent';
+import CardActions from '@mui/material/CardActions';
+import Collapse from '@mui/material/Collapse';
+import Avatar from '@mui/material/Avatar';
+import IconButton from '@mui/material/IconButton';
+import Typography from '@mui/material/Typography';
+import { red } from '@mui/material/colors';
+import FavoriteIcon from '@mui/icons-material/Favorite';
+import ShareIcon from '@mui/icons-material/Share';
+import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
+import MoreVertIcon from '@mui/icons-material/MoreVert';
+import RowItem from './RowItem';
+import { Grid, Paper } from '@mui/material';
+import { setLocalStorage, shortenAddress } from '../../utils/util';
+import ReadMoreIcon from '@mui/icons-material/ReadMore';
+import { useNavigate } from 'react-router-dom';
+
+const ExpandMore = styled((props) => {
+ const { expand, ...other } = props;
+ return ;
+})(({ theme, expand }) => ({
+ transform: !expand ? 'rotate(0deg)' : 'rotate(180deg)',
+ marginLeft: 'auto',
+ transition: theme.transitions.create('transform', {
+ duration: theme.transitions.duration.shortest,
+ }),
+}));
+
+const policyType = {
+ '/cosmos.group.v1.ThresholdDecisionPolicy': 'Threshold Decision Policy',
+ '/cosmos.group.v1.PercentageDecisionPolicy': 'Percentage Decision Policy',
+}
+
+export default function PolicyCard({ obj }) {
+ const [expanded, setExpanded] = React.useState(false);
+
+ const handleExpandClick = () => {
+ setExpanded(!expanded);
+ };
+
+ const navigate = useNavigate();
+
+ return (
+ {
+ setLocalStorage('policy', obj, 'object');
+ navigate(`/groups/${obj?.group_id}/policies/${obj?.address}`)
+ }}
+ sx={{m: 1, borderRadius: 2 }}
+ >
+
+
+
+ }
+ title={policyType[obj?.decision_policy['@type']]}
+ subheader={shortenAddress(obj?.address, 19)}
+ />
+
+
+
+ ## {obj?.metadata || '-'}
+
+ {
+ obj?.decision_policy['@type'] === '/cosmos.group.v1.ThresholdDecisionPolicy' ?
+ :
+
+ }
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/group/PolicyDetails.tsx b/src/components/group/PolicyDetails.tsx
new file mode 100644
index 000000000..cf1471d38
--- /dev/null
+++ b/src/components/group/PolicyDetails.tsx
@@ -0,0 +1,248 @@
+import { Grid, IconButton, TextField, Typography } from '@mui/material'
+import { Box } from '@mui/system'
+import React, { useEffect, useState } from 'react'
+import EditIcon from '@mui/icons-material/Edit';
+import CancelIcon from '@mui/icons-material/Cancel';
+import { getLocalTime } from '../../utils/datetime';
+import CheckIcon from '@mui/icons-material/Check';
+import { useSelector } from 'react-redux';
+import { ThresholdDecisionPolicy } from '../../utils/util';
+
+interface GridItemProps {
+ label: string;
+ text: string;
+ isEditMode?: boolean;
+ handleUpdate: any
+}
+
+const GridItemEdit = ({
+ label,
+ text,
+ isEditMode,
+ handleUpdate
+}: GridItemProps) => {
+ const [isEdit, setIsEdit] = useState(isEditMode);
+
+ return (
+
+
+
+ {label}
+
+
+
+ {
+ isEdit ?
+ setIsEdit(false)}
+ /> :
+ <>
+
+ {text}
+
+ setIsEdit(true)} color='primary' />
+ >
+ }
+
+
+
+ )
+}
+
+interface GridItemTextProps {
+ label: string;
+ text: string;
+ isEditMode?: boolean;
+ isEqualColumn?: boolean
+}
+
+const GridItemText = ({
+ label,
+ text,
+ isEqualColumn
+}: GridItemTextProps) => {
+ return (
+
+
+
+ {label}
+
+
+
+
+ {text}
+
+
+
+ )
+}
+
+interface EditTextFieldProps {
+ name?: string;
+ value: string;
+ hideShowEdit?: any;
+ placeholder?: string
+ handleUpdate?: any;
+}
+
+const EditTextField = ({
+ placeholder,
+ handleUpdate,
+ name, value, hideShowEdit,
+}: EditTextFieldProps) => {
+ const [field, setField] = useState(value);
+
+ return (
+
+ setField(e.target.value)}
+ value={field} fullWidth />
+
+ handleUpdate(field)}
+ color='primary'
+ sx={{ border: '1px solid', borderRadius: 2, ml: 2 }}
+ >
+
+
+ hideShowEdit()}
+ sx={{ border: '1px solid', borderRadius: 2, ml: 2 }}>
+
+
+
+ )
+}
+
+interface PolicyDetailsProps {
+ policyObj: any,
+ handleUpdateAdmin: any,
+ handleUpdateMetadata: any
+}
+
+function PolicyDetails({
+ policyObj,
+ handleUpdateMetadata,
+ handleUpdateAdmin }: PolicyDetailsProps) {
+ const [isMetaEditMode, setIsMetaEditMode] = useState(false);
+ const [isAdminEdit, setIsAdminEdit] = useState(false);
+
+ const updateMetadataRes = useSelector((state: any) => state?.group?.updateGroupMetadataRes);
+
+ useEffect(() => {
+ if (updateMetadataRes?.status === 'idle') {
+ setIsMetaEditMode(false);
+ }
+ }, [updateMetadataRes?.status])
+
+ const updatePolicyAdminRes = useSelector((state: any) => state?.group?.updatePolicyAdminRes);
+
+ useEffect(() => {
+ if (updatePolicyAdminRes?.status === 'idle') {
+ setIsAdminEdit(false);
+ }
+ }, [updatePolicyAdminRes?.status])
+
+ return (
+
+
+ {
+ isMetaEditMode ?
+ {
+ setIsMetaEditMode(false);
+ }}
+ value={policyObj?.metadata} />
+ :
+
+ ## {policyObj?.metadata}
+ setIsMetaEditMode(true)} />
+
+ }
+
+
+
+ Note: Only admin can be update metadata.
+
+
+
+
+
+
+
+
+ Note: Only admin can be update admin address.
+
+
+
+
+ {
+ policyObj?.decision_policy['@type'] === ThresholdDecisionPolicy ?
+ :
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default PolicyDetails
\ No newline at end of file
diff --git a/src/components/group/PolicyForm.tsx b/src/components/group/PolicyForm.tsx
new file mode 100644
index 000000000..8147c173a
--- /dev/null
+++ b/src/components/group/PolicyForm.tsx
@@ -0,0 +1,235 @@
+import {
+ Box, Button, TextField, Select, MenuItem, FormControlLabel, Switch, Typography, Grid, FormControl, InputLabel, InputAdornment,
+} from '@mui/material';
+import React, { useState } from 'react'
+import { useForm, Controller } from 'react-hook-form';
+import { Interface } from 'readline';
+import Policy from '../../pages/group/Policy';
+import { gpStyles } from './groupCmpStyles';
+
+interface PolicyFormProps {
+ handlePolicy: any,
+ handlePolicyClose: any
+ policyObj?: any
+}
+
+function PolicyForm({ handlePolicy, policyObj, handlePolicyClose }: PolicyFormProps) {
+ var policyInitialObj = {
+ metadata: '',
+ decisionPolicy: '',
+ threshold: 0,
+ percentage: 0,
+ votingPeriod: '',
+ minExecPeriod: 0
+ };
+
+ if (policyObj) {
+ policyInitialObj = {
+ metadata: 'sample ' || policyObj?.metadata,
+ decisionPolicy: policyObj?.decision_policy?.['@type'] === '/cosmos.group.v1.ThresholdDecisionPolicy' ?
+ 'threshold' : 'percentage',
+ threshold: Number(policyObj?.decision_policy?.threshold || 0),
+ percentage: Number(policyObj?.decision_policy?.percentage || 0),
+ votingPeriod: '12' || parseFloat(policyObj?.decision_policy?.windows?.voting_period || 0),
+ minExecPeriod: 12 || parseFloat(policyObj?.decision_policy?.windows?.min_execution_period || 0)
+ }
+ }
+
+
+ console.log('intital---', policyInitialObj)
+
+ const { register, control,
+ handleSubmit,
+ watch,
+ formState: { errors },
+ reset, trigger, setError } = useForm({
+ defaultValues: {
+ ...policyInitialObj
+ }
+ });
+
+ return (
+
+
+ Add Decision Policy
+
+
+
+
+ )
+}
+
+export default PolicyForm
\ No newline at end of file
diff --git a/src/components/group/ProposalCard.tsx b/src/components/group/ProposalCard.tsx
new file mode 100644
index 000000000..85777caab
--- /dev/null
+++ b/src/components/group/ProposalCard.tsx
@@ -0,0 +1,131 @@
+import ReadMore from '@mui/icons-material/ReadMore'
+import { Avatar, Chip, Grid, Paper, Stack, Typography } from '@mui/material'
+import { deepOrange, deepPurple } from '@mui/material/colors'
+import { Box } from '@mui/system'
+import React from 'react'
+import { useNavigate } from 'react-router-dom'
+import { getLocalTime } from '../../utils/datetime'
+import { proposalStatus, shortenAddress } from '../../utils/util'
+
+function stringAvatar(name: string) {
+ return {
+ sx: {
+ color: '#000000',
+ bgcolor: deepOrange[50],
+
+ },
+ children: `${name}`,
+ };
+}
+
+
+interface ProposalCardProps {
+ proposal: any
+}
+
+function ProposalCard({ proposal }: ProposalCardProps) {
+ const proposalStatuses: any = proposalStatus;
+ const yes = parseFloat(proposal?.final_tally_result?.yes_count);
+ const no = parseFloat(proposal?.final_tally_result?.no_count);
+ const abstain = parseFloat(proposal?.final_tally_result?.abstain_count);
+ const veto = parseFloat(proposal?.final_tally_result?.no_with_veto_count);
+
+ const sum = yes + no + abstain + veto;
+ const yesP = (yes / sum).toFixed(2);
+ const noP = (no / sum).toFixed(2);
+ const abP = (abstain / sum).toFixed(2)
+ const vetoP = (veto / sum).toFixed(2);
+
+
+ const navigate = useNavigate();
+
+ return (
+ {
+ navigate(`/groups/proposals/${proposal?.id}`)
+ }}
+ sx={{ padding: 3 }} variant='outlined' elevation={1}>
+
+
+
+
+
+ # {proposal?.metadata || '-'}
+
+
+
+
+
+
+
+
+ Voting End Time
+
+
+ {getLocalTime(proposal?.voting_period_end)}
+
+
+
+ Submit Time
+
+
+ {getLocalTime(proposal?.submit_time)}
+
+
+
+
+
+ Proposers
+
+ {
+ proposal?.proposers?.map((p: string) => (
+
+ {p && shortenAddress(p, 21)}
+
+ ))
+ }
+
+
+ Policy Address
+
+ {proposal?.group_policy_address && shortenAddress(proposal?.group_policy_address, 21)}
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default ProposalCard
\ No newline at end of file
diff --git a/src/components/group/RowItem.jsx b/src/components/group/RowItem.jsx
new file mode 100644
index 000000000..92188b8df
--- /dev/null
+++ b/src/components/group/RowItem.jsx
@@ -0,0 +1,22 @@
+import { Grid, Typography } from '@mui/material'
+import React from 'react'
+
+function RowItem({ lable, value, equal }) {
+ return (
+
+
+ {lable}
+
+
+ {value}
+
+
+ )
+}
+
+export default RowItem
\ No newline at end of file
diff --git a/src/components/group/TxBasicFields.tsx b/src/components/group/TxBasicFields.tsx
new file mode 100644
index 000000000..38d5c1069
--- /dev/null
+++ b/src/components/group/TxBasicFields.tsx
@@ -0,0 +1,92 @@
+import { Grid, TextField } from '@mui/material';
+import React from 'react'
+import { Controller, useFormContext } from 'react-hook-form';
+import FeeComponent from '../multisig/FeeComponent';
+
+interface TxBasicFieldsProps {
+ chainInfo: any
+}
+
+function TxBasicFields({
+ chainInfo
+}: TxBasicFieldsProps) {
+ const { handleSubmit, control,
+ setValue, formState:{errors}, } = useFormContext();
+
+ return (
+
+
+ (
+
+ )}
+ />
+
+
+ (
+
+ )}
+ />
+
+
+ {
+ setValue(
+ "fees",
+ Number(v) *
+ 10 **
+ chainInfo?.config?.currencies[0].coinDecimals
+ );
+ }}
+ chainInfo={chainInfo}
+ />
+
+
+ )
+}
+
+export default TxBasicFields
\ No newline at end of file
diff --git a/src/components/group/TxTypeComponent.tsx b/src/components/group/TxTypeComponent.tsx
new file mode 100644
index 000000000..c28e7cd77
--- /dev/null
+++ b/src/components/group/TxTypeComponent.tsx
@@ -0,0 +1,48 @@
+import * as React from 'react';
+import Radio from '@mui/material/Radio';
+import RadioGroup from '@mui/material/RadioGroup';
+import FormControlLabel from '@mui/material/FormControlLabel';
+import FormControl from '@mui/material/FormControl';
+import FormLabel from '@mui/material/FormLabel';
+import { Box } from '@mui/system';
+import { FormHelperText } from '@mui/material';
+
+interface TxTypeComponentProps {
+ handleType: any
+}
+
+export default function TxTypeComponent({ handleType }: TxTypeComponentProps) {
+ const [value, setValue] = React.useState('');
+
+ const handleRadioChange = (event: React.ChangeEvent) => {
+ setValue((event.target as HTMLInputElement).value);
+ handleType((event.target as HTMLInputElement).value);
+ };
+
+ return (
+
+
+
+ How do you want a add transaction?
+
+
+ } label="Manually" />
+ } label="Upload through file" />
+
+
+ Only one transaction at a time.
+ Multiple transactions at a time.
+
+
+
+
+ );
+}
diff --git a/src/components/group/UpdateGroupMemberForm.tsx b/src/components/group/UpdateGroupMemberForm.tsx
new file mode 100644
index 000000000..dd5b6bf15
--- /dev/null
+++ b/src/components/group/UpdateGroupMemberForm.tsx
@@ -0,0 +1,131 @@
+
+import {
+ Box, TextField, IconButton, Grid, Button,
+} from '@mui/material';
+import DeleteOutline from "@mui/icons-material/DeleteOutline";
+import { Controller, useFieldArray, useForm } from "react-hook-form";
+import React from 'react';
+import AddIcon from '@mui/icons-material/Add';
+
+interface UpdateGroupMemberFormProps {
+ members: any,
+ handleUpdate: any,
+ handleCancel: any
+}
+
+export function UpdateGroupMemberForm({
+ members,
+ handleUpdate,
+ handleCancel
+}: UpdateGroupMemberFormProps) {
+ const { register, control,
+ handleSubmit,
+ watch,
+ formState: { errors },
+ reset, trigger, setError } = useForm({
+ defaultValues: {
+ members: members
+ }
+ });
+
+ const { fields, append, remove } = useFieldArray({
+ control,
+ name: "members"
+ });
+
+ return (
+
+
+
+
+
+ )
+}
+
+export default UpdateGroupMemberForm
diff --git a/src/components/group/VotesTable.js b/src/components/group/VotesTable.js
new file mode 100644
index 000000000..dd49a7f92
--- /dev/null
+++ b/src/components/group/VotesTable.js
@@ -0,0 +1,129 @@
+import * as React from 'react';
+import { styled } from '@mui/material/styles';
+import Table from '@mui/material/Table';
+import TableBody from '@mui/material/TableBody';
+import TableCell, { tableCellClasses } from '@mui/material/TableCell';
+import TableContainer from '@mui/material/TableContainer';
+import TableHead from '@mui/material/TableHead';
+import TableRow from '@mui/material/TableRow';
+import Paper from '@mui/material/Paper';
+import TablePagination from '@mui/material/TablePagination';
+import { Typography } from '@mui/material';
+import { getLocalTime } from '../../utils/datetime';
+
+const votesStatus = {
+ 'VOTE_OPTION_YES': {
+ label: 'Yes',
+ bgColor: '#d8ead8',
+ color: 'green'
+ },
+ 'VOTE_OPTION_NO': {
+ label: 'NO',
+ bgColor: '#edcdcd',
+ color: 'red'
+ },
+ 'VOTE_OPTION_ABSTAIN': {
+ label: 'ABSTAIN',
+ bgColor: '#cdd1ed',
+ color: '#0026ff'
+ },
+ 'VOTE_OPTION_NO_WITH_VETO': {
+ label: 'NO WITH VETO',
+ bgColor: '#f2dbf1',
+ color: '#df00fa'
+ }
+}
+
+const StyledTableCell = styled(TableCell)(({ theme }) => ({
+ [`&.${tableCellClasses.head}`]: {
+ backgroundColor: theme.palette.common.black,
+ color: theme.palette.common.white,
+ textAlign: 'center',
+ },
+ [`&.${tableCellClasses.body}`]: {
+ fontSize: 16,
+ textAlign: 'center'
+ },
+}));
+
+const StyledTableRow = styled(TableRow)(({ theme }) => ({
+ '&:nth-of-type(odd)': {
+ backgroundColor: theme.palette.action.hover,
+ },
+ // hide last border
+ '&:last-child td, &:last-child th': {
+ border: 0,
+ },
+}));
+
+export default function VotesTable({ rows, total,
+ pageNumber = 0, limit, handleMembersPagination }) {
+ const [page, setPage] = React.useState(pageNumber);
+ const [rowsPerPage, setRowsPerPage] = React.useState(limit);
+ const handleChangePage = (event, newPage) => {
+ setPage(newPage);
+
+ handleMembersPagination(Number(newPage), rowsPerPage, rows?.pagination?.next_key)
+ };
+
+ const handleChangeRowsPerPage = (event) => {
+ setRowsPerPage(+event.target.value);
+ setPage(+event.target.value);
+
+ handleMembersPagination(Number(page), +event.target.value, '')
+ };
+
+ return (
+
+ Votes
+
+
+
+ Voter
+ Option
+ Metdata
+ Submit Time
+
+
+
+ {rows?.votes?.map((row) => (
+
+
+ {row?.voter || '-'}
+
+
+
+ {votesStatus[row?.option]?.label || '-'}
+
+
+
+ {row?.metadata || '-'}
+ {
+ getLocalTime(row?.submit_time) || '-'}
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/components/group/bulk/Delegate.jsx b/src/components/group/bulk/Delegate.jsx
new file mode 100644
index 000000000..fb25f515d
--- /dev/null
+++ b/src/components/group/bulk/Delegate.jsx
@@ -0,0 +1,126 @@
+import { Box, Button, InputAdornment, TextField } from "@mui/material";
+import React, { useEffect, useState } from "react";
+import { Decimal } from "@cosmjs/math";
+import { useSelector } from "react-redux";
+import PropTypes from "prop-types";
+import Autocomplete from "@mui/material/Autocomplete";
+
+import { useForm, Controller, useFormContext } from "react-hook-form";
+
+Delegate.propTypes = {
+ currency: PropTypes.object.isRequired,
+};
+
+export default function Delegate(props) {
+ const { currency } = props;
+
+ const { handleSubmit, watch, control,
+ setValue,
+ formState: { errors }, } = useFormContext({
+ defaultValues: {
+ amount: 0,
+ validator: null,
+ },
+ });
+
+ var validators = useSelector((state) => state.staking.validators);
+ var [data, setData] = useState([]);
+
+ useEffect(() => {
+ const data = [];
+ for (let i = 0; i < validators.activeSorted.length; i++) {
+ const validator = validators.active[validators.activeSorted[i]];
+ const temp = {
+ label: validator.description.moniker,
+ value: validators.activeSorted[i],
+ };
+ data.push(temp);
+ }
+
+ for (let i = 0; i < validators.inactiveSorted.length; i++) {
+ const validator = validators.inactive[validators.inactiveSorted[i]];
+ if (!validator.jailed) {
+ const temp = {
+ label: validator.description.moniker,
+ value: validators.inactiveSorted[i],
+ };
+ data.push(temp);
+ }
+ }
+
+ setData(data);
+ }, [validators]);
+
+ return (
+ <>
+ (
+
+ option.value === value.value
+ }
+ options={data}
+ onChange={(event, item) => {
+ onChange(item);
+ }}
+ renderInput={(params) => (
+
+ )}
+ />
+ )}
+ />
+
+ {
+ return Number(value) > 0;
+ },
+ }}
+ render={({ field, fieldState: { error } }) => (
+
+ {currency?.coinDenom}
+
+ ),
+ }}
+ />
+ )}
+ />
+ >
+ );
+}
diff --git a/src/components/group/bulk/RedelegateForm.jsx b/src/components/group/bulk/RedelegateForm.jsx
new file mode 100644
index 000000000..61d4be4cf
--- /dev/null
+++ b/src/components/group/bulk/RedelegateForm.jsx
@@ -0,0 +1,252 @@
+import { Box, Button, InputAdornment, TextField } from "@mui/material";
+import React, { useEffect, useState } from "react";
+import { Decimal } from "@cosmjs/math";
+import { useSelector } from "react-redux";
+import PropTypes from "prop-types";
+import Autocomplete from "@mui/material/Autocomplete";
+import { Redelegate } from "../../../txns/staking";
+
+import { useForm, Controller, useFormContext } from "react-hook-form";
+
+RedelegateForm.propTypes = {
+ chainInfo: PropTypes.object.isRequired,
+ address: PropTypes.string.isRequired,
+ onRedelegate: PropTypes.object.isRequired,
+};
+
+function parseDelegation(delegation, currency) {
+ return (
+ parseFloat(delegation?.delegation?.shares) /
+ (10 ** currency?.coinDecimals).toFixed(6)
+ );
+}
+
+export default function RedelegateForm(props) {
+ const wallet = useSelector(state => state?.wallet);
+
+ const { chainInfo } = wallet;
+
+ const { handleSubmit, watch, control,
+ setValue,
+ formState: { errors }, } = useFormContext({
+ defaultValues: {
+ amount: 0,
+ source: null,
+ destination: null,
+ },
+ });
+
+ var validators = useSelector((state) => state.staking.validators);
+ const delegations = useSelector(
+ (state) => state.staking.delegations.delegations
+ );
+
+ var [data, setData] = useState([]);
+
+ useEffect(() => {
+ const data = [];
+
+ for (let i = 0; i < delegations.length; i++) {
+ if (validators.active[delegations[i].delegation.validator_address]) {
+ const temp = {
+ label:
+ validators.active[delegations[i].delegation.validator_address]
+ .description.moniker,
+ value: {
+ shares: parseDelegation(
+ delegations[i],
+ chainInfo.config.currencies[0]
+ ),
+ validator: delegations[i].delegation.validator_address,
+ },
+ };
+ data.push(temp);
+ } else if (
+ validators.inactive[delegations[i].delegation.validator_address]
+ ) {
+ const temp = {
+ label:
+ validators.inactive[delegations[i].delegation.validator_address]
+ .description.moniker,
+ value: {
+ shares: parseDelegation(
+ delegations[i],
+ chainInfo.config.currencies[0]
+ ),
+ validator: delegations[i].delegation.validator_address,
+ },
+ };
+ data.push(temp);
+ }
+ }
+
+ setData(data);
+ }, [delegations]);
+
+ const currency = chainInfo.config.currencies[0];
+ const onSubmit = (data) => {
+ const baseAmount = Decimal.fromUserInput(
+ data.amount,
+ Number(currency?.coinDecimals)
+ ).atomics;
+
+ const msgRedelegate = Redelegate(
+ data.delegator,
+ data.source?.value?.validator,
+ data.destination?.value,
+ baseAmount,
+ chainInfo.config.currencies[0].coinMinimalDenom
+ );
+
+ props.onRedelegate(msgRedelegate);
+ };
+
+ const [vals, setVals] = useState([]);
+ useEffect(() => {
+ const data = [];
+ for (let i = 0; i < validators.activeSorted.length; i++) {
+ const validator = validators.active[validators.activeSorted[i]];
+ const temp = {
+ label: validator.description.moniker,
+ value: validators.activeSorted[i],
+ };
+ data.push(temp);
+ }
+
+ for (let i = 0; i < validators.inactiveSorted.length; i++) {
+ const validator = validators.inactive[validators.inactiveSorted[i]];
+ if (!validator.jailed) {
+ const temp = {
+ label: validator.description.moniker,
+ value: validators.inactiveSorted[i],
+ };
+ data.push(temp);
+ }
+ }
+
+ setVals(data);
+ }, [validators]);
+
+ return (
+ <>
+ (
+
+ )}
+ />
+ (
+
+ option.value?.validator === value.value?.validator
+ }
+ options={data}
+ onChange={(event, item) => {
+ onChange(item);
+ }}
+ renderInput={(params) => (
+
+ )}
+ />
+ )}
+ />
+
+ (
+
+ option.value === value.value
+ }
+ options={vals}
+ onChange={(event, item) => {
+ onChange(item);
+ }}
+ renderInput={(params) => (
+
+ )}
+ />
+ )}
+ />
+
+ {
+ return Number(value) > 0;
+ },
+ }}
+ render={({ field, fieldState: { error } }) => (
+
+ {currency?.coinDenom}
+
+ ),
+ }}
+ />
+ )}
+ />
+ >
+ );
+}
diff --git a/src/components/group/bulk/Send.jsx b/src/components/group/bulk/Send.jsx
new file mode 100644
index 000000000..ff43e1be8
--- /dev/null
+++ b/src/components/group/bulk/Send.jsx
@@ -0,0 +1,96 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { Button, InputAdornment, TextField } from "@mui/material";
+import { Decimal } from "@cosmjs/math";
+import { Box } from "@mui/system";
+import { useForm, Controller, useFormContext } from "react-hook-form";
+import { fromBech32 } from "@cosmjs/encoding";
+
+Send.propTypes = {
+ chainInfo: PropTypes.object.isRequired,
+ address: PropTypes.string.isRequired,
+ onSend: PropTypes.object.isRequired,
+};
+
+export default function Send(props) {
+ const { currency } = props;
+
+ const { handleSubmit, watch, control,
+ setValue,
+ formState: { errors }, } = useFormContext({
+ defaultValues: {
+ amount: 0,
+ recipient: "",
+ },
+ });
+
+ return (
+ <>
+ {
+ try {
+ fromBech32(value);
+ return true;
+ } catch (error) {
+ return false;
+ }
+ },
+ }}
+ render={({ field, fieldState: { error } }) => (
+
+ )}
+ />
+ {
+ return Number(value) > 0;
+ },
+ }}
+ render={({ field, fieldState: { error } }) => (
+
+ {currency?.coinDenom}
+
+ ),
+ }}
+ />
+ )}
+ />
+ >
+ );
+}
diff --git a/src/components/group/bulk/UnDelegateForm.jsx b/src/components/group/bulk/UnDelegateForm.jsx
new file mode 100644
index 000000000..7ddf7d091
--- /dev/null
+++ b/src/components/group/bulk/UnDelegateForm.jsx
@@ -0,0 +1,176 @@
+import { Box, Button, InputAdornment, TextField } from "@mui/material";
+import React, { useEffect, useState } from "react";
+import { Decimal } from "@cosmjs/math";
+import { useSelector } from "react-redux";
+import PropTypes from "prop-types";
+import { useForm, Controller, useFormContext } from "react-hook-form";
+import Autocomplete from "@mui/material/Autocomplete";
+import { UnDelegate } from "../../../txns/staking";
+
+UnDelegateForm.propTypes = {
+ chainInfo: PropTypes.object.isRequired,
+ address: PropTypes.string.isRequired,
+ onUndelegate: PropTypes.func.isRequired,
+};
+
+function parseDelegation(delegation, currency) {
+ return (
+ parseFloat(delegation?.delegation?.shares) /
+ (10 ** currency?.coinDecimals).toFixed(6)
+ );
+}
+
+export default function UnDelegateForm(props) {
+ const { address } = props;
+
+ const wallet = useSelector(state => state?.wallet);
+ const { chainInfo } = wallet;
+
+ const {
+ handleSubmit,
+ control,
+ formState: { errors },
+ } = useFormContext({
+ defaultValues: {
+ amount: 0,
+ validator: null,
+ delegator: address,
+ },
+ });
+
+ const validators = useSelector((state) => state.staking.validators);
+ const delegations = useSelector(
+ (state) => state.staking.delegations.delegations
+ );
+ var [data, setData] = useState([]);
+
+ useEffect(() => {
+ const data = [];
+
+ for (let i = 0; i < delegations.length; i++) {
+ if (validators.active[delegations[i].delegation.validator_address]) {
+ const temp = {
+ label:
+ validators.active[delegations[i].delegation.validator_address]
+ .description.moniker,
+ value: {
+ shares: parseDelegation(
+ delegations[i],
+ chainInfo.config.currencies[0]
+ ),
+ validator: delegations[i].delegation.validator_address,
+ },
+ };
+ data.push(temp);
+ } else if (
+ validators.inactive[delegations[i].delegation.validator_address]
+ ) {
+ const temp = {
+ label:
+ validators.inactive[delegations[i].delegation.validator_address]
+ .description.moniker,
+ value: {
+ shares: parseDelegation(
+ delegations[i],
+ chainInfo.config.currencies[0]
+ ),
+ validator: delegations[i].delegation.validator_address,
+ },
+ };
+ data.push(temp);
+ }
+ }
+
+ setData(data);
+ }, [delegations]);
+
+ const currency = chainInfo.config.currencies[0];
+
+ return (
+ <>
+ (
+
+ )}
+ />
+ (
+
+ option.value?.validator === value.value?.validator
+ }
+ options={data}
+ onChange={(event, item) => {
+ onChange(item);
+ }}
+ renderInput={(params) => (
+
+ )}
+ />
+ )}
+ />
+
+ {
+ return Number(value) > 0;
+ },
+ }}
+ render={({ field, fieldState: { error } }) => (
+
+ {currency?.coinDenom}
+
+ ),
+ }}
+ />
+ )}
+ />
+ >
+ );
+}
diff --git a/src/components/group/groupCmpStyles.ts b/src/components/group/groupCmpStyles.ts
new file mode 100644
index 000000000..7329c55e4
--- /dev/null
+++ b/src/components/group/groupCmpStyles.ts
@@ -0,0 +1,21 @@
+export const gpStyles = {
+ alert_card: {
+ width: '100%',
+ mt: 2,
+ boxShadow: 'none'
+ },
+ policy_box: {
+ border: '1px solid #eeeeee',
+ p: 5,
+ mt: 2,
+ borderRadius: 2,
+ width: '70%',
+ m: '0 auto',
+
+ },
+ flex_center: { display: 'flex', justifyContent: 'center' },
+ f_22: {
+ fontSize: 22
+ },
+ j_center: {justifyContent: 'center', ml: 2}
+}
\ No newline at end of file
diff --git a/src/features/common/commonSlice.js b/src/features/common/commonSlice.js
index 562dfad43..0cb665579 100644
--- a/src/features/common/commonSlice.js
+++ b/src/features/common/commonSlice.js
@@ -7,7 +7,8 @@ const initialState = {
},
txSuccess: {
hash: ''
- }
+ },
+ txLoadRes: { load: false }
}
export const commonSlice = createSlice({
@@ -25,6 +26,12 @@ export const commonSlice = createSlice({
hash: action.payload.hash,
}
},
+ setTxLoad: (state) => {
+ state.txLoadRes = { load: true }
+ },
+ resetTxLoad: (state) => {
+ state.txLoadRes = { load: false }
+ },
resetTxHash: (state) => {
state.txSuccess = {
hash: '',
@@ -35,10 +42,21 @@ export const commonSlice = createSlice({
message: '',
type: ''
}
+ },
+ resetDecisionPolicies: (state) => {
+ state.groupPolicies = {}
+ },
+ resetActiveProposals: (state)=>{
+ state.policyProposals = {}
}
},
})
-export const { setError, resetError, setTxHash, resetTxHash } = commonSlice.actions
+export const {
+ setError, resetError,
+ resetActiveProposals,
+ resetDecisionPolicies,
+ setTxLoad, resetTxLoad,
+ setTxHash, resetTxHash } = commonSlice.actions
export default commonSlice.reducer
\ No newline at end of file
diff --git a/src/features/group/groupService.js b/src/features/group/groupService.js
index 47cf97e3c..36328b9a3 100644
--- a/src/features/group/groupService.js
+++ b/src/features/group/groupService.js
@@ -1,14 +1,21 @@
import Axios from 'axios';
-import { convertPaginationToParams } from '../utils';
+import { convertPaginationToParams, convertPaginationToParamsOffset } from '../utils';
const groupByAdminURL = (admin) => `/cosmos/group/v1/groups_by_admin/${admin}`
const groupByMemberURL = (address) => `/cosmos/group/v1/groups_by_member/${address}`
+const groupMembersURL = groupId => `/cosmos/group/v1/group_members/${groupId}`
+const groupByIdURL = groupId => `/cosmos/group/v1/group_info/${groupId}`
+const groupMembersByIdURL = groupId => `/cosmos/group/v1/group_members/${groupId}`
+const groupPoliciesByIdURL = groupId => `/cosmos/group/v1/group_policies_by_group/${groupId}`
+const groupPolicyProposalsURL = address => `/cosmos/group/v1/proposals_by_group_policy/${address}`
+const votesPropsalURL = proposal_id => `/cosmos/group/v1/votes_by_proposal/${proposal_id}`
+const GroupProposalURL = proposal_id => `/cosmos/group/v1/proposal/${proposal_id}`
const fetchGroupsByAdmin = (baseURL, admin, pagination) => {
let uri = `${baseURL}${groupByAdminURL(admin)}`
- const pageParams = convertPaginationToParams(pagination)
- if (pageParams !== "") uri += `?${pageParams}`
+ const pageParams = convertPaginationToParamsOffset(pagination)
+ if (pageParams !== "") uri += `?${pageParams}&pagination.count_total=true`
return Axios.get(uri, {
headers: {
@@ -19,6 +26,18 @@ const fetchGroupsByAdmin = (baseURL, admin, pagination) => {
const fetchGroupsByMember = (baseURL, address, pagination) => {
let uri = `${baseURL}${groupByMemberURL(address)}`
+ const pageParams = convertPaginationToParamsOffset(pagination)
+ if (pageParams !== "") uri += `?${pageParams}&pagination.count_total=true`
+
+ return Axios.get(uri, {
+ headers: {
+ Accept: 'application/json, text/plain, */*',
+ },
+ })
+}
+
+const fetchGroupMembers = (baseURL, groupId, pagination) => {
+ let uri = `${baseURL}${groupMembersURL(groupId)}`
const pageParams = convertPaginationToParams(pagination)
if (pageParams !== "") uri += `?${pageParams}`
@@ -29,9 +48,87 @@ const fetchGroupsByMember = (baseURL, address, pagination) => {
})
}
+const fetchGroupById = (baseURL, groupId) => {
+ let uri = `${baseURL}${groupByIdURL(groupId)}`
+
+ return Axios.get(uri, {
+ headers: {
+ Accept: 'application/json, text/plain, */*',
+ },
+ })
+}
+
+const fetchGroupMembersById = (baseURL, groupId, pagination) => {
+ let uri = `${baseURL}${groupMembersByIdURL(groupId)}`
+ const pageParams = convertPaginationToParams(pagination)
+ if (pageParams !== "") uri += `?${pageParams}&pagination.count_total=true`
+
+ return Axios.get(uri, {
+ headers: {
+ Accept: 'application/json, text/plain, */*',
+ },
+ })
+}
+
+const fetchVotesProposalById = (baseURL, proposalId, pagination) => {
+ let uri = `${baseURL}${votesPropsalURL(proposalId)}`
+ const pageParams = convertPaginationToParams(pagination)
+ if (pageParams !== "") uri += `?${pageParams}&pagination.count_total=true`
+
+ return Axios.get(uri, {
+ headers: {
+ Accept: 'application/json, text/plain, */*',
+ },
+ })
+}
+
+const fetchGroupPoliciesById = (baseURL, groupId, pagination) => {
+ let uri = `${baseURL}${groupPoliciesByIdURL(groupId)}`
+ const pageParams = convertPaginationToParams(pagination)
+ if (pageParams !== "") uri += `?${pageParams}&pagination.count_total=true`
+
+ return Axios.get(uri, {
+ headers: {
+ Accept: 'application/json, text/plain, */*',
+ },
+ })
+}
+
+
+const fetchGroupPolicyProposalsById = (baseURL, address, pagination) => {
+ let uri = `${baseURL}${groupPolicyProposalsURL(address)}`
+ const pageParams = convertPaginationToParams(pagination)
+ if (pageParams !== "") uri += `?${pageParams}&pagination.count_total=true`
+
+ return Axios.get(uri, {
+ headers: {
+ Accept: 'application/json, text/plain, */*',
+ },
+ })
+}
+
+const fetchGroupProposalById = (baseURL, id) => {
+ let uri = `${baseURL}${GroupProposalURL(id)}`
+
+ return Axios.get(uri, {
+ headers: {
+ Accept: 'application/json, text/plain, */*',
+ },
+ })
+}
+
+
+
const result = {
groupsByAdmin: fetchGroupsByAdmin,
groupsByMember: fetchGroupsByMember,
+ groupMembers: fetchGroupMembers,
+ fetchGroupById: fetchGroupById,
+ fetchGroupMembersById: fetchGroupMembersById,
+ fetchGroupPoliciesById: fetchGroupPoliciesById,
+ fetchGroupPolicyProposalsById,
+ fetchVotesProposalById,
+ fetchGroupProposalById,
}
export default result;
\ No newline at end of file
diff --git a/src/features/group/groupSlice.js b/src/features/group/groupSlice.js
index 129745834..f4bea5877 100644
--- a/src/features/group/groupSlice.js
+++ b/src/features/group/groupSlice.js
@@ -1,10 +1,11 @@
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
-import { fee, signAndBroadcastProto } from "../../txns/execute";
-import { CreateGroup, CreateGroupWithPolicy } from "../../txns/proto";
-import { setError, setTxHash } from "../common/commonSlice";
+import { fee, signAndBroadcastAddGroupPolicy, signAndBroadcastGroupMsg, signAndBroadcastGroupProposal, signAndBroadcastGroupProposalExecute, signAndBroadcastGroupProposalVote, signAndBroadcastLeaveGroup, signAndBroadcastProto, signAndBroadcastUpdateGroupAdmin, signAndBroadcastUpdateGroupMembers, signAndBroadcastUpdateGroupMetadata, signAndBroadcastUpdateGroupPolicy, signAndBroadcastUpdateGroupPolicyAdmin, signAndBroadcastUpdateGroupPolicyMetadata } from "../../txns/execute";
+import { CreateGroup, CreateGroupPolicy, CreateGroupProposal, CreateGroupWithPolicy, CreateLeaveGroupMember, CreateProposalExecute, CreateProposalVote, UpdateGroupAdmin, UpdateGroupMembers, UpdateGroupMetadata, UpdateGroupPolicy, UpdatePolicyAdmin, UpdatePolicyMetadata } from "../../txns/group/group";
+import { resetDecisionPolicies, resetTxLoad, setError, setTxHash, setTxLoad } from "../common/commonSlice";
import groupService from "./groupService";
const initialState = {
+ txCreateGroupRes: {},
tx: {
status: "idle",
type: "",
@@ -21,57 +22,838 @@ const initialState = {
status: "idle",
},
},
+ members: {
+ data: []
+ },
+ groupInfo: {
+ },
+ groupMembers: {},
+ groupPolicies: {},
+ groupProposalRes: {},
+ proposals: {},
+ voteRes: {},
+ executeRes: {},
+ updateGroupRes: {},
+ leaveGroupRes: {},
+ proposalVotes: {
+ data: []
+ },
+ groupProposal: {},
+ updateGroupPolicyRes: {},
+ updateGroupAdminRes: {},
+ updateGroupMetadataRes: {},
+ addGroupPolicyRes: {},
+ addPolicyMetadataRes: {},
+ updatePolicyAdminRes: {},
+ policyProposals: {},
};
-export const getGroupsByAdmin = createAsyncThunk(
- "group/group-by-admin",
- async (data) => {
- const response = await groupService.groupsByAdmin(
- data.baseURL,
- data.admin,
- data.pagination
- );
- return response.data;
+export const getGroupsByAdmin = createAsyncThunk(
+ "group/group-by-admin",
+ async (data) => {
+ const response = await groupService.groupsByAdmin(
+ data.baseURL,
+ data.admin,
+ data.pagination
+ );
+ return response.data;
+ }
+);
+
+export const getGroupById = createAsyncThunk(
+ "group/group-by-id",
+ async (data) => {
+ const response = await groupService.fetchGroupById(
+ data.baseURL, data.id
+ );
+ return response.data;
+ }
+);
+
+export const getGroupMembersById = createAsyncThunk(
+ "group/group-members-by-id",
+ async (data) => {
+ const response = await groupService.fetchGroupMembersById(
+ data.baseURL, data.id, data.pagination
+ );
+ return response.data;
+ }
+);
+
+export const getVotesProposalById = createAsyncThunk(
+ "group/group-proposal-votes",
+ async (data) => {
+ const response = await groupService.fetchVotesProposalById(
+ data.baseURL, data.id, data.pagination
+ );
+ return response.data;
+ }
+);
+
+export const getGroupProposalById = createAsyncThunk(
+ "group/group-proposal-info",
+ async (data) => {
+ const response = await groupService.fetchGroupProposalById(
+ data.baseURL, data.id
+ );
+ return response.data;
+ }
+);
+
+export const getGroupPoliciesById = createAsyncThunk(
+ "group/group-policies-by-id",
+ async (data, { dispatch }) => {
+ // dispatch(resetDecisionPolicies())
+ const response = await groupService.fetchGroupPoliciesById(
+ data.baseURL, data.id, data.pagination
+ );
+ return response.data;
+ }
+);
+
+export const getGroupsByMember = createAsyncThunk(
+ "group/group-by-member",
+ async (data) => {
+ const response = await groupService.groupsByMember(
+ data.baseURL,
+ data.address,
+ data.pagination
+ );
+ return response.data;
+ }
+);
+
+export const getGroupMembers = createAsyncThunk(
+ "group/get-group-members",
+ async (data) => {
+ const response = await groupService.groupMembers(data.baseURL, data.groupId);
+ return response.data;
+ }
+)
+
+export const getGroupPolicyProposalsByPage = createAsyncThunk(
+ "group/get-group-policy-proposals-by-page",
+ async (data, { dispatch }) => {
+ var totalData = [];
+ var allPolicies = [];
+
+ try {
+ allPolicies = await groupService.fetchGroupPoliciesById(
+ data.baseURL, data.groupId, data?.pagination
+ )
+ } catch (error) {
+ throw error;
+ }
+
+ const getProposalsPagination = async (address, pagination) => {
+ const response = await groupService.fetchGroupPolicyProposalsById(
+ data.baseURL, address,
+ pagination
+ );
+
+ let filteredProposals = response?.data?.proposals?.filter(p => p?.status === 'PROPOSAL_STATUS_SUBMITTED')
+
+ totalData = [...totalData, ...filteredProposals];
+
+ if (response?.data?.pagination?.next_key)
+ return getProposalsPagination(address, { limit: 1, key: response?.data?.pagination?.next_key })
+ else return totalData;
+ }
+
+
+ if (allPolicies?.data?.group_policies?.length) {
+ let data = allPolicies?.data?.group_policies || [];
+ let totalPolicyData = [];
+ for (let i = 0; i < data.length; i++) {
+
+ try {
+ await getProposalsPagination(data[i]?.address, {
+ limit: 1,
+ key: ''
+ });
+
+ } catch (error) {
+ console.log('Error while getting proposals', error)
+ throw error;
+ }
+ }
+
+ return totalData;
+ } else return ''
+ }
+)
+
+export const getGroupPolicyProposals = createAsyncThunk(
+ "group/get-group-policy-proposals",
+ async (data, { dispatch }) => {
+ const response = await groupService.fetchGroupPolicyProposalsById(
+ data.baseURL, data.address,
+ data.pagination
+ );
+
+ return response.data;
+ }
+)
+
+export const txGroupProposalVote = createAsyncThunk(
+ 'group/tx-group-proposal-vote',
+ async (data, { rejectWithValue, fulfillWithValue, dispatch }) => {
+ dispatch(setTxLoad());
+
+ try {
+ let msg = CreateProposalVote(
+ data.proposalId,
+ data.voter,
+ data.option,
+ data?.metadata || '',
+ )
+
+ console.log('msg----', msg)
+
+ const result = await signAndBroadcastGroupProposalVote(
+ data.admin,
+ [msg],
+ fee(data.denom, data.feeAmount, 260000),
+ data.chainId,
+ data.rpc
+ );
+
+ dispatch(resetTxLoad());
+ if (result?.code === 0) {
+ dispatch(
+ setTxHash({
+ hash: result?.transactionHash,
+ })
+ );
+ return fulfillWithValue({ txHash: result?.transactionHash });
+ } else {
+ console.log('Error while creating propsoal', result?.rawLog)
+ dispatch(
+ setError({
+ type: "error",
+ message: result?.rawLog,
+ })
+ );
+ return rejectWithValue(result?.rawLog);
+ }
+ } catch (error) {
+
+ dispatch(resetTxLoad());
+
+ console.log('Error while creating the group proposal', error.message)
+ dispatch(
+ setError({
+ type: "error",
+ message: error.message,
+ })
+ );
+ return rejectWithValue(error.message);
+ }
+ }
+)
+
+export const txGroupProposalExecute = createAsyncThunk(
+ 'group/tx-group-proposal-execute',
+ async (data, { rejectWithValue, fulfillWithValue, dispatch }) => {
+ dispatch(setTxLoad());
+ try {
+ let msg = CreateProposalExecute(
+ data.proposalId,
+ data.executor
+ )
+
+ console.log('msg----', msg)
+
+ const result = await signAndBroadcastGroupProposalExecute(
+ data.admin,
+ [msg],
+ fee(data.denom, data.feeAmount, 260000),
+ data.chainId,
+ data.rpc
+ );
+
+ dispatch(resetTxLoad());
+
+ if (result?.code === 0) {
+ dispatch(
+ setTxHash({
+ hash: result?.transactionHash,
+ })
+ );
+ return fulfillWithValue({ txHash: result?.transactionHash });
+ } else {
+ console.log('Error while creating propsoal', result?.rawLog)
+ dispatch(
+ setError({
+ type: "error",
+ message: result?.rawLog,
+ })
+ );
+ return rejectWithValue(result?.rawLog);
+ }
+ } catch (error) {
+
+ dispatch(resetTxLoad());
+
+ console.log('Error while creating the group proposal', error.message)
+ dispatch(
+ setError({
+ type: "error",
+ message: error.message,
+ })
+ );
+ return rejectWithValue(error.message);
+ }
+ }
+)
+
+export const txUpdateGroupAdmin = createAsyncThunk(
+ 'group/tx-update-group-admin',
+ async (data, { rejectWithValue, fulfillWithValue, dispatch }) => {
+ dispatch(setTxLoad());
+
+ try {
+ let msg = UpdateGroupAdmin(
+ data.admin,
+ data.groupId,
+ data.newAdmin
+ )
+
+ console.log('msg----', msg)
+
+ const result = await signAndBroadcastUpdateGroupAdmin(
+ data.signer,
+ [msg],
+ fee(data.denom, data.feeAmount, 260000),
+ data.chainId,
+ data.rpc
+ );
+ dispatch(resetTxLoad());
+ if (result?.code === 0) {
+ dispatch(
+ setTxHash({
+ hash: result?.transactionHash,
+ })
+ );
+ return fulfillWithValue({ txHash: result?.transactionHash });
+ } else {
+ console.log('Error while creating propsoal', result?.rawLog)
+ dispatch(
+ setError({
+ type: "error",
+ message: result?.rawLog,
+ })
+ );
+ return rejectWithValue(result?.rawLog);
+ }
+ } catch (error) {
+
+ dispatch(resetTxLoad());
+
+ console.log('Error while creating the group proposal', error.message)
+ dispatch(
+ setError({
+ type: "error",
+ message: error.message,
+ })
+ );
+ return rejectWithValue(error.message);
+ }
+ }
+)
+
+export const txUpdateGroupMetadata = createAsyncThunk(
+ 'group/tx-update-group-metadata',
+ async (data, { rejectWithValue, fulfillWithValue, dispatch }) => {
+ dispatch(setTxLoad());
+
+ try {
+ let msg = UpdateGroupMetadata(
+ data.admin,
+ data.groupId,
+ data.metadata
+ )
+
+ console.log('msg----', msg)
+
+ const result = await signAndBroadcastUpdateGroupMetadata(
+ data.signer,
+ [msg],
+ fee(data.denom, data.feeAmount, 260000),
+ data.chainId,
+ data.rpc
+ );
+ dispatch(resetTxLoad());
+ if (result?.code === 0) {
+ dispatch(
+ setTxHash({
+ hash: result?.transactionHash,
+ })
+ );
+ return fulfillWithValue({ txHash: result?.transactionHash });
+ } else {
+ console.log('Error while creating propsoal', result?.rawLog)
+ dispatch(
+ setError({
+ type: "error",
+ message: result?.rawLog,
+ })
+ );
+ return rejectWithValue(result?.rawLog);
+ }
+ } catch (error) {
+
+ dispatch(resetTxLoad());
+
+ console.log('Error while creating the group proposal', error.message)
+ dispatch(
+ setError({
+ type: "error",
+ message: error.message,
+ })
+ );
+ return rejectWithValue(error.message);
+ }
+ }
+)
+
+export const txCreateGroupProposal = createAsyncThunk(
+ 'group/tx-create-group-proposal',
+ async (data, { rejectWithValue, fulfillWithValue, dispatch }) => {
+ dispatch(setTxLoad());
+ console.log('proposal --- ', data)
+ try {
+ let msg = CreateGroupProposal(
+ data.groupPolicyAddress,
+ data.proposers,
+ data.metadata,
+ data.messages,
+ )
+
+ console.log('msg----', msg)
+
+ const result = await signAndBroadcastGroupProposal(
+ data.admin,
+ [msg],
+ fee(data.denom, data.feeAmount, 260000),
+ data.chainId,
+ data.rpc
+ );
+ dispatch(resetTxLoad());
+ if (result?.code === 0) {
+ dispatch(
+ setTxHash({
+ hash: result?.transactionHash,
+ })
+ );
+ return fulfillWithValue({ txHash: result?.transactionHash });
+ } else {
+ console.log('Error while creating propsoal', result?.rawLog)
+ dispatch(
+ setError({
+ type: "error",
+ message: result?.rawLog,
+ })
+ );
+ return rejectWithValue(result?.rawLog);
+ }
+ } catch (error) {
+
+ dispatch(resetTxLoad());
+
+ console.log('Error while creating the group proposal', error.message)
+ dispatch(
+ setError({
+ type: "error",
+ message: error.message,
+ })
+ );
+ return rejectWithValue(error.message);
+ }
+ }
+)
+
+export const txCreateGroup = createAsyncThunk(
+ "group/tx-create-group",
+ async (data, { rejectWithValue, fulfillWithValue, dispatch }) => {
+ dispatch(setTxLoad());
+ let msg;
+ try {
+ if (data?.members?.length > 0) {
+ if (data?.policyData && Object.keys(data?.policyData)?.length) {
+
+ msg = CreateGroupWithPolicy(
+ data.admin,
+ data.groupMetaData,
+ data.members,
+ data.decisionPolicy,
+ data.policyData,
+ data.policyAsAdmin
+ );
+ } else {
+ msg = CreateGroup(data.admin, data.groupMetaData, data?.members);
+ }
+ } else {
+ msg = CreateGroup(data.admin, data.groupMetaData, []);
+ }
+
+ console.log('msg--', msg)
+ console.log('admin--', data)
+
+ const result = await signAndBroadcastGroupMsg(
+ data.admin,
+ [msg],
+ fee(data.denom, data.feeAmount, 260000),
+ data.chainId,
+ data.rpc
+ );
+
+ dispatch(resetTxLoad());
+
+ if (result?.code === 0) {
+ dispatch(
+ setTxHash({
+ hash: result?.transactionHash,
+ })
+ );
+ return fulfillWithValue({ txHash: result?.transactionHash });
+ } else {
+ dispatch(
+ setError({
+ type: "error",
+ message: result?.rawLog,
+ })
+ );
+ return rejectWithValue(result?.rawLog);
+ }
+ } catch (error) {
+
+ dispatch(resetTxLoad());
+
+ console.log('Error while creating the group', error.message)
+ dispatch(
+ setError({
+ type: "error",
+ message: error.message,
+ })
+ );
+ return rejectWithValue(error.message);
+ }
+ }
+);
+
+export const txUpdateGroupMember = createAsyncThunk(
+ "group/tx-update-group-member",
+ async (data, { rejectWithValue, fulfillWithValue, dispatch }) => {
+ let msg;
+ dispatch(setTxLoad());
+ console.log({ data })
+ try {
+ msg = UpdateGroupMembers(
+ data.admin,
+ data.members,
+ data.groupId
+ );
+
+ console.log({ msg })
+
+ const result = await signAndBroadcastUpdateGroupMembers(
+ data.admin,
+ [msg],
+ fee(data.denom, data.feeAmount, 260000),
+ data.chainId,
+ data.rpc
+ );
+
+ dispatch(resetTxLoad());
+
+ if (result?.code === 0) {
+ dispatch(
+ setTxHash({
+ hash: result?.transactionHash,
+ })
+ );
+ return fulfillWithValue({ txHash: result?.transactionHash });
+ } else {
+ console.log('Error while creating the group', result?.rawLog)
+ dispatch(
+ setError({
+ type: "error",
+ message: result?.rawLog,
+ })
+ );
+ return rejectWithValue(result?.rawLog);
+ }
+ } catch (error) {
+
+ dispatch(resetTxLoad());
+
+ console.log('Error while creating the group', error.message)
+ dispatch(
+ setError({
+ type: "error",
+ message: error.message,
+ })
+ );
+ return rejectWithValue(error.message);
+ }
+ }
+);
+
+export const txAddGroupPolicy = createAsyncThunk(
+ "group/tx-add-group-policy",
+ async (data, { rejectWithValue, fulfillWithValue, dispatch }) => {
+ let msg;
+ dispatch(setTxLoad());
+ console.log({ data })
+ try {
+ msg = CreateGroupPolicy(
+ data.admin,
+ data.groupId,
+ data.policyMetadata
+ );
+
+ console.log({ msg })
+
+ const result = await signAndBroadcastAddGroupPolicy(
+ data.admin,
+ [msg],
+ fee(data.denom, data.feeAmount, 260000),
+ data.chainId,
+ data.rpc
+ );
+
+ dispatch(resetTxLoad());
+
+ if (result?.code === 0) {
+ dispatch(
+ setTxHash({
+ hash: result?.transactionHash,
+ })
+ );
+ return fulfillWithValue({ txHash: result?.transactionHash });
+ } else {
+ console.log('Error while creating the group', result?.rawLog)
+ dispatch(
+ setError({
+ type: "error",
+ message: result?.rawLog,
+ })
+ );
+ return rejectWithValue(result?.rawLog);
+ }
+ } catch (error) {
+
+ dispatch(resetTxLoad());
+
+ console.log('Error while creating the group', error.message)
+ dispatch(
+ setError({
+ type: "error",
+ message: error.message,
+ })
+ );
+ return rejectWithValue(error.message);
+ }
+ }
+);
+
+export const txUpdateGroupPolicy = createAsyncThunk(
+ "group/tx-update-group-policy",
+ async (data, { rejectWithValue, fulfillWithValue, dispatch }) => {
+ let msg;
+ dispatch(setTxLoad());
+ console.log({ data })
+ try {
+ msg = UpdateGroupPolicy(
+ data.admin,
+ data.groupPolicyAddress,
+ data.policyMetadata
+ );
+
+ console.log({ msg })
+
+ const result = await signAndBroadcastUpdateGroupPolicy(
+ data.admin,
+ [msg],
+ fee(data.denom, data.feeAmount, 260000),
+ data.chainId,
+ data.rpc
+ );
+
+ dispatch(resetTxLoad());
+
+ if (result?.code === 0) {
+ dispatch(
+ setTxHash({
+ hash: result?.transactionHash,
+ })
+ );
+ return fulfillWithValue({ txHash: result?.transactionHash });
+ } else {
+ console.log('Error while updating the policy metadata', result, result?.rawLog)
+ dispatch(
+ setError({
+ type: "error",
+ message: result?.rawLog,
+ })
+ );
+ return rejectWithValue(result?.rawLog);
+ }
+ } catch (error) {
+
+ dispatch(resetTxLoad());
+
+ console.log('Error while creating the group', error.message)
+ dispatch(
+ setError({
+ type: "error",
+ message: error.message,
+ })
+ );
+ return rejectWithValue(error.message);
+ }
}
);
-export const getGroupsByMember = createAsyncThunk(
- "group/group-by-member",
- async (data) => {
- const response = await groupService.groupsByMember(
- data.baseURL,
- data.address,
- data.pagination
- );
- return response.data;
+export const txUpdateGroupPolicyMetdata = createAsyncThunk(
+ "group/tx-update-group-policy-metadata",
+ async (data, { rejectWithValue, fulfillWithValue, dispatch }) => {
+ let msg;
+ dispatch(setTxLoad());
+ console.log({ data })
+ try {
+ msg = UpdatePolicyMetadata(
+ data.admin,
+ data.groupPolicyAddress,
+ data.metadata
+ );
+
+ console.log({ msg })
+
+ const result = await signAndBroadcastUpdateGroupPolicyMetadata(
+ data.admin,
+ [msg],
+ fee(data.denom, data.feeAmount, 260000),
+ data.chainId,
+ data.rpc
+ );
+
+ dispatch(resetTxLoad());
+
+ if (result?.code === 0) {
+ dispatch(
+ setTxHash({
+ hash: result?.transactionHash,
+ })
+ );
+ return fulfillWithValue({ txHash: result?.transactionHash });
+ } else {
+ console.log('Error while updating the policy metadata', result, result?.rawLog)
+ dispatch(
+ setError({
+ type: "error",
+ message: result?.rawLog,
+ })
+ );
+ return rejectWithValue(result?.rawLog);
+ }
+ } catch (error) {
+
+ dispatch(resetTxLoad());
+
+ console.log('Error while creating the group', error.message)
+ dispatch(
+ setError({
+ type: "error",
+ message: error.message,
+ })
+ );
+ return rejectWithValue(error.message);
+ }
}
);
-export const txCreateGroup = createAsyncThunk(
- "group/tx-create-group",
+export const txUpdateGroupPolicyAdmin = createAsyncThunk(
+ "group/tx-update-group-policy-admin",
async (data, { rejectWithValue, fulfillWithValue, dispatch }) => {
let msg;
+ dispatch(setTxLoad());
+ console.log({ data })
try {
- if (data.members.length > 0) {
- if (data.decisionPolicy) {
- msg = CreateGroupWithPolicy(
- data.admin,
- data.groupMetadata,
- data.members,
- data.decisionPolicy,
- data.policyMetadata,
- data.policyAsAdmin
- );
- }
- msg = CreateGroup(data.admin, data.groupMetadata, data.members);
+ msg = UpdatePolicyAdmin(
+ data.admin,
+ data.groupPolicyAddress,
+ data.newAdmin
+ );
+
+ console.log({ msg })
+
+ const result = await signAndBroadcastUpdateGroupPolicyAdmin(
+ data.admin,
+ [msg],
+ fee(data.denom, data.feeAmount, 260000),
+ data.chainId,
+ data.rpc
+ );
+
+ dispatch(resetTxLoad());
+
+ if (result?.code === 0) {
+ dispatch(
+ setTxHash({
+ hash: result?.transactionHash,
+ })
+ );
+ return fulfillWithValue({ txHash: result?.transactionHash });
} else {
- msg = CreateGroup(data.admin, data.groupMetadata, []);
+ console.log('Error while updating the policy metadata', result, result?.rawLog)
+ dispatch(
+ setError({
+ type: "error",
+ message: result?.rawLog,
+ })
+ );
+ return rejectWithValue(result?.rawLog);
}
- const result = await signAndBroadcastProto(
+ } catch (error) {
+
+ dispatch(resetTxLoad());
+
+ console.log('Error while creating the group', error.message)
+ dispatch(
+ setError({
+ type: "error",
+ message: error.message,
+ })
+ );
+ return rejectWithValue(error.message);
+ }
+ }
+);
+
+export const txLeaveGroupMember = createAsyncThunk(
+ "group/tx-leave-group-member",
+ async (data, { rejectWithValue, fulfillWithValue, dispatch }) => {
+ let msg;
+ dispatch(setTxLoad());
+ console.log({ data })
+ try {
+ msg = CreateLeaveGroupMember(
+ data.admin,
+ data.groupId
+ );
+
+ const result = await signAndBroadcastLeaveGroup(
+ data.admin,
[msg],
fee(data.denom, data.feeAmount, 260000),
+ data.chainId,
data.rpc
);
+
+ dispatch(resetTxLoad());
+
if (result?.code === 0) {
dispatch(
setTxHash({
@@ -80,6 +862,7 @@ export const txCreateGroup = createAsyncThunk(
);
return fulfillWithValue({ txHash: result?.transactionHash });
} else {
+ console.log('Error while creating the group', result?.rawLog)
dispatch(
setError({
type: "error",
@@ -89,6 +872,10 @@ export const txCreateGroup = createAsyncThunk(
return rejectWithValue(result?.rawLog);
}
} catch (error) {
+
+ dispatch(resetTxLoad());
+
+ console.log('Error while creating the group', error.message)
dispatch(
setError({
type: "error",
@@ -107,6 +894,16 @@ export const groupSlice = createSlice({
resetTxType: (state, _) => {
state.tx.type = "";
},
+ resetGroupTx: (state, _) => {
+ state.tx.status = '';
+ state.txCreateGroupRes = {};
+ },
+ resetUpdateGroupMember: (state) => {
+ state.updateGroupRes.status = '';
+ },
+ resetCreateGroupProposalRes: state => {
+ state.groupProposalRes.status = '';
+ }
},
// The `extraReducers` field lets the slice handle actions defined elsewhere,
// including actions generated by createAsyncThunk or in other slices.
@@ -146,14 +943,240 @@ export const groupSlice = createSlice({
builder
.addCase(txCreateGroup.pending, (state) => {
state.tx.status = `pending`;
+ state.txCreateGroupRes.status = 'pending';
})
.addCase(txCreateGroup.fulfilled, (state, _) => {
state.tx.status = `idle`;
+ state.txCreateGroupRes.status = 'idle';
})
.addCase(txCreateGroup.rejected, (state, _) => {
state.tx.status = `rejected`;
+ state.txCreateGroupRes.status = 'rejected';
+ });
+
+ builder
+ .addCase(getGroupMembers.pending, (state) => {
+ state.members.status = `pending`;
+ })
+ .addCase(getGroupMembers.fulfilled, (state, action) => {
+ state.members.status = 'idle';
+ console.log('action paydddddddd', action.payload)
+ state.members.data = [...state.members.data, action.payload]
+ })
+ .addCase(getGroupMembers.rejected, (state, _) => {
+ state.members.status = `rejected`;
+ });
+
+ builder
+ .addCase(getGroupById.pending, (state) => {
+ state.groupInfo.status = `pending`;
+ })
+ .addCase(getGroupById.fulfilled, (state, action) => {
+ console.log('fffffffff', action.payload)
+ state.groupInfo.status = 'idle';
+ state.groupInfo.data = action.payload
+ })
+ .addCase(getGroupById.rejected, (state, _) => {
+ state.groupInfo.status = `rejected`;
+ });
+
+ builder
+ .addCase(getGroupMembersById.pending, (state) => {
+ state.groupMembers.status = `pending`;
+ })
+ .addCase(getGroupMembersById.fulfilled, (state, action) => {
+ state.groupMembers.status = 'idle';
+ state.groupMembers.data = action.payload
+ })
+ .addCase(getGroupMembersById.rejected, (state, _) => {
+ state.groupMembers.status = `rejected`;
+ });
+
+ builder
+ .addCase(getGroupPoliciesById.pending, (state) => {
+ state.groupPolicies.status = `pending`;
+ })
+ .addCase(getGroupPoliciesById.fulfilled, (state, action) => {
+ state.groupPolicies.status = 'idle';
+ state.groupPolicies.data = action.payload
+ })
+ .addCase(getGroupPoliciesById.rejected, (state, _) => {
+ state.groupPolicies.status = `rejected`;
+ });
+
+ builder
+ .addCase(getVotesProposalById.pending, (state) => {
+ state.proposalVotes.status = `pending`;
+ })
+ .addCase(getVotesProposalById.fulfilled, (state, action) => {
+ state.proposalVotes.status = 'idle';
+ state.proposalVotes.data = action.payload
+ })
+ .addCase(getVotesProposalById.rejected, (state, _) => {
+ state.proposalVotes.status = `rejected`;
+ });
+
+ builder
+ .addCase(getGroupPolicyProposals.pending, (state) => {
+ state.proposals.status = `pending`;
+ })
+ .addCase(getGroupPolicyProposals.fulfilled, (state, action) => {
+ state.proposals.status = 'idle';
+ state.proposals.data = action.payload
+ })
+ .addCase(getGroupPolicyProposals.rejected, (state, _) => {
+ state.proposals.status = `rejected`;
+ });
+
+ builder
+ .addCase(txCreateGroupProposal.pending, (state) => {
+ state.groupProposalRes.status = `pending`;
+ })
+ .addCase(txCreateGroupProposal.fulfilled, (state, action) => {
+ state.groupProposalRes.status = 'idle';
+ })
+ .addCase(txCreateGroupProposal.rejected, (state, _) => {
+ state.groupProposalRes.status = `rejected`;
+ });
+
+ builder
+ .addCase(txGroupProposalVote.pending, (state) => {
+ state.voteRes.status = `pending`;
+ })
+ .addCase(txGroupProposalVote.fulfilled, (state, action) => {
+ state.voteRes.status = 'idle';
+ })
+ .addCase(txGroupProposalVote.rejected, (state, _) => {
+ state.voteRes.status = `rejected`;
+ });
+
+ builder
+ .addCase(txGroupProposalExecute.pending, (state) => {
+ state.executeRes.status = `pending`;
+ })
+ .addCase(txGroupProposalExecute.fulfilled, (state, action) => {
+ state.executeRes.status = 'idle';
+ })
+ .addCase(txGroupProposalExecute.rejected, (state, _) => {
+ state.executeRes.status = `rejected`;
+ });
+
+ builder
+ .addCase(txUpdateGroupMember.pending, (state) => {
+ state.updateGroupRes.status = `pending`;
+ })
+ .addCase(txUpdateGroupMember.fulfilled, (state, action) => {
+ state.updateGroupRes.status = 'idle';
+ })
+ .addCase(txUpdateGroupMember.rejected, (state, _) => {
+ state.updateGroupRes.status = `rejected`;
+ });
+
+ builder
+ .addCase(txLeaveGroupMember.pending, (state) => {
+ state.leaveGroupRes.status = `pending`;
+ })
+ .addCase(txLeaveGroupMember.fulfilled, (state, action) => {
+ state.leaveGroupRes.status = 'idle';
+ })
+ .addCase(txLeaveGroupMember.rejected, (state, _) => {
+ state.leaveGroupRes.status = `rejected`;
+ });
+
+ builder
+ .addCase(getGroupProposalById.pending, (state) => {
+ state.groupProposal.status = `pending`;
+ })
+ .addCase(getGroupProposalById.fulfilled, (state, action) => {
+ state.groupProposal.status = 'idle';
+ state.groupProposal.data = action.payload;
+ })
+ .addCase(getGroupProposalById.rejected, (state, _) => {
+ state.groupProposal.status = `rejected`;
+ });
+
+ builder
+ .addCase(txUpdateGroupPolicy.pending, (state) => {
+ state.updateGroupPolicyRes.status = `pending`;
+ })
+ .addCase(txUpdateGroupPolicy.fulfilled, (state, action) => {
+ state.updateGroupPolicyRes.status = 'idle';
+ })
+ .addCase(txUpdateGroupPolicy.rejected, (state, _) => {
+ state.updateGroupPolicyRes.status = `rejected`;
+ });
+
+ builder
+ .addCase(txUpdateGroupAdmin.pending, (state) => {
+ state.updateGroupAdminRes.status = `pending`;
+ })
+ .addCase(txUpdateGroupAdmin.fulfilled, (state, action) => {
+ state.updateGroupAdminRes.status = 'idle';
+ })
+ .addCase(txUpdateGroupAdmin.rejected, (state, _) => {
+ state.updateGroupAdminRes.status = `rejected`;
+ });
+
+ builder
+ .addCase(txUpdateGroupMetadata.pending, (state) => {
+ state.updateGroupMetadataRes.status = `pending`;
+ })
+ .addCase(txUpdateGroupMetadata.fulfilled, (state, action) => {
+ state.updateGroupMetadataRes.status = 'idle';
+ })
+ .addCase(txUpdateGroupMetadata.rejected, (state, _) => {
+ state.updateGroupMetadataRes.status = `rejected`;
+ });
+
+ builder
+ .addCase(txAddGroupPolicy.pending, (state) => {
+ state.addGroupPolicyRes.status = `pending`;
+ })
+ .addCase(txAddGroupPolicy.fulfilled, (state, action) => {
+ state.addGroupPolicyRes.status = 'idle';
+ })
+ .addCase(txAddGroupPolicy.rejected, (state, _) => {
+ state.addGroupPolicyRes.status = `rejected`;
+ });
+
+ builder
+ .addCase(txUpdateGroupPolicyMetdata.pending, (state) => {
+ state.updateGroupMetadataRes.status = `pending`;
+ })
+ .addCase(txUpdateGroupPolicyMetdata.fulfilled, (state, action) => {
+ state.updateGroupMetadataRes.status = 'idle';
+ })
+ .addCase(txUpdateGroupPolicyMetdata.rejected, (state, _) => {
+ state.updateGroupMetadataRes.status = `rejected`;
+ });
+
+ builder
+ .addCase(txUpdateGroupPolicyAdmin.pending, (state) => {
+ state.updatePolicyAdminRes.status = `pending`;
+ })
+ .addCase(txUpdateGroupPolicyAdmin.fulfilled, (state, action) => {
+ state.updatePolicyAdminRes.status = 'idle';
+ })
+ .addCase(txUpdateGroupPolicyAdmin.rejected, (state, _) => {
+ state.updatePolicyAdminRes.status = `rejected`;
+ });
+
+ builder
+ .addCase(getGroupPolicyProposalsByPage.pending, (state) => {
+ state.policyProposals.status = `pending`;
+ })
+ .addCase(getGroupPolicyProposalsByPage.fulfilled, (state, action) => {
+ state.policyProposals.status = 'idle';
+ state.policyProposals.data = action?.payload;
+ })
+ .addCase(getGroupPolicyProposalsByPage.rejected, (state, _) => {
+ state.policyProposals.status = `rejected`;
});
},
});
+export const { resetGroupTx,
+ resetCreateGroupProposalRes,
+ resetUpdateGroupMember } = groupSlice.actions;
+
export default groupSlice.reducer;
diff --git a/src/features/node/nodeService.js b/src/features/node/nodeService.js
new file mode 100644
index 000000000..6edb025ef
--- /dev/null
+++ b/src/features/node/nodeService.js
@@ -0,0 +1,16 @@
+import Axios from "axios";
+
+const NODE_STATUS_URL = "/cosmos/base/tendermint/v1beta1/node_info";
+
+const fetchNodeInfo = (baseURL) =>
+ Axios.get(`${baseURL}${NODE_STATUS_URL}`, {
+ headers: {
+ Accept: "application/json, text/plain, */*",
+ },
+ });
+
+const result = {
+ fetchNodeInfo
+};
+
+export default result;
diff --git a/src/features/node/nodeSlice.js b/src/features/node/nodeSlice.js
new file mode 100644
index 000000000..c10d3dea0
--- /dev/null
+++ b/src/features/node/nodeSlice.js
@@ -0,0 +1,42 @@
+import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
+import nodeService from "./nodeService";
+
+const initialState = {
+ nodeInfo: {}
+};
+
+export const getNodeInfo = createAsyncThunk(
+ "node/node-info",
+ async (data, { rejectWithValue }) => {
+ try {
+ const response = await nodeService.fetchNodeInfo(data.baseURL);
+ return response.data;
+ } catch (error) {
+ return rejectWithValue(error);
+ }
+ }
+);
+
+
+export const nodeSlice = createSlice({
+ name: "node",
+ initialState,
+ reducers: {
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(getNodeInfo.pending, (state) => {
+ state.nodeInfo.status = 'pending'
+ })
+ .addCase(getNodeInfo.fulfilled, (state, action) => {
+ state.nodeInfo.status = 'idle'
+ state.nodeInfo.data = action?.payload;
+ })
+ .addCase(getNodeInfo.rejected, (state, action) => {
+ state.nodeInfo.status = 'rejected'
+ });
+ },
+});
+
+export const { } = nodeSlice.actions;
+export default nodeSlice.reducer;
diff --git a/src/features/utils.js b/src/features/utils.js
index 6f22eb215..6482ab580 100644
--- a/src/features/utils.js
+++ b/src/features/utils.js
@@ -1,20 +1,42 @@
export const convertPaginationToParams = (pagination) => {
- let result = "";
- if (pagination === undefined || (pagination?.key === null && pagination?.limit === null) ||
- (pagination?.key === undefined && pagination?.limit === undefined)
- ) {
- return ""
- }
- if (pagination.key !== null) {
- result += `pagination.key=${encodeURIComponent(pagination.key)}`
- if (pagination.limit !== null) {
- result += `&pagination.limit=${pagination.limit}`
- }
- } else {
- if (pagination.limit !== null) {
- result += `pagination.limit=${pagination.limit}`
- }
- }
+ let result = "";
+ if (pagination === undefined || (pagination?.key === null && pagination?.limit === null) ||
+ (pagination?.key === undefined && pagination?.limit === undefined)
+ ) {
+ return ""
+ }
+ if (pagination.key !== null) {
+ result += `pagination.key=${encodeURIComponent(pagination.key)}`
+ if (pagination.limit !== null) {
+ result += `&pagination.limit=${pagination.limit}`
+ }
+ } else {
+ if (pagination.limit !== null) {
+ result += `pagination.limit=${pagination.limit}`
+ }
+ }
- return result
+ return result
+}
+
+export const convertPaginationToParamsOffset = (pagination) => {
+ let result = "";
+ if (pagination === undefined || (pagination?.offset === null &&
+ pagination?.limit === null) ||
+ (pagination?.offset === undefined && pagination?.limit === undefined)
+ ) {
+ return ""
+ }
+ if (pagination.offset !== null) {
+ result += `pagination.offset=${pagination.offset}`
+ if (pagination.limit !== null) {
+ result += `&pagination.limit=${pagination.limit}`
+ }
+ } else {
+ if (pagination.limit !== null) {
+ result += `pagination.limit=${pagination.limit}`
+ }
+ }
+
+ return result
}
\ No newline at end of file
diff --git a/src/pages/Dashboard.js b/src/pages/Dashboard.js
index 5145de130..817f4703e 100644
--- a/src/pages/Dashboard.js
+++ b/src/pages/Dashboard.js
@@ -18,6 +18,7 @@ import AlertTitle from "@mui/material/AlertTitle";
import Snackbar from "@mui/material/Snackbar";
import Overview from "./Overview";
import { CustomAppBar } from "../components/CustomAppBar";
+import { resetError, resetTxLoad, setError } from "../features/common/commonSlice";
import Page404 from "./Page404";
import AppDrawer from "../components/AppDrawer";
import { Alert } from "../components/Alert";
@@ -27,6 +28,13 @@ import { Paper, Typography } from "@mui/material";
import { exitAuthzMode } from "../features/authz/authzSlice";
import { copyToClipboard } from "../utils/clipboard";
+const GroupPage = lazy(() => import("./GroupPage"));
+const Group = lazy(() => import("./group/Group"));
+const Policy = lazy(() => import("./group/Policy"));
+const CreateGroupPage = lazy(() => import("./group/CreateGroup"));
+const Proposal = lazy(() => import("./group/Proposal"))
+const CreateProposal = lazy(() => import("./group/CreateProposal"))
+
const Authz = lazy(() => import("./Authz"));
const Validators = lazy(() => import("./Validators"));
const Proposals = lazy(() => import("./Proposals"));
@@ -51,6 +59,7 @@ function DashboardContent(props) {
setDarkMode(!darkMode);
};
+ const txLoadRes = useSelector(state => state?.common?.txLoadRes?.load)
const [pallet, setPallet] = useState(getPallet());
const balance = useSelector((state) => state.bank.balance);
@@ -310,11 +319,44 @@ function DashboardContent(props) {
}
>
- {/* }>
+ }>
+
+
+ }>
+ }>
+
+
+
+ }>
+ }>
+
+
+
+ }>
+ }>
+
+
+ }>
+ }>
+
+
+
+ }>
}
- > */}
+ element={
+ }>
+
+
+ }
+ >
+
}>
@@ -352,6 +394,33 @@ function DashboardContent(props) {
<>>
)}
+ {
+ showTxSnack(false);
+ }}
+ anchorOrigin={{ vertical: "top", horizontal: "right" }}
+ >
+ ,
+ }}
+ onClose={() => {
+ dispatch(resetTxLoad())
+ }}
+ severity="info"
+ sx={{ width: "100%" }}
+ >
+
+ Loading..
+
+
+ Please wait for sometime.
+
+
+
import('./group/AdminGroupList'))
+const MemberGroupList = lazy(() => import('./group/MemberGroupList'))
- const address = useSelector((state) => state.wallet.address);
- const chainInfo = useSelector((state) => state.wallet.chainInfo);
- const groups = useSelector((state) => state.group.groups);
- const dispatch = useDispatch();
- useEffect(() => {
- if (address.length > 0)
- dispatch(
- getGroupsByAdmin({
- baseURL: chainInfo.config.rest,
- admin: address,
- })
- );
- }, [address]);
+export default function GroupPage() {
+ const [tab, setTab] = useState(0);
- useEffect(() => {
- if (address.length > 0 && selectedTab === "member")
- dispatch(
- getGroupsByMember({
- baseURL: chainInfo.config.rest,
- address: address,
- })
- );
- }, [selectedTab]);
+ const handleTabChange = (value) => {
+ setTab(value);
+ }
const navigate = useNavigate();
function navigateTo(path) {
@@ -47,10 +27,7 @@ export default function GroupPage() {
-
-
-
-
-
-
- {selectedTab === "admin" ? (
- {
- console.log(group);
- }}
- />
- ) : (
- {
- console.log(group);
- }}
- />
- )}
+
+
+
+
+
+ }>
+
+
+
+
+
+ }>
+
+
+
-
+
);
}
diff --git a/src/pages/group/ActiveProposals.jsx b/src/pages/group/ActiveProposals.jsx
new file mode 100644
index 000000000..58bde33d2
--- /dev/null
+++ b/src/pages/group/ActiveProposals.jsx
@@ -0,0 +1,75 @@
+import { Box, CircularProgress, Grid } from '@mui/material'
+import React, { useEffect, useState } from 'react'
+import { useDispatch, useSelector } from 'react-redux'
+import AlertMsg from '../../components/group/AlertMsg';
+import ProposalCard from '../../components/group/ProposalCard';
+import { resetActiveProposals } from '../../features/common/commonSlice';
+import { getGroupPoliciesById, getGroupPolicyProposals, getGroupPolicyProposalsByPage } from '../../features/group/groupSlice';
+
+const limit = 100;
+
+function ActiveProposals({ id, wallet }) {
+ const dispatch = useDispatch();
+
+ var [proposals, setProposals] = useState([]);
+
+ const proposalsRes = useSelector(state => state.group?.policyProposals)
+ console.log('proposa ---res', proposalsRes)
+
+ const getProposalByAddress = () => {
+ dispatch(getGroupPolicyProposalsByPage({
+ baseURL: wallet?.chainInfo?.config?.rest,
+ groupId: id,
+ }))
+ }
+
+ useEffect(() => {
+ getProposalByAddress();
+ }, [])
+
+ useEffect(() => {
+ setProposals([])
+ if (proposalsRes?.status === 'idle') {
+
+ // let allProposals = proposalsRes?.data?.filter(p => p.status === 'PROPOSAL_STATUS_SUBMITTED')
+ // let allProposals = proposalsRes?.data?.sort((a, b) => b.id - a.id)
+ // proposals = [...proposals, ...allProposals]
+ setProposals([...proposalsRes?.data])
+ }
+
+ }, [proposalsRes?.status])
+
+ useEffect(() => {
+ return () => {
+ // setProposals([])
+ dispatch(resetActiveProposals())
+ }
+ })
+
+ return (
+
+
+ {
+ !proposals?.length ?
+ : null
+
+ }
+
+ {
+ proposalsRes?.status === 'pending' ?
+ : null
+ }
+
+ {
+ proposals?.map(p => (
+
+
+
+ ))
+ }
+
+
+ )
+}
+
+export default ActiveProposals
\ No newline at end of file
diff --git a/src/pages/group/AddFileTx.jsx b/src/pages/group/AddFileTx.jsx
new file mode 100644
index 000000000..973cef154
--- /dev/null
+++ b/src/pages/group/AddFileTx.jsx
@@ -0,0 +1,566 @@
+import {
+ Box, FormControl, Grid, InputLabel,
+ MenuItem, Pagination, Select, Typography,
+ IconButton,
+ Button,
+ TextField,
+} from '@mui/material'
+import React, { useState, useEffect } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { Divider } from "@mui/material";
+import { useForm, Controller, FormProvider, useFormContext } from "react-hook-form";
+import DeleteOutline from "@mui/icons-material/DeleteOutline";
+
+import FileProposalOptions from '../../components/group/FileProposalOptions';
+import {
+ DELEGATE_TYPE_URL,
+ parseDelegateMsgsFromContent,
+ parseReDelegateMsgsFromContent,
+ parseSendMsgsFromContent,
+ parseUnDelegateMsgsFromContent,
+ REDELEGATE_TYPE_URL,
+ SEND_TYPE_URL,
+ UNDELEGATE_TYPE_URL,
+} from "./utils";
+
+import { setError } from '../../features/common/commonSlice';
+import TxBasicFields from '../../components/group/TxBasicFields';
+import { parseBalance } from "../../utils/denom";
+import { shortenAddress } from '../../utils/util';
+import { useNavigate, useParams } from 'react-router-dom';
+import { resetCreateGroupProposalRes, txCreateGroupProposal } from '../../features/group/groupSlice';
+
+const TYPE_SEND = "SEND";
+const TYPE_DELEGATE = "DELEGATE";
+const TYPE_UNDELEGATE = "UNDELEGATE";
+const TYPE_REDELEGATE = "REDELEGATE";
+
+
+const PER_PAGE = 6;
+
+function AddFileTx({ address }) {
+ const { policyAddress, id } = useParams();
+
+ const [txType, setTxType] = useState();
+ const [messages, setMessages] = useState([]);
+ const [slicedMsgs, setSlicedMsgs] = useState([]);
+ const [currentPage, setCurrentPage] = useState(1);
+
+ const dispatch = useDispatch();
+
+ const wallet = useSelector(state => state?.wallet);
+ const { chainInfo } = wallet;
+
+ const methods = useForm({
+ defaultValues: {
+ gas: 20000
+ }
+ });
+ const { setValue, control, handleSubmit } = methods;
+
+ var createRes = useSelector((state) => state.group.groupProposalRes);
+
+ let navigate = useNavigate();
+
+ useEffect(() => {
+ if (createRes?.status === "rejected") {
+ dispatch(
+ setError({
+ type: "error",
+ message: createRes?.error,
+ })
+ );
+ } else if (createRes?.status === "idle") {
+ dispatch(
+ setError({
+ type: "success",
+ message: "Transaction created",
+ })
+ );
+
+ setTimeout(() => {
+ navigate(`/groups/${id}/policies/${policyAddress}`);
+ }, 200);
+ }
+ }, [createRes?.status]);
+
+ useEffect(() => {
+ return () => {
+ dispatch(resetCreateGroupProposalRes())
+ }
+ }, [])
+
+ useEffect(() => {
+ if (messages.length < PER_PAGE) {
+ setSlicedMsgs(messages);
+ } else {
+ setCurrentPage(1);
+ setSlicedMsgs(messages?.slice(0, 1 * PER_PAGE));
+ }
+ }, [messages]);
+
+ const onDeleteMsg = (index) => {
+ const arr = messages.filter((_, i) => i !== index);
+ setMessages(arr);
+ setValue('msgs', arr)
+ };
+
+ const onFileContents = (content, type) => {
+ switch (type) {
+ case TYPE_SEND: {
+ const [parsedTxns, error] = parseSendMsgsFromContent(policyAddress, content);
+ if (error) {
+ dispatch(
+ setError({
+ type: "error",
+ message: error,
+ })
+ );
+ } else {
+ setMessages(parsedTxns);
+ methods.setValue('msgs', parsedTxns)
+ }
+ break;
+ }
+ case TYPE_DELEGATE: {
+ const [parsedTxns, error] = parseDelegateMsgsFromContent(
+ policyAddress,
+ content
+ );
+ if (error) {
+ dispatch(
+ setError({
+ type: "error",
+ message: error,
+ })
+ );
+ } else {
+ setMessages(parsedTxns);
+ methods.setValue('msgs', parsedTxns)
+ }
+ break;
+ }
+ case TYPE_REDELEGATE: {
+ const [parsedTxns, error] = parseReDelegateMsgsFromContent(
+ policyAddress,
+ content
+ );
+ if (error) {
+ dispatch(
+ setError({
+ type: "error",
+ message: error,
+ })
+ );
+ } else {
+ setMessages(parsedTxns);
+ methods.setValue('msgs', parsedTxns)
+ }
+ break;
+ }
+ case TYPE_UNDELEGATE: {
+ const [parsedTxns, error] = parseUnDelegateMsgsFromContent(
+ policyAddress,
+ content
+ );
+ if (error) {
+ dispatch(
+ setError({
+ type: "error",
+ message: error,
+ })
+ );
+ } else {
+ setMessages(parsedTxns);
+ methods.setValue('msgs', parsedTxns);
+ }
+ break;
+ }
+ default:
+ setMessages([]);
+ methods.setValue('msgs', [])
+ }
+ };
+
+ const renderMessage = (msg, index, currency, onDelete) => {
+ switch (msg.typeUrl) {
+ case SEND_TYPE_URL: {
+ return RenderSendMessage(msg, index, currency, onDelete);
+ }
+ case DELEGATE_TYPE_URL:
+ return RenderDelegateMessage(msg, index, currency, onDelete);
+ case UNDELEGATE_TYPE_URL:
+ return RenderUnDelegateMessage(msg, index, currency, onDelete);
+ case REDELEGATE_TYPE_URL:
+ return RenderReDelegateMessage(msg, index, currency, onDelete);
+ default:
+ return "";
+ }
+ };
+
+ const onSubmit = (data) => {
+ dispatch(txCreateGroupProposal(
+ {
+ metadata: data?.metadata,
+ admin: wallet?.address,
+ proposers: [wallet?.address],
+ messages: data?.msgs,
+ groupPolicyAddress: policyAddress,
+ chainId: chainInfo?.config?.chainId,
+ rpc: chainInfo?.config?.rpc,
+ denom: chainInfo?.config.currencies[0].coinMinimalDenom,
+ feeAmount: data?.fees,
+ memo: data?.memo,
+ gas: data?.gas
+ }
+ ))
+ };
+
+ return (
+
+
+
+
+
+ Select Transaction
+
+
+
+
+ {txType && || null}
+
+
+
+
+ Messages
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default AddFileTx;
+
+export const RenderSendMessage = (message, index, currency, onDelete) => {
+ return (
+
+
+
+ #{index + 1}
+
+
+ Send
+
+
+ {parseBalance(
+ message.value.amount,
+ currency.coinDecimals,
+ currency.coinMinimalDenom
+ )}
+ {currency.coinDenom}
+
+
+ to
+
+
+ {shortenAddress(message.value.toAddress, 21)}
+
+
+ {onDelete ? (
+ onDelete(index)}
+ >
+
+
+ ) : null}
+
+ );
+};
+
+export const RenderDelegateMessage = (message, index, currency, onDelete) => {
+ return (
+
+
+
+ #{index + 1}
+
+
+ Delegate
+
+
+ {parseBalance(
+ message.value.amount,
+ currency.coinDecimals,
+ currency.coinMinimalDenom
+ )}
+ {currency.coinDenom}
+
+
+ to
+
+
+ {shortenAddress(message.value.validatorAddress, 21)}
+
+
+ {onDelete ? (
+ onDelete(index)}
+ >
+
+
+ ) : null}
+
+ );
+};
+
+export const RenderUnDelegateMessage = (message, index, currency, onDelete) => {
+ return (
+
+
+
+ #{index + 1}
+
+
+ Undelegate
+
+
+ {parseBalance(
+ [message.value.amount],
+ currency.coinDecimals,
+ currency.coinMinimalDenom
+ )}
+ {currency.coinDenom}
+
+
+ from
+
+
+ {shortenAddress(message.value?.validatorAddress || "", 21)}
+
+
+ {onDelete ? (
+ onDelete(index)}
+ >
+
+
+ ) : null}
+
+ );
+};
+
+export const RenderReDelegateMessage = (message, index, currency, onDelete) => {
+ return (
+
+
+
+ #{index + 1}
+
+
+ Redelegate
+
+
+ {parseBalance(
+ message.value.amount,
+ currency.coinDecimals,
+ currency.coinMinimalDenom
+ )}
+ {currency.coinDenom}
+
+
+ from
+
+
+ {shortenAddress(message.value.validatorSrcAddress, 21)}
+
+
+ to
+
+
+ {shortenAddress(message.value.validatorDstAddress, 21)}
+
+
+ {onDelete ? (
+ onDelete(index)}
+ >
+
+
+ ) : null}
+
+ );
+};
\ No newline at end of file
diff --git a/src/pages/group/AddManualTx.jsx b/src/pages/group/AddManualTx.jsx
new file mode 100644
index 000000000..7a1e99555
--- /dev/null
+++ b/src/pages/group/AddManualTx.jsx
@@ -0,0 +1,255 @@
+import { Box, Button, FormControl, InputLabel, MenuItem, Select, TextField, Typography } from '@mui/material'
+import React, { useEffect, useState } from 'react'
+import Delegate from '../../components/group/bulk/Delegate';
+import RedelegateForm from '../../components/group/bulk/RedelegateForm';
+import Send from '../../components/group/bulk/Send';
+import UnDelegateForm from '../../components/group/bulk/UnDelegateForm';
+import TxBasicFields from '../../components/group/TxBasicFields';
+import { useForm, FormProvider, useFormContext, Controller } from "react-hook-form";
+import { Decimal } from "@cosmjs/math";
+import { getAllValidators, getDelegations } from '../../features/staking/stakeSlice';
+import { useDispatch, useSelector } from 'react-redux';
+import { resetCreateGroupProposalRes, txCreateGroupProposal } from '../../features/group/groupSlice';
+import { fee } from '../../txns/execute';
+import { useNavigate, useParams } from 'react-router-dom';
+import { setError } from '../../features/common/commonSlice';
+
+const TYPE_SEND = "SEND";
+const TYPE_DELEGATE = "DELEGATE";
+const TYPE_UNDELEGATE = "UNDELEGATE";
+const TYPE_REDELEGATE = "REDELEGATE";
+
+const getAmountInAtomics = (amount, currency) => {
+ const amountInAtomics = Decimal.fromUserInput(
+ amount,
+ Number(currency.coinDecimals)
+ ).atomics;
+
+ return {
+ amount: amountInAtomics,
+ denom: currency.coinMinimalDenom,
+ }
+}
+
+function AddManualTx({
+ address,
+ chainInfo,
+ handleCancel
+}) {
+ const { policyAddress, id } = useParams();
+
+ const currency = chainInfo?.config?.currencies[0];
+
+ const [txType, setTxType] = useState();
+
+ const methods = useForm({
+ defaultValues: {
+ gas: 20000
+ }
+ });
+ const dispatch = useDispatch();
+
+ const validators = useSelector((state) => state.staking.validators);
+ const wallet = useSelector(state => state.wallet);
+
+ useEffect(() => {
+ dispatch(
+ getAllValidators({
+ baseURL: chainInfo.config.rest,
+ status: null,
+ })
+ );
+
+ dispatch(
+ getDelegations({
+ baseURL: chainInfo.config.rest,
+ address: address,
+ })
+ );
+ }, []);
+
+ var createRes = useSelector((state) => state.group.groupProposalRes);
+
+ let navigate = useNavigate();
+
+ useEffect(() => {
+ if (createRes?.status === "rejected") {
+ dispatch(
+ setError({
+ type: "error",
+ message: createRes?.error,
+ })
+ );
+ } else if (createRes?.status === "idle") {
+ dispatch(
+ setError({
+ type: "success",
+ message: "Transaction created",
+ })
+ );
+
+ setTimeout(() => {
+ navigate(`/groups/${id}/policies/${policyAddress}`);
+ }, 200);
+ }
+ }, [createRes?.status]);
+
+ useEffect(() => {
+ return () => {
+ dispatch(resetCreateGroupProposalRes())
+ }
+ }, [])
+
+
+ const onSubmit = data => {
+ let msg = {
+ };
+
+ switch (data.txType) {
+ case TYPE_SEND:
+ msg = {
+ typeUrl: "/cosmos.bank.v1beta1.MsgSend",
+ value: {
+ fromAddress: address,
+ toAddress: data.toAddress,
+ amount: [getAmountInAtomics(data.amount, currency)]
+ },
+ ...msg
+ }
+ break;
+
+ case TYPE_DELEGATE:
+ msg = {
+ typeUrl: "/cosmos.staking.v1beta1.MsgDelegate",
+ value: {
+ delegatorAddress: address,
+ validatorAddress: data?.validator?.value,
+ amount: getAmountInAtomics(data.amount, currency)
+ },
+ ...msg
+ }
+ break;
+ }
+
+ dispatch(txCreateGroupProposal(
+ {
+ metadata: data?.metadata,
+ admin: wallet?.address,
+ proposers: [wallet?.address],
+ messages: [msg],
+ groupPolicyAddress: address,
+ chainId: chainInfo?.config?.chainId,
+ rpc: chainInfo?.config?.rpc,
+ denom: chainInfo?.config.currencies[0].coinMinimalDenom,
+ feeAmount: data?.fees,
+ memo: data?.memo,
+ gas: data?.gas
+ }
+ ))
+ };
+
+
+ return (
+
+
+
+
+
+ Select Transaction
+
+
+
+
+
+
+
+ )
+}
+
+export default AddManualTx
\ No newline at end of file
diff --git a/src/pages/group/AdminGroupList.jsx b/src/pages/group/AdminGroupList.jsx
new file mode 100644
index 000000000..a8989b1c6
--- /dev/null
+++ b/src/pages/group/AdminGroupList.jsx
@@ -0,0 +1,53 @@
+import React, { useState, useEffect } from 'react'
+import { useDispatch, useSelector } from 'react-redux';
+import GroupList from '../../components/group/GroupList';
+import { getGroupsByAdmin } from '../../features/group/groupSlice';
+
+function AdminGroupList() {
+ const [adminTotal, setAdminTotal] = useState(0);
+ const limit = 9;
+ const dispatch = useDispatch();
+
+ const address = useSelector((state) => state.wallet.address);
+ const chainInfo = useSelector((state) => state.wallet.chainInfo);
+ const groups = useSelector((state) => state.group.groups);
+
+ const fetchGroupsByAdmin = (offset = 0, limit = 9) => {
+
+ dispatch(
+ getGroupsByAdmin({
+ baseURL: chainInfo.config.rest,
+ admin: address,
+ pagination: {
+ offset,
+ limit
+ }
+ })
+ );
+ }
+
+ useEffect(() => {
+ fetchGroupsByAdmin(0, limit);
+ }, [address]);
+
+ useEffect(() => {
+ if (Number(groups?.admin?.pagination?.total))
+ setAdminTotal(Number(groups?.admin?.pagination?.total))
+ }, [groups?.admin?.pagination?.total])
+
+ const handlePagination = (page) => {
+ fetchGroupsByAdmin(page, limit)
+ }
+
+ return (
+
+ )
+}
+
+export default AdminGroupList
\ No newline at end of file
diff --git a/src/pages/group/CreateGroup.jsx b/src/pages/group/CreateGroup.jsx
index 773b4bd2b..d48d96209 100644
--- a/src/pages/group/CreateGroup.jsx
+++ b/src/pages/group/CreateGroup.jsx
@@ -1,125 +1,75 @@
import Button from "@mui/material/Button";
-import React, { useState } from "react";
-import DialogAddGroupMember from "../../components/group/DialogAddGroupMember";
-import { Grid, IconButton, Paper, TextField, Typography } from "@mui/material";
+import React, { useEffect, useState } from "react";
+import { Paper, TextField, Typography } from "@mui/material";
import { Box } from "@mui/system";
-import { Controller, useForm } from "react-hook-form";
-import { shortenAddress } from "../../utils/util";
-import DeleteOutline from "@mui/icons-material/DeleteOutline";
-import ModeEditOutlineOutlinedIcon from "@mui/icons-material/ModeEditOutlineOutlined";
-import DialogAttachPolicy from "../../components/group/DialogAttachPolicy";
-import { useDispatch } from "react-redux";
+import { Controller, useFieldArray, useForm } from "react-hook-form";
+import { useDispatch, useSelector } from "react-redux";
+import { resetGroupTx, txCreateGroup } from "../../features/group/groupSlice";
+import CreateGroupForm from "./CreateGroupForm";
+import CreateGroupPolicy from "./CreateGroupPolicy";
+import { useNavigate } from "react-router-dom";
+import PersonAddAltIcon from '@mui/icons-material/PersonAddAlt';
+import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
+
export default function CreateGroupPage() {
- const [showMemberDialog, setShowMemberDialog] = useState(false);
- const [members, setMembers] = useState([]);
+ const [showAddPolicyForm, setShowAddPolicyForm] = useState(null);
- const showGroupMemberDialog = () => {
- setShowMemberDialog(true);
- };
+ const wallet = useSelector((state) => state.wallet);
+ const { chainInfo, connected, address } = wallet;
+ const navigate = useNavigate();
+ const txCreateGroupRes = useSelector(state => state?.group?.txCreateGroupRes);
- const {
- handleSubmit,
- control,
- setValue,
- formState: { errors },
- } = useForm({
- defaultValues: {
- metadata: "",
- },
- });
+ useEffect(() => {
+ return () => {
+ dispatch(resetGroupTx());
+ }
+ }, [])
+
+ useEffect(() => {
+ if (txCreateGroupRes?.status === 'idle') {
+ navigate(`/group`)
+ }
+ }, [txCreateGroupRes?.status])
const dispatch = useDispatch();
const onSubmit = (data) => {
- console.log(data);
- console.log(members);
- console.log(policyData);
- // dispatch({
- // granter: address,
- // grantee: data.grantee,
- // spendLimit: amountToMinimalValue(data.spendLimit, chainInfo.config.currencies[0]),
- // expiration: data.expiration,
- // denom: currency.coinMinimalDenom,
- // chainId: chainInfo.config.chainId,
- // rpc: chainInfo.config.rpc,
- // feeAmount: chainInfo.config.gasPriceStep.average,
- // })
- };
+ const dataObj = {
+ admin: address,
+ granter: address,
+ grantee: data.grantee,
+ members: data.members,
+ groupMetaData: data?.metadata,
+ expiration: data?.expiration,
+ chainId: chainInfo.config.chainId,
+ rpc: chainInfo.config.rpc,
+ feeAmount: chainInfo.config.gasPriceStep.average,
+ denom: chainInfo?.config?.currencies?.[0]?.coinMinimalDenom
+ }
- const [memberFields, setMemberFields] = useState({
- address: "",
- weight: "",
- metadata: "",
- });
- const removeMember = (address) => {
- const newMembers = members.filter(function (member) {
- return member.address != address;
- });
- setMembers(newMembers);
- };
+ if (data.policyMetadata) {
+ dataObj['policyData'] = data.policyMetadata
+ }
- const editMember = (address, weight, metadata) => {
- setMemberFields({
- address: address,
- weight: weight,
- metadata: metadata,
- });
- setShowMemberDialog(true);
+ dispatch(txCreateGroup(dataObj))
};
- const [policyDialogOpen, setpolicyDialogOpen] = useState(false);
- const [policyData, setPolicyData] = useState({});
- const onAttachPolicy = (data) => {
- setpolicyDialogOpen(false);
- setPolicyData(data);
- };
- return (
- <>
- {showMemberDialog ? (
- {
- for (let i = 0; i < members.length; i++) {
- if (members[i].address === address) {
- members[i] = {
- address: address,
- metadata: metadata,
- weight: weight,
- };
- setShowMemberDialog(false);
- return;
- }
- }
+ const { register, control,
+ handleSubmit,
+ watch,
+ setValue,
+ formState: { errors },
+ reset, trigger, setError } = useForm({});
- setMembers([
- ...members,
- {
- address: address,
- metadata: metadata,
- weight: weight,
- },
- ]);
+ const { fields, append, remove } = useFieldArray({
+ control,
+ name: "members"
+ });
- setShowMemberDialog(false);
- }}
- onClose={() => setShowMemberDialog(false)}
- />
- ) : (
- <>>
- )}
- {policyDialogOpen ? (
- setpolicyDialogOpen(false)}
- onAttachPolicy={onAttachPolicy}
- members={members.reduce((a, o) => Number(a)+Number(o.weight), 0)}
- />
- ) : (
- <>>
- )}
+ return (
+
Create Group
@@ -130,6 +80,8 @@ export default function CreateGroupPage() {
sx={{
"& .MuiTextField-root": { mt: 1.5, mb: 1.5 },
p: 2,
+ width: '70%',
+ margin: '0 auto'
}}
>
- >
- );
-}
-
-function MemberItem(props) {
- const { address, weight, metadata } = props.member;
- const { onRemove, onEdit } = props;
-
- return (
-
-
-
-
-
- onRemove(address)}
- >
-
-
- onEdit(address, weight, metadata)}
- >
-
-
-
);
}
diff --git a/src/pages/group/CreateGroupForm.jsx b/src/pages/group/CreateGroupForm.jsx
new file mode 100644
index 000000000..ca68a0e69
--- /dev/null
+++ b/src/pages/group/CreateGroupForm.jsx
@@ -0,0 +1,94 @@
+import {
+ Box, TextField, IconButton,Grid,
+} from '@mui/material';
+import DeleteOutline from "@mui/icons-material/DeleteOutline";
+import { Controller } from "react-hook-form";
+import React from 'react';
+import AddIcon from '@mui/icons-material/Add';
+
+export function CreateGroupForm({
+ fields,
+ control,
+ append,
+ remove
+}) {
+ return (
+
+
+ {
+ fields.map((item, index) => {
+ return
+
+ (
+
+ )}
+ />
+
+
+ (
+
+ )}
+ />
+
+
+ (
+
+ )}
+ />
+
+
+ remove(index)} color='error'>
+
+
+ {
+ append({ address: '', metadata: '', weight: 0 })
+ }} color='primary'>
+
+
+
+
+ })
+ }
+
+
+ )
+}
+
+export default CreateGroupForm
\ No newline at end of file
diff --git a/src/pages/group/CreateGroupPolicy.js b/src/pages/group/CreateGroupPolicy.js
new file mode 100644
index 000000000..55888eeda
--- /dev/null
+++ b/src/pages/group/CreateGroupPolicy.js
@@ -0,0 +1,214 @@
+import {
+ Box, Button, TextField, Select, MenuItem, FormControlLabel, Switch, Typography, Grid, FormControl, InputLabel, InputAdornment, Paper,
+} from '@mui/material';
+import React, { useState } from 'react';
+import CloseIcon from '@mui/icons-material/Close';
+import { Controller } from "react-hook-form";
+
+function CreateGroupPolicy({
+ control,
+ register,
+ watch,
+ fields,
+ errors,
+ reset,
+ handleCancelPolicy }) {
+
+ return (
+
+
+
+ Add Decision Policy
+
+
+
+
+
+
+ (
+
+ )}
+ />
+
+
+ (
+
+
+ Decision Policy
+
+
+
+ )}
+ />
+
+
+ {
+ watch('policyMetadata.decisionPolicy') === 'percentage' ?
+ (
+
+
+
+ )}
+ />
+ :
+ (
+
+
+
+ )} />
+ }
+
+
+ (
+
+ Sec,
+ }}
+ error={errors?.policyMetadata?.votingPeriod}
+ helperText={errors?.policyMetadata?.votingPeriod?.message}
+ />
+
+ )} />
+
+
+
+ (
+
+ Sec,
+ }}
+ error={errors?.policyMetadata?.minExecPeriod}
+ helperText={errors?.policyMetadata?.minExecPeriod?.message}
+ />
+
+ )} />
+
+
+
+
+
+ }
+ label="Group policy as admin"
+ labelPlacement="start"
+ />
+
+ if set to true, the group policy account address will be used as
+ group and policy admin
+
+
+
+
+
+
+
+ )
+}
+
+export default CreateGroupPolicy
\ No newline at end of file
diff --git a/src/pages/group/CreateProposal.jsx b/src/pages/group/CreateProposal.jsx
new file mode 100644
index 000000000..67589dce1
--- /dev/null
+++ b/src/pages/group/CreateProposal.jsx
@@ -0,0 +1,68 @@
+import { Button, Paper, TextField, Typography } from '@mui/material'
+import { Box } from '@mui/system';
+import React from 'react'
+import { useDispatch, useSelector } from 'react-redux';
+import { useParams } from 'react-router-dom';
+import { useForm, Controller, FormProvider } from "react-hook-form";
+
+import PagePolicyTx from './PagePolicyTx';
+import { txCreateGroupProposal } from '../../features/group/groupSlice';
+import TxTypeComponent from '../../components/group/TxTypeComponent';
+import AddManualTx from './AddManualTx';
+import AddFileTx from './AddFileTx';
+
+function CreateProposal() {
+ const [type, setType] = React.useState(null);
+ const { policyAddress } = useParams();
+ const dispatch = useDispatch();
+
+ const wallet = useSelector(state => state.wallet);
+ const chainInfo = wallet?.chainInfo;
+
+ const onSubmit = (data) => {
+ data.groupPolicyAddress = policyAddress;
+ data.messages = data.msgs;
+ data.proposers = [wallet?.address];
+ data.admin = wallet?.address;
+ data.chainId = wallet?.chainInfo?.config?.chainId
+ data.rpc = wallet?.chainInfo?.config?.rpc;
+ data.denom = wallet?.chainInfo?.config?.currencies?.[0]?.coinMinimalDenom || ''
+ data.feeAmount = wallet?.chainInfo?.config?.gasPriceStep?.average || 0;
+ console.log('fee amount', data)
+ // dispatch(txCreateGroupProposal(data));
+ }
+
+ return (
+ <>
+
+ Create Policy Proposal
+
+
+
+ Proposer
+
+ {wallet?.address}
+
+
+
+ {
+ setType(type)
+ }} />
+
+
+
+ {type === 'single' && setType(null)}
+ /> || null}
+ {type === 'multiple' && || null}
+
+
+ >
+
+ )
+}
+
+export default CreateProposal
\ No newline at end of file
diff --git a/src/pages/group/Group.jsx b/src/pages/group/Group.jsx
new file mode 100644
index 000000000..e90034afd
--- /dev/null
+++ b/src/pages/group/Group.jsx
@@ -0,0 +1,421 @@
+import React, { useEffect, useState } from 'react';
+import { experimentalStyled as styled } from '@mui/material/styles';
+import Box from '@mui/material/Box';
+import Paper from '@mui/material/Paper';
+import Grid from '@mui/material/Grid';
+import { Alert, Button, Card, CircularProgress, FormControl, IconButton, Table, TableCell, TableRow, TextField, Typography } from '@mui/material';
+import { useNavigate, useParams } from 'react-router-dom';
+import RowItem from '../../components/group/RowItem';
+import { useDispatch, useSelector } from 'react-redux';
+import AccountCircleIcon from '@mui/icons-material/AccountCircle';
+import { getGroupById, getGroupMembers, getGroupMembersById, getGroupPoliciesById, resetUpdateGroupMember, txAddGroupPolicy, txLeaveGroupMember, txUpdateGroupAdmin, txUpdateGroupMember, txUpdateGroupMetadata, txUpdateGroupPolicy } from '../../features/group/groupSlice';
+
+import MembersTable from '../../components/group/MembersTable';
+import PolicyCard from '../../components/group/PolicyCard';
+import CreateGroupForm from './CreateGroupForm';
+import UpdateGroupMemberForm from '../../components/group/UpdateGroupMemberForm';
+import CreateGroupPolicy from './CreateGroupPolicy';
+import EditIcon from '@mui/icons-material/Edit';
+import { UpdateGroupAdmin } from '../../txns/group/group';
+import PolicyForm from '../../components/group/PolicyForm';
+import { groupStyles } from './group-css';
+import CancelIcon from '@mui/icons-material/Cancel';
+import CheckIcon from '@mui/icons-material/Check';
+import GroupTab, { TabPanel } from '../../components/group/GroupTab';
+import GroupInfo from './GroupInfo';
+import ActiveProposals from './ActiveProposals';
+import { useForm } from 'react-hook-form';
+
+
+const Item = styled(Paper)(({ theme }) => ({
+ backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
+ ...theme.typography.body2,
+ boxShadow: 'none',
+ border: '1px solid #908d8d',
+ padding: theme.spacing(2),
+ textAlign: 'center',
+ color: theme.palette.text.secondary,
+}));
+
+const GroupPolicies = ({ id, wallet }) => {
+ const navigate = useNavigate();
+ const dispatch = useDispatch();
+ const [showForm, setShowForm] = useState(false);
+ const [limit, setLimit] = useState(5);
+ const [total, setTotal] = useState(0);
+ const [pageNumber, setPageNumber] = useState(0);
+ const groupDetails = useSelector(state => state.group.groupInfo);
+ const { data: groupInformation, status: groupInfoStatus } = groupDetails;
+
+ const addPolicyRes = useSelector(state => state.group.addGroupPolicyRes);
+
+ const groupMembers = useSelector(state => state.group.groupMembers);
+
+ useEffect(() => {
+ if (addPolicyRes?.status === 'idle') {
+ getPolicies(limit, '')
+ setShowForm(false);
+ }
+ }, [addPolicyRes?.status])
+
+ const getPolicies = (limit, key = '') => {
+ dispatch(getGroupPoliciesById({
+ baseURL: wallet?.chainInfo?.config?.rest,
+ id: id,
+ pagination: { limit: limit, key },
+ }))
+ }
+ useEffect(() => {
+ getPolicies(limit, '')
+ }, [])
+
+ const getGroupmembers = () => {
+ dispatch(getGroupMembersById({
+ baseURL: wallet?.chainInfo?.config?.rest,
+ id: id,
+ pagination: { limit: limit, key: '' },
+ }))
+ }
+
+ useEffect(() => {
+ getGroupmembers();
+ }, [])
+
+ const handlePagination = (number, limit, key) => {
+ setLimit(limit);
+ setPageNumber(number);
+ getPolicies(limit, key)
+ }
+
+ let group_policies = [];
+ const groupInfo = useSelector(state => state.group.groupPolicies);
+ const { data, status } = groupInfo;
+
+ if (data) {
+ group_policies = data?.group_policies;
+ }
+
+ useEffect(() => {
+ if (Number(data?.pagination?.total))
+ setTotal(Number(data?.pagination?.total || 0))
+ }, [data])
+
+ const handlePolicy = (policyObj) => {
+ const chainInfo = wallet?.chainInfo;
+
+ dispatch(txAddGroupPolicy({
+ admin: groupInformation?.info?.admin,
+ groupId: id,
+ policyMetadata: policyObj?.policyMetadata,
+ denom: chainInfo?.config?.currencies?.[0]?.minimalCoinDenom,
+ chainId: chainInfo.config.chainId,
+ rpc: chainInfo.config.rpc,
+ feeAmount: chainInfo.config.gasPriceStep.average,
+ }))
+ }
+
+ const { register, control,
+ handleSubmit,
+ watch,
+ formState: { errors },
+ reset, trigger, setError } = useForm({});
+
+ return (
+
+
+
+
+ {
+ showForm &&
+
+ {
+ groupMembers?.status === 'pending' ?
+
+ Wait fetching group members information ...
+ : null
+ }
+
+ {
+ groupMembers?.status === 'idle' &&
+ Number(groupMembers?.data?.pagination?.total) > 0 ?
+ :
+
+ No members found on this group.
+
+ }
+
+
+
+ }
+
+
+
+ {
+ status === 'pending' ?
+ : null
+ }
+ {
+ (status !== 'pending' &&
+ !showForm &&
+ !group_policies?.length) && (
+
+
+ No policies found.
+
+
+ )
+ }
+
+ {status !== 'pending' && group_policies.map((p, index) => (
+
+
+
+ ))}
+
+
+ )
+}
+
+const GroupMembers = ({ id, wallet }) => {
+ const dispatch = useDispatch();
+ const [limit, setLimit] = useState(5);
+ const [total, setTotal] = useState(0);
+ const [pageNumber, setPageNumber] = useState(0);
+
+ const createGroupRes = useSelector(state => state.group?.updateGroupRes)
+
+ const getGroupmembers = () => {
+ dispatch(getGroupMembersById({
+ baseURL: wallet?.chainInfo?.config?.rest,
+ id: id,
+ pagination: { limit: limit, key: '' },
+ }))
+ }
+
+ useEffect(() => {
+ dispatch(resetUpdateGroupMember())
+ getGroupmembers();
+ }, [createGroupRes?.status])
+
+ useEffect(() => {
+ getGroupmembers();
+ }, [])
+
+ const handleMembersPagination = (number, limit, key) => {
+ setLimit(limit);
+ setPageNumber(number);
+ dispatch(getGroupMembersById({
+ baseURL: wallet?.chainInfo?.config?.rest,
+ id: id,
+ pagination: { limit: limit, key: key },
+ }))
+ }
+
+ const groupInfo = useSelector(state => state.group.groupMembers);
+ const { data, status } = groupInfo;
+
+ useEffect(() => {
+ if (Number(data?.pagination?.total))
+ setTotal(Number(data?.pagination?.total || 0))
+ }, [data])
+
+ const handleDeleteMember = (deleteMemberObj) => {
+ const chainInfo = wallet?.chainInfo;
+
+ const dataObj = {
+ admin: wallet?.address,
+ groupId: id,
+ members: [deleteMemberObj],
+ denom: chainInfo?.config?.currencies?.[0]?.minimalCoinDenom,
+ chainId: chainInfo.config.chainId,
+ rpc: chainInfo.config.rpc,
+ feeAmount: chainInfo.config.gasPriceStep.average,
+ }
+
+ dispatch(txUpdateGroupMember(dataObj))
+ }
+
+ return (
+
+ {
+ status === 'pending' ?
+ : null
+ }
+ {
+ status !== 'pending' ?
+
+
+ : null}
+
+ )
+}
+
+
+
+const UpdateGroupMember = ({ id, wallet }) => {
+ const [showForm, setShowForm] = useState(false);
+ var [members2, setMembers] = useState([]);
+ const dispatch = useDispatch();
+ const updateRes = useSelector(state => state.group.updateGroupRes)
+
+ useEffect(() => {
+ if (updateRes?.status === 'idle') {
+ setShowForm(false);
+ }
+ }, [updateRes])
+
+ useEffect(() => {
+ return () => {
+ dispatch(resetUpdateGroupMember())
+ }
+ }, [])
+
+ const groupMembers = useSelector(state => state.group.groupMembers)
+ const members1 = [{ address: '', weight: '', metadata: '' }];
+ const { data: members, status: memberStatus } = groupMembers;
+ console.log({ members })
+
+ const getMembers = () => {
+ let m = members && members?.members?.map(m => {
+ let { added_at, ...newObj } = m?.member;
+ return newObj
+ })
+ if (m?.length)
+ setMembers([...m])
+ else
+ setMembers([...members1]);
+ }
+
+ // useEffect(() => {
+ // getMembers()
+ // }, [])
+
+ const getGroupmembers = () => {
+ dispatch(getGroupMembersById({
+ baseURL: wallet?.chainInfo?.config?.rest,
+ id: id,
+ pagination: { limit: 200, key: '' },
+ }))
+ }
+
+ useEffect(() => {
+ getMembers();
+ }, [memberStatus])
+
+ useEffect(() => {
+ getGroupmembers();
+ }, [])
+
+ const handleUpdate = (allMembers) => {
+ const chainInfo = wallet?.chainInfo;
+
+ console.log({ allMembers })
+ const dataObj = {
+ admin: wallet?.address,
+ groupId: id,
+ members: allMembers?.members,
+ denom: chainInfo?.config?.currencies?.[0]?.minimalCoinDenom,
+ chainId: chainInfo.config.chainId,
+ rpc: chainInfo.config.rpc,
+ feeAmount: chainInfo.config.gasPriceStep.average,
+ }
+
+ dispatch(txUpdateGroupMember(dataObj))
+ }
+
+ return (
+
+
+
+
+
+ {
+ showForm ?
+
+
+ {
+ memberStatus?.status === 'pending' ? :
+ {
+ setShowForm(false);
+ }}
+ handleUpdate={handleUpdate}
+ members={members2}
+ />
+ }
+
+
+ : null
+ }
+
+
+
+ )
+}
+
+function Group() {
+ const params = useParams();
+ const [tabIndex, setTabIndex] = useState(0);
+
+ const wallet = useSelector(state => state.wallet)
+ const arr = ['Members', 'Decision Policies', 'Active Proposals']
+
+ return (
+
+
+
+
+ setTabIndex(i)} />
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default Group
\ No newline at end of file
diff --git a/src/pages/group/GroupInfo.jsx b/src/pages/group/GroupInfo.jsx
new file mode 100644
index 000000000..7736d03de
--- /dev/null
+++ b/src/pages/group/GroupInfo.jsx
@@ -0,0 +1,377 @@
+import {
+ Box, Button, CircularProgress, Grid, Paper,
+ TextField,
+ Typography, IconButton, Tooltip
+} from "@mui/material";
+import React, { useEffect, useState } from "react";
+import { useDispatch, useSelector } from "react-redux";
+import GroupsIcon from '@mui/icons-material/Groups';
+import CancelIcon from '@mui/icons-material/Cancel';
+import CheckIcon from '@mui/icons-material/Check';
+import EditIcon from '@mui/icons-material/Edit';
+
+import { getGroupById, txLeaveGroupMember, txUpdateGroupAdmin, txUpdateGroupMetadata } from "../../features/group/groupSlice";
+
+const GroupInfo = ({ id, wallet }) => {
+ const dispatch = useDispatch();
+ const [showAdminInput, setShowAdminInput] = useState(false);
+ const [admin, setAdmin] = useState('');
+ const [metadata, setMetadata] = useState('');
+ const [showMetadataInput, setShowMetadataInput] = useState(false);
+ const [groupInformation, setGroupInformation] = useState({});
+
+ const groupMembers = useSelector(state => state.group.groupMembers)
+ const { data: members, status: memberStatus } = groupMembers;
+
+ const groupInfo = useSelector(state => state.group.groupInfo);
+ const { data, status } = groupInfo;
+
+ const leaveGroupRes = useSelector(state => state.group.leaveGroupRes);
+ const updateAdminRes = useSelector(state => state.group.updateGroupAdminRes);
+ const updateMetadataRes = useSelector(state => state.group.updateGroupMetadataRes);
+
+
+ const isExistInGroup = () => {
+ const existMember = members?.members?.filter(m => m?.member?.address === wallet?.address)
+
+ if (existMember && existMember?.length) return true
+
+ if (groupInformation?.admin === wallet?.address) return true
+
+ return false;
+ }
+
+ const getGroup = () => {
+ dispatch(getGroupById({
+ baseURL: wallet?.chainInfo?.config?.rest, id: id
+ }))
+ }
+
+ useEffect(() => {
+ getGroup();
+ }, [])
+
+ useEffect(() => {
+ if (groupInfo?.status === 'idle') {
+ setGroupInformation(groupInfo?.data?.info)
+ }
+
+ }, [groupInfo?.status])
+
+ useEffect(() => {
+ if (updateAdminRes?.status === 'idle') {
+ setShowAdminInput(false);
+ getGroup();
+ }
+
+ }, [updateAdminRes?.status])
+
+ useEffect(() => {
+ if (updateMetadataRes?.status === 'idle') {
+ setShowMetadataInput(false);
+ getGroup();
+ }
+
+ }, [updateMetadataRes?.status])
+
+
+ const handleLeaveGroup = () => {
+ const chainInfo = wallet?.chainInfo;
+ dispatch(txLeaveGroupMember({
+ admin: wallet?.address,
+ groupId: id,
+ denom: chainInfo?.config?.currencies?.[0]?.minimalCoinDenom,
+ chainId: chainInfo.config.chainId,
+ rpc: chainInfo.config.rpc,
+ feeAmount: chainInfo.config.gasPriceStep.average,
+ }));
+ }
+
+ const UpdateAdmin = () => {
+ const chainInfo = wallet?.chainInfo;
+ dispatch(txUpdateGroupAdmin({
+ signer: wallet?.address,
+ admin: data?.info?.admin,
+ groupId: id,
+ newAdmin: admin,
+ denom: chainInfo?.config?.currencies?.[0]?.minimalCoinDenom,
+ chainId: chainInfo.config.chainId,
+ rpc: chainInfo.config.rpc,
+ feeAmount: chainInfo.config.gasPriceStep.average,
+ }));
+ }
+
+ const UpdateMetadata = () => {
+ const chainInfo = wallet?.chainInfo;
+ dispatch(txUpdateGroupMetadata({
+ signer: wallet?.address,
+ admin: data?.info?.admin,
+ groupId: id,
+ metadata,
+ denom: chainInfo?.config?.currencies?.[0]?.minimalCoinDenom,
+ chainId: chainInfo.config.chainId,
+ rpc: chainInfo.config.rpc,
+ feeAmount: chainInfo.config.gasPriceStep.average,
+ }));
+ }
+
+ return (
+
+
+
+
+
+
+ # {data?.info?.id}
+
+ {
+ memberStatus?.status === 'pending' ?
+ : null
+ }
+ {
+ memberStatus?.status !== 'pending' && isExistInGroup() ?
+ : null
+ }
+
+
+
+ {
+ status === 'pending' ?
+ : null
+ }
+ {
+ status !== 'pending' ?
+
+
+
+ Admin
+
+
+ {
+ showAdminInput ?
+ {
+ setAdmin(e.target.value)
+ }}
+ />
+ :
+ <>
+
+ {data?.info?.admin || '-'}
+
+
+ Note: Only admin can be update admin address.
+
+ >
+ }
+
+
+
+ {
+ showAdminInput ?
+ updateAdminRes?.status === 'pending' ?
+ :
+
+
+
+ UpdateAdmin()
+ }
+ color="primary">
+
+
+
+
+ setShowAdminInput(false)
+ }
+ color="error">
+
+
+
+
+ :
+ isExistInGroup() ?
+
+ {
+ setAdmin(data?.info?.admin)
+ setShowAdminInput(!showAdminInput)
+ }
+ } />
+
+ : null
+ }
+
+
+
+
+
+
+ Metdata
+
+
+ {
+ showMetadataInput ?
+ {
+ setMetadata(e.target.value)
+ }}
+ />
+ :
+ <>
+
+ {data?.info?.metadata || '-'}
+
+
+ Note: Only admin can be update metadata.
+
+ >
+ }
+
+
+
+ {
+ showMetadataInput ?
+ updateMetadataRes?.status === 'pending' ?
+ :
+
+
+ UpdateMetadata()
+ }
+ color="primary">
+
+
+
+
+
+ setShowMetadataInput(false)
+ }
+ color="error">
+
+
+
+
+ :
+ isExistInGroup() ?
+
+ {
+ setMetadata(data?.info?.metadata)
+ setShowMetadataInput(!showMetadataInput)
+ }
+ } />
+
+ : null
+
+
+ }
+
+
+
+
+
+
+ Version
+
+
+ {data?.info?.version || '-'}
+
+
+
+
+ Weight
+
+
+ {data?.info?.total_weight || '-'}
+
+
+
+ : null
+ }
+
+
+
+
+
+ )
+}
+
+export default GroupInfo
\ No newline at end of file
diff --git a/src/pages/group/GroupMembersInfo.jsx b/src/pages/group/GroupMembersInfo.jsx
new file mode 100644
index 000000000..d678aa699
--- /dev/null
+++ b/src/pages/group/GroupMembersInfo.jsx
@@ -0,0 +1,9 @@
+import React from 'react'
+
+function GroupMembersInfo() {
+ return (
+ GroupMembersInfo
+ )
+}
+
+export default GroupMembersInfo
\ No newline at end of file
diff --git a/src/pages/group/MemberGroupList.jsx b/src/pages/group/MemberGroupList.jsx
new file mode 100644
index 000000000..da81ba1e0
--- /dev/null
+++ b/src/pages/group/MemberGroupList.jsx
@@ -0,0 +1,52 @@
+import React, {useState, useEffect} from 'react'
+import { useDispatch, useSelector } from 'react-redux';
+import GroupList from '../../components/group/GroupList';
+import { getGroupsByMember } from '../../features/group/groupSlice';
+
+function MemberGroupList() {
+ const [memberTotal, setMemberTotal] = useState(0);
+ const limit = 9;
+ const dispatch = useDispatch();
+
+ const address = useSelector((state) => state.wallet.address);
+ const chainInfo = useSelector((state) => state.wallet.chainInfo);
+ const groups = useSelector((state) => state.group.groups);
+
+ const fetchGroupsByMember = (offset = 0, limit = 9) => {
+ dispatch(
+ getGroupsByMember({
+ baseURL: chainInfo.config.rest,
+ address: address,
+ pagination: {
+ offset,
+ limit
+ }
+ })
+ );
+ }
+
+ const handlePagination = (page) => {
+ fetchGroupsByMember(page, limit)
+ }
+
+ useEffect(() => {
+ fetchGroupsByMember(0, limit)
+ }, [address]);
+
+ useEffect(() => {
+ if (Number(groups?.member?.pagination?.total))
+ setMemberTotal(Number(groups?.member?.pagination?.total))
+ }, [groups?.member?.pagination?.total])
+
+ return (
+
+ )
+}
+
+export default MemberGroupList
\ No newline at end of file
diff --git a/src/pages/group/PagePolicyTx.jsx b/src/pages/group/PagePolicyTx.jsx
new file mode 100644
index 000000000..49f452108
--- /dev/null
+++ b/src/pages/group/PagePolicyTx.jsx
@@ -0,0 +1,957 @@
+import react, { useState, useEffect } from "react";
+import { Button, Grid, IconButton, Paper, Typography } from "@mui/material";
+import { useNavigate, useParams } from "react-router-dom";
+import { Box, FormControl, InputLabel, MenuItem, Select } from "@mui/material";
+import { useDispatch, useSelector } from "react-redux";
+import Send from "../../components/group/bulk/Send";
+import UnDelegateForm from "../../components/group/bulk/UnDelegateForm";
+import RedelegateForm from "../../components/group/bulk/RedelegateForm";
+import { shortenAddress } from "../../utils/util";
+import { Divider } from "@mui/material";
+import DeleteOutline from "@mui/icons-material/DeleteOutline";
+import {
+ getAllValidators,
+ getDelegations,
+} from "../../features/staking/stakeSlice";
+import Delegate from "../../components/group/bulk/Delegate";
+import FileUploadOutlinedIcon from "@mui/icons-material/FileUploadOutlined";
+import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined";
+import {
+ DELEGATE_TYPE_URL,
+ parseDelegateMsgsFromContent,
+ parseReDelegateMsgsFromContent,
+ parseSendMsgsFromContent,
+ parseUnDelegateMsgsFromContent,
+ REDELEGATE_TYPE_URL,
+ SEND_TYPE_URL,
+ UNDELEGATE_TYPE_URL,
+} from "./utils";
+import { parseBalance } from "../../utils/denom";
+import { Pagination } from "@mui/material";
+import { TextField } from "@mui/material";
+import FeeComponent from "./../../components/multisig/FeeComponent";
+// import {
+// createTxn,
+// resetCreateTxnState,
+// } from "../../features/multisig/multisigSlice";
+import { fee } from "../../txns/execute";
+import { resetError, setError } from "../../features/common/commonSlice";
+import TxBasicFields from "../../components/group/TxBasicFields";
+import { useForm, Controller, FormProvider, useFormContext } from "react-hook-form";
+
+
+// TODO: serve urls from env
+
+const MULTISIG_SEND_TEMPLATE = "https://resolute.witval.com/_static/send.csv";
+const MULTISIG_DELEGATE_TEMPLATE =
+ "https://resolute.witval.com/_static/delegate.csv";
+const MULTISIG_UNDELEGATE_TEMPLATE =
+ "https://resolute.witval.com/_static/undelegate.csv";
+const MULTISIG_REDELEGATE_TEMPLATE =
+ "https://resolute.witval.com/_static/redelegate.csv";
+
+const PER_PAGE = 6;
+
+const TYPE_SEND = "SEND";
+const TYPE_DELEGATE = "DELEGATE";
+const TYPE_UNDELEGATE = "UNDELEGATE";
+const TYPE_REDELEGATE = "REDELEGATE";
+
+const SelectTransactionType = (props) => {
+ return (
+
+
+
+
+ );
+};
+
+const FileUpload = (props) => {
+ const [txType, setTxType] = useState(TYPE_SEND);
+ return (
+
+
+
+ Select Transaction
+
+
+
+
+ }
+ sx={{
+ textTransform: "none",
+ }}
+ onClick={() => {
+ switch (txType) {
+ case TYPE_SEND:
+ window.open(
+ MULTISIG_SEND_TEMPLATE,
+ "_blank",
+ "noopener,noreferrer"
+ );
+ break;
+ case TYPE_DELEGATE:
+ window.open(
+ MULTISIG_DELEGATE_TEMPLATE,
+ "_blank",
+ "noopener,noreferrer"
+ );
+ break;
+ case TYPE_UNDELEGATE:
+ window.open(
+ MULTISIG_UNDELEGATE_TEMPLATE,
+ "_blank",
+ "noopener,noreferrer"
+ );
+ break;
+ case TYPE_REDELEGATE:
+ window.open(
+ MULTISIG_REDELEGATE_TEMPLATE,
+ "_blank",
+ "noopener,noreferrer"
+ );
+ break;
+ default:
+ alert("unknown message type");
+ }
+ }}
+ >
+ Download template
+
+ }
+ sx={{
+ mb: 2,
+ mt: 2,
+ ml: 1,
+ textTransform: "none",
+ }}
+ onClick={() => {
+ document.getElementById("multisig_file").click();
+ }}
+ >
+ {
+ const file = e.target.files[0];
+ if (!file) {
+ return;
+ }
+
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ const contents = e.target.result;
+ props.onFileContents(contents, txType);
+ };
+ reader.onerror = (e) => {
+ alert(e);
+ };
+ reader.readAsText(file);
+ e.target.value = null;
+ }}
+ />
+ Upload csv file
+
+
+
+ );
+};
+
+export default function PagePolicyTx({ control, setValue }) {
+ const { policyAddress: address } = useParams();
+
+ const [txType, setTxType] = useState("");
+
+ const wallet = useSelector((state) => state.wallet);
+ const { chainInfo, connected } = wallet;
+
+ const validators = useSelector((state) => state.staking.validators);
+ const methods = useForm({
+ defaultValues: {
+ gas: 20000
+ }
+ });
+ const onSubmit = data => console.log(data);
+
+ const dispatch = useDispatch();
+ useEffect(() => {
+ if (connected) {
+ dispatch(
+ getAllValidators({
+ baseURL: chainInfo.config.rest,
+ status: null,
+ })
+ );
+
+ dispatch(
+ getDelegations({
+ baseURL: chainInfo.config.rest,
+ address: address,
+ })
+ );
+ }
+ }, [connected]);
+
+ const handleTypeChange = (event) => {
+ setTxType(event.target.value);
+ };
+
+ const [messages, setMessages] = useState([]);
+
+ const renderMessage = (msg, index, currency, onDelete) => {
+ switch (msg.typeUrl) {
+ case SEND_TYPE_URL: {
+ return RenderSendMessage(msg, index, currency, onDelete);
+ }
+ case DELEGATE_TYPE_URL:
+ return RenderDelegateMessage(msg, index, currency, onDelete);
+ case UNDELEGATE_TYPE_URL:
+ return RenderUnDelegateMessage(msg, index, currency, onDelete);
+ case REDELEGATE_TYPE_URL:
+ return RenderReDelegateMessage(msg, index, currency, onDelete);
+ default:
+ return "";
+ }
+ };
+
+ const onDeleteMsg = (index) => {
+ const arr = messages.filter((_, i) => i !== index);
+ setMessages(arr);
+ setValue('msgs', arr)
+ };
+
+ const onFileContents = (content, type) => {
+ switch (type) {
+ case TYPE_SEND: {
+ const [parsedTxns, error] = parseSendMsgsFromContent(address, content);
+ if (error) {
+ dispatch(
+ setError({
+ type: "error",
+ message: error,
+ })
+ );
+ } else {
+ setMessages(parsedTxns);
+ setValue('msgs', parsedTxns)
+ }
+ break;
+ }
+ case TYPE_DELEGATE: {
+ const [parsedTxns, error] = parseDelegateMsgsFromContent(
+ address,
+ content
+ );
+ if (error) {
+ dispatch(
+ setError({
+ type: "error",
+ message: error,
+ })
+ );
+ } else {
+ setMessages(parsedTxns);
+ setValue('msgs', parsedTxns)
+ }
+ break;
+ }
+ case TYPE_REDELEGATE: {
+ const [parsedTxns, error] = parseReDelegateMsgsFromContent(
+ address,
+ content
+ );
+ if (error) {
+ dispatch(
+ setError({
+ type: "error",
+ message: error,
+ })
+ );
+ } else {
+ setMessages(parsedTxns);
+ setValue('msgs', parsedTxns)
+ }
+ break;
+ }
+ case TYPE_UNDELEGATE: {
+ const [parsedTxns, error] = parseUnDelegateMsgsFromContent(
+ address,
+ content
+ );
+ if (error) {
+ dispatch(
+ setError({
+ type: "error",
+ message: error,
+ })
+ );
+ } else {
+ setMessages(parsedTxns);
+ setValue('msgs', parsedTxns);
+ }
+ break;
+ }
+ default:
+ setMessages([]);
+ setValue('msgs', [])
+ }
+ };
+
+ const [mode, setMode] = useState("");
+
+ const [slicedMsgs, setSlicedMsgs] = useState([]);
+ const [currentPage, setCurrentPage] = useState(1);
+ // var createRes = useSelector((state) => state.multisig.createTxnRes);
+
+ let navigate = useNavigate();
+ // useEffect(() => {
+ // if (createRes?.status === "rejected") {
+ // dispatch(
+ // setError({
+ // type: "error",
+ // message: createRes?.error,
+ // })
+ // );
+ // } else if (createRes?.status === "idle") {
+ // dispatch(
+ // setError({
+ // type: "success",
+ // message: "Transaction created",
+ // })
+ // );
+
+ // setTimeout(() => {
+ // navigate(`/multisig/${address}/txs`);
+ // }, 200);
+ // }
+ // }, [createRes]);
+
+ useEffect(() => {
+ return () => {
+ dispatch(resetError());
+ // dispatch(resetCreateTxnState());
+ };
+ }, []);
+
+ useEffect(() => {
+ if (messages.length < PER_PAGE) {
+ setSlicedMsgs(messages);
+ } else {
+ setCurrentPage(1);
+ setSlicedMsgs(messages?.slice(0, 1 * PER_PAGE));
+ }
+ }, [messages]);
+
+ // const { handleSubmit, control, setValue } = useForm({
+ // defaultValues: {
+ // msgs: [],
+ // gas: 200000,
+ // memo: "",
+ // fees:
+ // chainInfo?.config?.gasPriceStep?.average *
+ // 10 ** chainInfo?.config?.currencies[0].coinDecimals,
+ // },
+ // });
+
+ // const onSubmit = (data) => {
+ // const feeObj = fee(
+ // chainInfo?.config.currencies[0].coinMinimalDenom,
+ // data.fees,
+ // data.gas
+ // );
+ // dispatch(
+ // createTxn({
+ // address: address,
+ // chainId: chainInfo?.config?.chainId,
+ // msgs: messages,
+ // fee: feeObj,
+ // memo: data.memo,
+ // gas: data.gas,
+ // })
+ // );
+ // };
+
+ return (
+ <>
+
+ {!connected ? (
+
+ Wallet is not connected
+
+ ) : (
+
+
+
+ {mode === "" ? (
+ setMode(mode)} />
+ ) : mode === "manual" ? (
+ <>
+
+
+ Select Transaction
+
+
+
+ >
+ ) : (
+ setMode('')
+ }
+ onFileContents={(content, type) =>
+ onFileContents(content, type)
+ }
+ />
+ )}
+
+ {
+ mode === 'manual' ?
+
+
+ : null
+ }
+ {/* */}
+
+
+
+
+
+
+ Messages
+
+
+ {messages.length === 0 ? (
+
+ No Messages
+
+ ) : null}
+ {slicedMsgs.map((msg, index) => {
+ return (
+
+ {renderMessage(
+ msg,
+ index + PER_PAGE * (currentPage - 1),
+ chainInfo.config.currencies[0],
+ onDeleteMsg
+ )}
+
+
+ );
+ })}
+
+ {messages.length > 0 ? (
+ {
+ setCurrentPage(v);
+ setSlicedMsgs(
+ messages?.slice((v - 1) * PER_PAGE, v * PER_PAGE)
+ );
+ }}
+ />
+ ) : null}
+
+ {messages.length > 0 ? (
+
+ //
+ //
+ // (
+ //
+ // )}
+ // />
+ //
+ //
+ // (
+ //
+ // )}
+ // />
+ //
+ //
+ // {
+ // setValue(
+ // "fees",
+ // Number(v) *
+ // 10 **
+ // chainInfo?.config?.currencies[0].coinDecimals
+ // );
+ // }}
+ // chainInfo={chainInfo}
+ // />
+ //
+
+ // {/*
+ //
+ // */}
+ //
+ ) : null}
+
+
+
+
+ )}
+ >
+ );
+}
+
+export const RenderSendMessage = (message, index, currency, onDelete) => {
+ return (
+
+
+
+ #{index + 1}
+
+
+ Send
+
+
+ {parseBalance(
+ message.value.amount,
+ currency.coinDecimals,
+ currency.coinMinimalDenom
+ )}
+ {currency.coinDenom}
+
+
+ to
+
+
+ {shortenAddress(message.value.toAddress, 21)}
+
+
+ {onDelete ? (
+ onDelete(index)}
+ >
+
+
+ ) : null}
+
+ );
+};
+
+export const RenderDelegateMessage = (message, index, currency, onDelete) => {
+ return (
+
+
+
+ #{index + 1}
+
+
+ Delegate
+
+
+ {parseBalance(
+ message.value.amount,
+ currency.coinDecimals,
+ currency.coinMinimalDenom
+ )}
+ {currency.coinDenom}
+
+
+ to
+
+
+ {shortenAddress(message.value.validatorAddress, 21)}
+
+
+ {onDelete ? (
+ onDelete(index)}
+ >
+
+
+ ) : null}
+
+ );
+};
+
+export const RenderUnDelegateMessage = (message, index, currency, onDelete) => {
+ return (
+
+
+
+ #{index + 1}
+
+
+ Undelegate
+
+
+ {parseBalance(
+ [message.value.amount],
+ currency.coinDecimals,
+ currency.coinMinimalDenom
+ )}
+ {currency.coinDenom}
+
+
+ from
+
+
+ {shortenAddress(message.value?.validatorAddress || "", 21)}
+
+
+ {onDelete ? (
+ onDelete(index)}
+ >
+
+
+ ) : null}
+
+ );
+};
+
+export const RenderReDelegateMessage = (message, index, currency, onDelete) => {
+ return (
+
+
+
+ #{index + 1}
+
+
+ Redelegate
+
+
+ {parseBalance(
+ message.value.amount,
+ currency.coinDecimals,
+ currency.coinMinimalDenom
+ )}
+ {currency.coinDenom}
+
+
+ from
+
+
+ {shortenAddress(message.value.validatorSrcAddress, 21)}
+
+
+ to
+
+
+ {shortenAddress(message.value.validatorDstAddress, 21)}
+
+
+ {onDelete ? (
+ onDelete(index)}
+ >
+
+
+ ) : null}
+
+ );
+};
diff --git a/src/pages/group/Policy.jsx b/src/pages/group/Policy.jsx
new file mode 100644
index 000000000..79685bd70
--- /dev/null
+++ b/src/pages/group/Policy.jsx
@@ -0,0 +1,694 @@
+import {
+ Box, Button, FormControl,
+ Paper,
+ Grid,
+ TextField, Typography, InputAdornment, IconButton, FormLabel, MenuItem, Select, InputLabel, Card, CircularProgress, Alert,
+} from '@mui/material'
+import React, { useEffect, useState } from 'react'
+import { experimentalStyled as styled } from '@mui/material/styles';
+import DeleteOutline from "@mui/icons-material/DeleteOutline";
+import MailOutlineIcon from '@mui/icons-material/MailOutline';
+import ProposalSendForm from './ProposalSendForm';
+import ProposalDelegateForm from './ProposalDelegateForm';
+import ProposalRedelegateForm from './ProposalRedelegateForm';
+import ProposalUndelegateForm from './ProposalUndelegateForm';
+import RowItem from '../../components/group/RowItem';
+import { getAmountObj, getLocalStorage, proposalStatus, shortenAddress } from '../../utils/util';
+import { useDispatch, useSelector } from 'react-redux';
+import { getGroupPolicyProposals, txCreateGroupProposal, txGroupProposalExecute, txGroupProposalVote, txUpdateGroupPolicy, txUpdateGroupPolicyAdmin, txUpdateGroupPolicyMetdata } from '../../features/group/groupSlice';
+import { useNavigate, useParams } from 'react-router-dom';
+import DialogVote from '../../components/group/DialogVote';
+import EastIcon from '@mui/icons-material/East';
+import PolicyForm from '../../components/group/PolicyForm';
+import EditIcon from '@mui/icons-material/Edit';
+import CancelIcon from '@mui/icons-material/Cancel';
+import PolicyInfo from './PolicyInfo';
+import PolicyProposalsList from './PolicyProposalsList';
+import AddIcon from '@mui/icons-material/Add';
+
+const DELEGATE_MSG = `/cosmos.staking.v1beta1.MsgDelegate`;
+const SEND_MSG = `/cosmos.bank.v1beta1.MsgSend`;
+
+const voteStatus = {
+ STATUS_CLOSED: 'Closed',
+}
+
+const Item = styled(Paper)(({ theme }) => ({
+ backgroundColor: theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
+ ...theme.typography.body2,
+ padding: theme.spacing(2),
+ boxShadow: 'none',
+ textAlign: 'center',
+ color: theme.palette.text.secondary,
+}));
+
+const DelegateMsgComponent = ({ msg, index, deleteMsg }) => {
+ return (
+
+ Delegate
+ {`${msg?.value?.amount?.amount} `}
+ {`${msg?.value?.amount?.denom} `}
+ To
+ {
+ shortenAddress(msg?.value?.validatorAddress, 30)
+ }
+
+ deleteMsg(index)}
+ sx={{ float: 'right', color: 'red' }}>
+
+
+
+
+ )
+}
+
+const SendMsgComponent = ({ msg, index, deleteMsg }) => {
+ return (
+
+ Send
+ {`${msg?.value?.amount?.[0]?.amount} `}
+ {`${msg?.value?.amount?.[0]?.denom} `}
+ To
+ {
+ shortenAddress(msg?.value?.toAddress, 30)
+ }
+
+ deleteMsg(index)}
+ sx={{ float: 'right', color: 'red' }}>
+
+
+
+
+ )
+}
+
+const CreateProposal = ({ policyInfo }) => {
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+ const params = useParams();
+ const wallet = useSelector(state => state.wallet);
+ const [showMsgFrom, setShowMsgForm] = useState(true);
+ const denom = wallet?.chainInfo?.config?.currencies?.[0].coinMinimalDenom || '-'
+ console.log('wallet', wallet)
+ const proposerObj = {
+ name: 'proposer',
+ placeholder: 'Enter Proposer Address',
+ value: '',
+ }
+
+ const [showCreateProposal, setShowCreateProposal] = useState(false);
+ var [proposers, setProposers] = useState([{ ...proposerObj }]);
+ const createProposalRes = useSelector(state => state.group.groupProposalRes)
+ const [obj, setObj] = useState({
+ groupPolicyAddress: policyInfo?.address || '',
+ messages: []
+ });
+
+ const handleAddProposer = () => {
+ proposers = [...proposers, proposerObj];
+ setProposers([...proposers]);
+ }
+
+ const handleChange = e => {
+ obj[e.target.name] = e.target.value;
+ setObj({ ...obj });
+ }
+
+ const handleMsgChange = msgObj => {
+ let message = {};
+ if (msgObj?.typeUrl === DELEGATE_MSG) {
+ let val = JSON.parse(msgObj?.validatorAddress)
+ message = {
+ typeUrl: msgObj?.typeUrl,
+ value: {
+ delegatorAddress: msgObj?.delegatorAddress,
+ validatorAddress: val?.value,
+ amount: getAmountObj(msgObj?.amount, wallet?.chainInfo)
+ }
+ }
+ }
+
+ if (msgObj?.typeUrl === SEND_MSG) {
+ message = {
+ typeUrl: msgObj?.typeUrl,
+ value: {
+ fromAddress: msgObj?.fromAddress,
+ toAddress: msgObj?.toAddress,
+ amount: [getAmountObj(msgObj?.amount, wallet?.chainInfo)]
+ }
+ }
+ }
+
+ obj.messages = [...obj?.messages, message];
+ setObj({ ...obj });
+ setShowMsgForm(false);
+ }
+
+ const onRemove = (index) => {
+ proposers.splice(index, 1);
+ setProposers([...proposers])
+ }
+
+ const handleSubmit = (e) => {
+ e.preventDefault();
+ obj['proposers'] = proposers.map(p => p.value);
+ obj['admin'] = wallet?.address;
+ obj['chainId'] = wallet?.chainInfo?.config?.chainId
+ obj['rpc'] = wallet?.chainInfo?.config?.rpc;
+ obj['denom'] = wallet?.chainInfo?.config?.currencies?.[0]?.coinMinimalDenom || ''
+ obj['feeAmount'] = wallet?.chainInfo?.config?.gasPriceStep?.average || 0
+ dispatch(txCreateGroupProposal(obj));
+ }
+
+ const deleteMsg = i => {
+ obj.messages.splice(i, 1);
+ setObj({ ...obj })
+ }
+
+ return (
+
+ }
+ sx={{ float: 'right', m: 6 }}
+ variant='contained'
+ onClick={()=>navigate(`/group/${params?.id}/policies/${policyInfo?.address}/proposals`)}
+ // onClick={() => setShowCreateProposal(!showCreateProposal)}
+ >
+ Create Proposal
+
+
+ {
+ showCreateProposal ?
+
+
+
+
+ Create Proposal
+
+
+
+ : null
+ }
+
+ )
+}
+
+const AllProposals = () => {
+ const dispatch = useDispatch();
+ const params = useParams();
+
+ const proposals = useSelector(state => state.group?.proposals)
+ const wallet = useSelector(state => state.wallet)
+ const [voteOpen, setVoteOpen] = useState(false);
+ const voteRes = useSelector(state => state.group?.voteRes);
+ const createProposalRes = useSelector(state => state.group?.groupProposalRes);
+ const navigate = useNavigate();
+
+ const getProposals = () => {
+ dispatch(getGroupPolicyProposals({
+ baseURL: wallet?.chainInfo?.config?.rest,
+ address: params?.policyId
+ }))
+ }
+
+ useEffect(() => {
+ if (createProposalRes?.status === 'idle')
+ getProposals()
+
+ }, [createProposalRes?.status])
+
+ useEffect(() => {
+ getProposals();
+ }, [])
+
+ const onVoteDailogClose = () => {
+ setVoteOpen(false);
+ }
+
+ const onConfirm = (voteObj) => {
+ const chainInfo = wallet?.chainInfo;
+
+ dispatch(txGroupProposalVote({
+ admin: wallet?.address,
+ voter: wallet?.address,
+ option: voteObj?.vote,
+ proposalId: voteObj?.proposalId,
+ chainId: chainInfo?.config?.chainId,
+ rpc: chainInfo?.config?.rpc,
+ denom: chainInfo?.config?.currencies?.[0]?.coinMinimalDenom,
+ feeAmount: chainInfo?.config?.gasPriceStep?.average,
+ }))
+ console.log('vote objj', voteObj)
+ }
+
+ const onExecute = (proposalId) => {
+ const chainInfo = wallet?.chainInfo;
+
+ dispatch(txGroupProposalExecute({
+ proposalId: proposalId,
+ admin: wallet?.address,
+ executor: wallet?.address,
+ chainId: chainInfo?.config?.chainId,
+ rpc: chainInfo?.config?.rpc,
+ denom: chainInfo?.config?.currencies?.[0]?.coinMinimalDenom,
+ feeAmount: chainInfo?.config?.gasPriceStep?.average,
+ }))
+ }
+
+ return (
+
+
+ All Proposals
+
+ {
+ proposals?.status === 'pending' ?
+ : null
+ }
+
+ {
+ proposals?.status !== 'pending' &&
+ !proposals?.data?.proposals?.length ?
+
+
+ No proposals found.
+
+
+ : null
+ }
+
+ {proposals?.status !== 'pending' && proposals?.data?.proposals?.length &&
+ proposals?.data?.proposals?.map((p, index) => (
+
+ -
+
+
+
+
+ # {p?.id}
+
+
+
+ {proposalStatus[p?.status]?.label || p?.status}
+
+
+
+ {
+ p?.status === 'PROPOSAL_STATUS_SUBMITTED' ?
+
+ : null
+ }
+
+ {
+ p?.status === 'PROPOSAL_STATUS_ACCEPTED' ?
+
+ : null
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Proposers
+
+
+
+ {
+ p?.proposers?.map(p1 => (
+
+ - {shortenAddress(p1, 19)}
+
+ ))
+ }
+
+
+
+
+
+
+
+
+ Messages
+
+
+
+ {
+ p?.messages?.map(m => (
+
+ {
+ m['@type'] === SEND_MSG ?
+
+ - Send
+ {
+ `${m?.amount?.[0]?.amount} `
+ }
+ {
+ `${m?.amount?.[0]?.denom} `
+ }
+ To
+ {
+ `${shortenAddress(m?.to_address, 19)}`
+ }
+ : null
+ }
+ {
+ m['@type'] === DELEGATE_MSG ?
+
+
+ - Delegate
+ {
+ `${m?.amount?.amount} `
+ }
+ {
+ `${m?.amount?.denom} `
+ }
+ To
+ {
+ `${shortenAddress(m?.validator_address, 19)}`
+ }
+ : null
+ }
+
+ ))
+ }
+
+
+
+
+ {
+ navigate(`/groups/proposals/${p?.id}`)
+ }}
+ sx={{
+ height: '50%',
+ m: '0 auto',
+ fontSize: 35,
+ color: '#000'
+ }} />
+
+
+
+
+
+
+ )) || null}
+
+
+ )
+}
+
+function Policy() {
+ const [showMetdataInput, setShowMetadataInput] = useState(false);
+ const [showAdminInput, setShowAdminInput] = useState(false);
+ const [metadata, setMetadata] = useState('');
+ const [admin, setAdmin] = useState('');
+ const [showEditForm, setShowEditForm] = useState(false);
+ const dispatch = useDispatch();
+
+ const wallet = useSelector(state => state.wallet);
+ const updateMetadataRes = useSelector(state => state.group.updateGroupMetadataRes)
+
+ let policyInfo = {};
+ try {
+ policyInfo = getLocalStorage('policy', 'object');
+ } catch (error) {
+ console.log('Errot while getting policy', error?.message)
+ }
+
+ useEffect(() => {
+ if (updateMetadataRes?.status === 'idle')
+ setShowMetadataInput(false);
+ }, [updateMetadataRes?.status])
+
+ const handleSubmitPolicy = (policyMetadata) => {
+ const chainInfo = wallet?.chainInfo;
+
+ dispatch(txUpdateGroupPolicy({
+ admin: policyInfo?.admin,
+ groupPolicyAddress: policyInfo?.address,
+ policyMetadata: policyMetadata,
+ denom: chainInfo?.config?.currencies?.[0]?.minimalCoinDenom,
+ chainId: chainInfo.config.chainId,
+ rpc: chainInfo.config.rpc,
+ feeAmount: chainInfo.config.gasPriceStep.average,
+ }))
+ }
+
+ const handlePolicyMetadata = () => {
+ const chainInfo = wallet?.chainInfo;
+
+ dispatch(txUpdateGroupPolicyMetdata({
+ admin: policyInfo?.admin,
+ groupPolicyAddress: policyInfo?.address,
+ metadata: metadata,
+ denom: chainInfo?.config?.currencies?.[0]?.minimalCoinDenom,
+ chainId: chainInfo.config.chainId,
+ rpc: chainInfo.config.rpc,
+ feeAmount: chainInfo.config.gasPriceStep.average,
+ }))
+ }
+
+ const handleUpdateAdmin = () => {
+ const chainInfo = wallet?.chainInfo;
+
+ dispatch(txUpdateGroupPolicyAdmin({
+ admin: policyInfo?.admin,
+ groupPolicyAddress: policyInfo?.address,
+ newAdmin: admin,
+ denom: chainInfo?.config?.currencies?.[0]?.minimalCoinDenom,
+ chainId: chainInfo.config.chainId,
+ rpc: chainInfo.config.rpc,
+ feeAmount: chainInfo.config.gasPriceStep.average,
+ }))
+ }
+
+ return (
+
+
+
+
+
+ )
+}
+
+export default Policy
\ No newline at end of file
diff --git a/src/pages/group/PolicyInfo.jsx b/src/pages/group/PolicyInfo.jsx
new file mode 100644
index 000000000..9aeadaf3f
--- /dev/null
+++ b/src/pages/group/PolicyInfo.jsx
@@ -0,0 +1,179 @@
+import {
+ Button, Grid, TextField,
+ Paper,
+ Box,
+ Typography,
+ CircularProgress
+} from '@mui/material';
+import React, { useEffect, useState } from 'react'
+import CancelIcon from '@mui/icons-material/Cancel';
+import { useDispatch, useSelector } from 'react-redux';
+import { getLocalStorage, shortenAddress } from '../../utils/util';
+import { getGroupPoliciesById, txUpdateGroupPolicy, txUpdateGroupPolicyAdmin, txUpdateGroupPolicyMetdata } from '../../features/group/groupSlice';
+import PolicyForm from '../../components/group/PolicyForm';
+import EditIcon from '@mui/icons-material/Edit';
+import RowItem from '../../components/group/RowItem';
+import { useParams } from 'react-router-dom';
+import AlertMsg from '../../components/group/AlertMsg';
+import PolicyDetails from '../../components/group/PolicyDetails';
+import PlayArrowIcon from '@mui/icons-material/PlayArrow';
+import { useForm } from 'react-hook-form';
+import CreateGroupPolicy from './CreateGroupPolicy'
+
+function PolicyInfo() {
+ const [policyObj, setPolicyObj] = useState({});
+ const [isEditPolicyForm, setEditPolicyForm] = useState(false);
+
+ const dispatch = useDispatch();
+ const { id, policyId } = useParams();
+
+ const wallet = useSelector(state => state.wallet);
+ const updateMetadataRes = useSelector(state => state.group.updateGroupMetadataRes);
+ const updatePolicyAdminRes = useSelector(state => state.group.updatePolicyAdminRes);
+ const updateGroupPolicyRes = useSelector(state => state.group.updateGroupPolicyRes);
+
+ const groupPoliceis = useSelector(state => state?.group?.groupPolicies)
+
+ const getPolicies = () => {
+ dispatch(getGroupPoliciesById({
+ baseURL: wallet?.chainInfo?.config?.rest,
+ id: id,
+ pagination: {
+ key: '',
+ limit: 100
+ }
+ }))
+ }
+
+ useEffect(() => {
+ getPolicies();
+ }, [])
+
+ useEffect(() => {
+ const data = groupPoliceis?.data?.group_policies || [];
+ if (data?.length) {
+ const pArr = data.filter(d => d.address === policyId)
+ if (pArr?.length) {
+ setPolicyObj(pArr[0]);
+ }
+ }
+ }, [groupPoliceis?.status])
+
+ useEffect(() => {
+ if (updateMetadataRes?.status === 'idle')
+ getPolicies();
+ }, [updateMetadataRes?.status])
+
+ useEffect(() => {
+ if (updatePolicyAdminRes?.status === 'idle')
+ getPolicies();
+ }, [updatePolicyAdminRes?.status])
+
+ useEffect(() => {
+ if (updateGroupPolicyRes?.status === 'idle') {
+ setEditPolicyForm(false);
+ getPolicies();
+ }
+ }, [updateGroupPolicyRes?.status])
+
+
+ const handlePolicyMetadata = (newMetadata) => {
+ const chainInfo = wallet?.chainInfo;
+
+ dispatch(txUpdateGroupPolicyMetdata({
+ admin: policyObj?.admin,
+ groupPolicyAddress: policyObj?.address,
+ metadata: newMetadata,
+ denom: chainInfo?.config?.currencies?.[0]?.minimalCoinDenom,
+ chainId: chainInfo.config.chainId,
+ rpc: chainInfo.config.rpc,
+ feeAmount: chainInfo.config.gasPriceStep.average,
+ }))
+ }
+
+ const handleUpdateAdmin = (newAdmin) => {
+ const chainInfo = wallet?.chainInfo;
+
+ dispatch(txUpdateGroupPolicyAdmin({
+ admin: policyObj?.admin,
+ groupPolicyAddress: policyObj?.address,
+ newAdmin: newAdmin,
+ denom: chainInfo?.config?.currencies?.[0]?.minimalCoinDenom,
+ chainId: chainInfo.config.chainId,
+ rpc: chainInfo.config.rpc,
+ feeAmount: chainInfo.config.gasPriceStep.average,
+ }))
+ }
+
+ const handleSubmitPolicy = (policyMetadata) => {
+ const chainInfo = wallet?.chainInfo;
+
+ console.log('handle policy metad-----', policyMetadata)
+
+ dispatch(txUpdateGroupPolicy({
+ admin: policyObj?.admin,
+ groupPolicyAddress: policyObj?.address,
+ policyMetadata: policyMetadata,
+ denom: chainInfo?.config?.currencies?.[0]?.minimalCoinDenom,
+ chainId: chainInfo.config.chainId,
+ rpc: chainInfo.config.rpc,
+ feeAmount: chainInfo.config.gasPriceStep.average,
+ }))
+ }
+
+ return (
+
+ Policy Information
+ {
+ groupPoliceis?.status === 'pending' ?
+
+
+ : null
+ }
+ {
+ (groupPoliceis?.status === 'idle' &&
+ !policyObj?.address) &&
+ }
+
+ {
+ (groupPoliceis?.status === 'idle' &&
+ policyObj?.address) ?
+
+
+ }
+ variant='contained'
+ onClick={() => setEditPolicyForm(true)}
+ sx={{ float: 'right', mb: 9 }}>
+ Update policy
+
+
+ {
+ isEditPolicyForm ?
+ setEditPolicyForm(false)}
+ />
+
+ :
+
+ }
+
+
+ : null
+ }
+
+ )
+}
+
+export default PolicyInfo
\ No newline at end of file
diff --git a/src/pages/group/PolicyProposalsList.jsx b/src/pages/group/PolicyProposalsList.jsx
new file mode 100644
index 000000000..f0e8442c3
--- /dev/null
+++ b/src/pages/group/PolicyProposalsList.jsx
@@ -0,0 +1,113 @@
+import {
+ Alert, Card, CircularProgress,
+ Button,
+ Grid, Paper, Typography
+} from '@mui/material';
+import { Box } from '@mui/system';
+import React, { useState, useEffect } from 'react'
+import { useDispatch, useSelector } from 'react-redux';
+import { useNavigate, useParams } from 'react-router-dom';
+import AlertMsg from '../../components/group/AlertMsg';
+import ProposalCard from '../../components/group/ProposalCard';
+import { getGroupPolicyProposals, txGroupProposalExecute, txGroupProposalVote } from '../../features/group/groupSlice';
+import { proposalStatus } from '../../utils/util';
+
+function PolicyProposalsList() {
+
+ const dispatch = useDispatch();
+ const params = useParams();
+
+ const proposals = useSelector(state => state.group?.proposals)
+ console.log('p----------', proposals)
+ const wallet = useSelector(state => state.wallet)
+ const [voteOpen, setVoteOpen] = useState(false);
+ const voteRes = useSelector(state => state.group?.voteRes);
+ const createProposalRes = useSelector(state => state.group?.groupProposalRes);
+ const navigate = useNavigate();
+
+ const getProposals = () => {
+ dispatch(getGroupPolicyProposals({
+ baseURL: wallet?.chainInfo?.config?.rest,
+ address: params?.policyId
+ }))
+ }
+
+ useEffect(() => {
+ if (createProposalRes?.status === 'idle')
+ getProposals()
+
+ }, [createProposalRes?.status])
+
+ useEffect(() => {
+ getProposals();
+ }, [])
+
+ const onVoteDailogClose = () => {
+ setVoteOpen(false);
+ }
+
+ const onConfirm = (voteObj) => {
+ const chainInfo = wallet?.chainInfo;
+
+ dispatch(txGroupProposalVote({
+ admin: wallet?.address,
+ voter: wallet?.address,
+ option: voteObj?.vote,
+ proposalId: voteObj?.proposalId,
+ chainId: chainInfo?.config?.chainId,
+ rpc: chainInfo?.config?.rpc,
+ denom: chainInfo?.config?.currencies?.[0]?.coinMinimalDenom,
+ feeAmount: chainInfo?.config?.gasPriceStep?.average,
+ }))
+ console.log('vote objj', voteObj)
+ }
+
+ const onExecute = (proposalId) => {
+ const chainInfo = wallet?.chainInfo;
+
+ dispatch(txGroupProposalExecute({
+ proposalId: proposalId,
+ admin: wallet?.address,
+ executor: wallet?.address,
+ chainId: chainInfo?.config?.chainId,
+ rpc: chainInfo?.config?.rpc,
+ denom: chainInfo?.config?.currencies?.[0]?.coinMinimalDenom,
+ feeAmount: chainInfo?.config?.gasPriceStep?.average,
+ }))
+ }
+
+ return (
+
+
+ All Proposals
+
+
+ {
+ proposals?.status === 'pending' ?
+ : null
+ }
+
+ {
+ (proposals?.status === 'idle' &&
+ !proposals?.data?.proposals?.length) ?
+ : null
+ }
+
+
+ {
+ proposals?.data?.proposals?.map(p => (
+
+
+
+ ))
+ }
+
+
+ )
+}
+
+export default PolicyProposalsList
\ No newline at end of file
diff --git a/src/pages/group/Proposal.jsx b/src/pages/group/Proposal.jsx
new file mode 100644
index 000000000..5d2002120
--- /dev/null
+++ b/src/pages/group/Proposal.jsx
@@ -0,0 +1,366 @@
+import { Alert, Button, Card, Chip, CircularProgress, Grid, Paper, Typography } from '@mui/material'
+import { Box } from '@mui/system'
+import React, { useEffect, useState } from 'react'
+import { useDispatch, useSelector } from 'react-redux';
+import { useParams } from 'react-router-dom';
+import VotesTable from '../../components/group/VotesTable';
+import RowItem from '../../components/group/RowItem';
+import { getGroupProposalById, getVotesProposalById, txGroupProposalExecute, txGroupProposalVote } from '../../features/group/groupSlice';
+import { proposalStatus, shortenAddress } from '../../utils/util';
+import AlertMsg from '../../components/group/AlertMsg';
+import { getLocalTime } from '../../utils/datetime';
+import DailogVote from '../../components/group/DialogVote';
+
+const ProposalInfo = ({ id, wallet }) => {
+ const [voteOpen, setVoteOpen] = useState(false);
+
+ const dispatch = useDispatch();
+ const proposalInfo = useSelector(state => state.group.groupProposal);
+ const voteRes = useSelector(state => state.group?.voteRes);
+
+ const { data: { proposal } = {} } = proposalInfo;
+
+ const getProposal = () => {
+ dispatch(getGroupProposalById({
+ baseURL: wallet?.chainInfo?.config?.rest,
+ id
+ }))
+ }
+
+ useEffect(() => {
+ getProposal();
+ }, [])
+
+ useEffect(() => {
+ if (voteRes?.status === 'idle')
+ setVoteOpen(false);
+ }, [voteRes?.status])
+
+ const onVoteDailogClose = () => {
+ setVoteOpen(false);
+ }
+
+ const onConfirm = (voteObj) => {
+ const chainInfo = wallet?.chainInfo;
+
+ dispatch(txGroupProposalVote({
+ admin: wallet?.address,
+ voter: wallet?.address,
+ option: voteObj?.vote,
+ proposalId: voteObj?.proposalId,
+ chainId: chainInfo?.config?.chainId,
+ rpc: chainInfo?.config?.rpc,
+ denom: chainInfo?.config?.currencies?.[0]?.coinMinimalDenom,
+ feeAmount: chainInfo?.config?.gasPriceStep?.average,
+ }))
+ console.log('vote objj', voteObj)
+ }
+
+ const onExecute = (proposalId) => {
+ const chainInfo = wallet?.chainInfo;
+
+ dispatch(txGroupProposalExecute({
+ proposalId: proposalId,
+ admin: wallet?.address,
+ executor: wallet?.address,
+ chainId: chainInfo?.config?.chainId,
+ rpc: chainInfo?.config?.rpc,
+ denom: chainInfo?.config?.currencies?.[0]?.coinMinimalDenom,
+ feeAmount: chainInfo?.config?.gasPriceStep?.average,
+ }))
+ }
+
+ return (
+
+
+ {
+ proposalInfo?.status === 'pending' ?
+ : null
+ }
+ {
+ (proposalInfo?.status === 'idle' &&
+ !proposal) ?
+ : null
+ }
+
+ {
+ proposalInfo?.status === 'idle' ?
+
+
+
+
+
+
+
+ # {proposal?.metadata || '-'}
+
+
+
+
+
+
+ Submit Time
+
+
+ {getLocalTime(proposal?.submit_time)}
+
+
+
+
+
+ Voting Ends
+
+
+ {getLocalTime(proposal?.voting_period_end)}
+
+
+
+
+
+ Group Policy Address
+
+
+ {shortenAddress(proposal?.group_policy_address, 21)}
+
+
+
+
+
+ Proposers
+
+
+ {
+ proposal?.proposers?.map(p => (
+ <>
+
+ {shortenAddress(p, 21)}
+
+
+ >
+ ))
+ }
+
+
+
+
+ {
+ proposal?.status === 'PROPOSAL_STATUS_SUBMITTED' ?
+
+ : null
+ }
+
+ {
+ proposal?.status === 'PROPOSAL_STATUS_ACCEPTED' ?
+
+ : null
+ }
+
+
+
+
+
+ Vote Details
+
+
+
+ Yes
+
+ {proposal?.final_tally_result?.yes_count || 0}
+
+
+
+
+
+
+ No
+
+ {proposal?.final_tally_result?.no_count || 0}
+
+
+
+
+
+
+ Abstain
+
+ {proposal?.final_tally_result?.abstain_count || 0}
+
+
+
+
+
+
+ Veto
+ {proposal?.final_tally_result?.no_with_veto_account || 0}
+
+
+
+
+
+
+
+ Messages
+
+ {
+ proposal?.messages?.map(p => (
+
+ {
+ p['@type'] === '/cosmos.bank.v1beta1.MsgSend' ?
+
+
+ Send
+ {p?.amount?.[0]?.amount}
+ {p?.amount?.[0]?.denom}
+ to
+ {p?.to_address}
+ : null
+ }
+ {
+ p['@type'] === '/cosmos.staking.v1beta1.MsgDelegate' ?
+
+
+ Delegate
+ {p?.amount?.amount} &?.[0]nbsp;
+ {p?.amount?.denom}
+ to
+ {p?.validator_address}
+ : null
+ }
+
+ ))
+ }
+
+
+ : null
+ }
+
+ )
+}
+
+function Proposal() {
+ const dispatch = useDispatch();
+ const params = useParams();
+ const { id } = params;
+ const [limit, setLimit] = useState(5);
+ const [total, setTotal] = useState(0);
+ const [pageNumber, setPageNumber] = useState(0);
+ const wallet = useSelector(state => state.wallet);
+ const voteRes = useSelector(state => state.group?.voteRes);
+
+ const fetchVotes = (baseURL, id, limit, key) => {
+ dispatch(getVotesProposalById({
+ baseURL: baseURL,
+ id: id,
+ pagination: { limit: limit, key: key },
+ }))
+ }
+
+ useEffect(() => {
+ if (voteRes?.status === 'idle')
+ fetchVotes(wallet?.chainInfo?.config?.rest, id, limit, '')
+ }, [voteRes?.status])
+
+ useEffect(() => {
+ fetchVotes(wallet?.chainInfo?.config?.rest, id, limit, '')
+ }, [])
+
+ const handleMembersPagination = (number, limit, key) => {
+ setLimit(limit);
+ setPageNumber(number);
+ fetchVotes(wallet?.chainInfo?.config?.rest, id, limit, key)
+ }
+
+ const groupInfo = useSelector(state => state.group.proposalVotes);
+ const { data, status } = groupInfo;
+
+ useEffect(() => {
+ if (Number(data?.pagination?.total))
+ setTotal(Number(data?.pagination?.total || 0))
+ }, [data])
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {
+ status === 'pending' ?
+ : null
+ }
+
+ {
+ status !== 'pending' ?
+
+
+ : null}
+
+
+
+ )
+}
+
+export default Proposal
\ No newline at end of file
diff --git a/src/pages/group/ProposalDelegateForm.jsx b/src/pages/group/ProposalDelegateForm.jsx
new file mode 100644
index 000000000..157adda6a
--- /dev/null
+++ b/src/pages/group/ProposalDelegateForm.jsx
@@ -0,0 +1,108 @@
+import { Button, FormControl, InputAdornment, InputLabel, MenuItem, Select, TextField } from '@mui/material'
+import { Box } from '@mui/system'
+import React, { useEffect, useState } from 'react'
+import { useDispatch, useSelector } from 'react-redux';
+import { getValidators } from '../../features/staking/stakeSlice';
+
+function ProposalDelegateForm({ chainInfo,
+ handleMsgChange,
+ type,
+ fromAddress }) {
+ var [data, setData] = useState([]);
+ const dispatch = useDispatch();
+
+ const denom = chainInfo?.config?.currencies?.[0]?.coinDenom || ''
+ const [obj, setObj] = useState({
+ typeUrl: type,
+ delegatorAddress: fromAddress
+ });
+
+ var validators = useSelector((state) => state.staking.validators);
+ validators = validators?.active || {};
+
+ useEffect(() => {
+ dispatch(
+ getValidators({
+ baseURL: chainInfo.config.rest,
+ status: null,
+ })
+ );
+ }, [])
+
+ useEffect(() => {
+ data = [];
+ Object.entries(validators).map(([k, v], index) => {
+ let obj1 = {
+ value: k,
+ label: v?.description?.moniker || k,
+ };
+
+ data = [...data, obj1];
+ });
+
+ setData([...data]);
+ }, [validators]);
+
+ const handleChange = e => {
+ obj[e.target.name] = e.target.value;
+ setObj({ ...obj });
+ }
+
+ const onSubmit = () => {
+ handleMsgChange(obj);
+ };
+
+ return (
+
+
+
+
+
+ Select Validator
+
+
+
+
+ {denom}
+
+ ),
+ }}
+ />
+
+
+
+
+ )
+}
+
+export default ProposalDelegateForm
\ No newline at end of file
diff --git a/src/pages/group/ProposalRedelegateForm.jsx b/src/pages/group/ProposalRedelegateForm.jsx
new file mode 100644
index 000000000..78f932150
--- /dev/null
+++ b/src/pages/group/ProposalRedelegateForm.jsx
@@ -0,0 +1,108 @@
+import { Button, FormControl, InputAdornment, InputLabel, MenuItem, Select, TextField } from '@mui/material'
+import { Box } from '@mui/system'
+import React, { useEffect, useState } from 'react'
+import { useDispatch, useSelector } from 'react-redux';
+import { getValidators } from '../../features/staking/stakeSlice';
+
+function ProposalReDelegateForm({ chainInfo,
+ handleMsgChange,
+ type,
+ fromAddress }) {
+ var [data, setData] = useState([]);
+ const dispatch = useDispatch();
+
+ const denom = chainInfo?.config?.currencies?.[0]?.coinDenom || ''
+ const [obj, setObj] = useState({
+ typeUrl: type,
+ delegatorAddress: fromAddress
+ });
+
+ var validators = useSelector((state) => state.staking.validators);
+ validators = validators?.active || {};
+
+ useEffect(() => {
+ dispatch(
+ getValidators({
+ baseURL: chainInfo.config.rest,
+ status: null,
+ })
+ );
+ }, [])
+
+ useEffect(() => {
+ data = [];
+ Object.entries(validators).map(([k, v], index) => {
+ let obj1 = {
+ value: k,
+ label: v?.description?.moniker || k,
+ };
+
+ data = [...data, obj1];
+ });
+
+ setData([...data]);
+ }, [validators]);
+
+ const handleChange = e => {
+ obj[e.target.name] = e.target.value;
+ setObj({ ...obj });
+ }
+
+ const onSubmit = () => {
+ handleMsgChange(obj);
+ };
+
+ return (
+
+
+
+
+
+ Select Validator
+
+
+
+
+ {denom}
+
+ ),
+ }}
+ />
+
+
+
+
+ )
+}
+
+export default ProposalReDelegateForm
\ No newline at end of file
diff --git a/src/pages/group/ProposalSendForm.jsx b/src/pages/group/ProposalSendForm.jsx
new file mode 100644
index 000000000..105dca534
--- /dev/null
+++ b/src/pages/group/ProposalSendForm.jsx
@@ -0,0 +1,47 @@
+import { Box, Button, FormControl, TextField } from '@mui/material'
+import React, { useState } from 'react';
+
+const TextFieldComponent = ({ name, placeholder, value,
+ disbled, onChange, type }) => (
+
+
+
+)
+
+function ProposalSendForm({ handleMsgChange, fromAddress, type }) {
+ const [obj, setObj] = useState({
+ typeUrl: type,
+ fromAddress: fromAddress,
+ });
+
+ const handleChange = e => {
+ obj[e.target.name] = e.target.value;
+ setObj({ ...obj });
+ }
+
+ return (
+
+
+
+
+
+
+ )
+}
+
+export default ProposalSendForm
\ No newline at end of file
diff --git a/src/pages/group/ProposalUndelegateForm.jsx b/src/pages/group/ProposalUndelegateForm.jsx
new file mode 100644
index 000000000..2e2e3557b
--- /dev/null
+++ b/src/pages/group/ProposalUndelegateForm.jsx
@@ -0,0 +1,110 @@
+import { Button, FormControl, InputAdornment, InputLabel, MenuItem, Select, TextField } from '@mui/material'
+import { Box } from '@mui/system'
+import React, { useEffect, useState } from 'react'
+import { useDispatch, useSelector } from 'react-redux';
+import { getDelegations, getValidators } from '../../features/staking/stakeSlice';
+
+function ProposalUnDelegateForm({ chainInfo,
+ handleMsgChange,
+ type,
+ address }) {
+ var [data, setData] = useState([]);
+ const dispatch = useDispatch();
+
+ const denom = chainInfo?.config?.currencies?.[0]?.coinDenom || ''
+ const [obj, setObj] = useState({
+ typeUrl: type,
+ delegatorAddress: address
+ });
+
+ var validators = useSelector((state) => state.staking.validators);
+ var delegatorVals = useSelector((state) => state.multisig.delegatorVals);
+ console.log('dddddddddd', delegatorVals)
+ validators = validators?.active || {};
+
+ useEffect(() => {
+ dispatch(
+ getDelegations({
+ baseURL: chainInfo.config.rest,
+ address: address,
+ })
+ );
+ }, [])
+
+ useEffect(() => {
+ data = [];
+ Object.entries(validators).map(([k, v], index) => {
+ let obj1 = {
+ value: k,
+ label: v?.description?.moniker || k,
+ };
+
+ data = [...data, obj1];
+ });
+
+ setData([...data]);
+ }, [validators]);
+
+ const handleChange = e => {
+ obj[e.target.name] = e.target.value;
+ setObj({ ...obj });
+ }
+
+ const onSubmit = () => {
+ handleMsgChange(obj);
+ };
+
+ return (
+
+
+
+
+
+ Select Validator
+
+
+
+
+ {denom}
+
+ ),
+ }}
+ />
+
+
+
+
+ )
+}
+
+export default ProposalUnDelegateForm
\ No newline at end of file
diff --git a/src/pages/group/group-css.js b/src/pages/group/group-css.js
new file mode 100644
index 000000000..dd30f220e
--- /dev/null
+++ b/src/pages/group/group-css.js
@@ -0,0 +1,45 @@
+export const groupStyles = {
+ fr: {
+ float: 'right'
+ },
+ mb_2: {
+ mb: 2
+ },
+ mt_2: {
+ mt: 2
+ },
+ fr_mb_2: {
+ float: 'right',
+ mb: 2
+ },
+ fw: {
+ width: '100%',
+ mb: 2
+ },
+ j_right: {
+ justifyContent: 'right',
+ },
+ d_flex: {
+ display: 'flex',
+ },
+ get gp_main() {
+ return {
+ ...this.j_right,
+ ...this.d_flex
+ }
+ },
+ t_transform_btn: {
+ textTransform: 'none',
+ padding: '10px',
+ fontSize: '16px'
+ },
+ t_align: {
+ textAlign: 'left'
+ },
+ get btn_g_box() {
+ return {
+ ...this.t_align,
+ ...this.mt_2
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/pages/group/utils.ts b/src/pages/group/utils.ts
new file mode 100644
index 000000000..31f03c089
--- /dev/null
+++ b/src/pages/group/utils.ts
@@ -0,0 +1,206 @@
+import { parseCoins } from "@cosmjs/proto-signing";
+import { Msg } from "../../txns/types";
+
+export const SEND_TYPE_URL = "/cosmos.bank.v1beta1.MsgSend";
+export const DELEGATE_TYPE_URL = "/cosmos.staking.v1beta1.MsgDelegate";
+export const UNDELEGATE_TYPE_URL = "/cosmos.staking.v1beta1.MsgUndelegate";
+export const REDELEGATE_TYPE_URL = "/cosmos.staking.v1beta1.MsgBeginRedelegate";
+
+// parseSendMsgsFromContent returns list of parsed send messages. It returns an error
+// if provided content is invalid.
+export const parseSendMsgsFromContent = (
+ from: string,
+ content: string
+): [Msg[], string] => {
+ const messages = content.split("\n");
+
+ if (messages?.length === 0) {
+ return [[], "no messages or invalid file content"];
+ }
+
+ const msgs = [];
+ for (let i = 1; i < messages.length; i++) {
+ try {
+ const tx = parseSendTx(from, messages[i]);
+ if (tx && Object.keys(tx)?.length)
+ msgs.push(tx);
+ } catch (error: any) {
+ return [[], error?.message || `failed to parse message at ${i}`];
+ }
+ }
+
+ return [msgs, ""];
+};
+
+const parseSendTx = (from: string, msg: string): Msg => {
+ const values = msg.split(",");
+ console.log('Parsed values-----', values)
+ if (values?.length !== 2) {
+ return {}
+ // throw new Error(
+ // `invalid message: expected ${2} values got ${values.length}`
+ // );
+ }
+
+ const to = values[0];
+ const amount = parseCoins(values[1]);
+
+ if (amount.length === 0) {
+ throw new Error("amount cannot be empty");
+ }
+
+ return {
+ typeUrl: SEND_TYPE_URL,
+ value: {
+ fromAddress: from,
+ toAddress: to,
+ amount: amount,
+ },
+ };
+};
+
+// parseDelegateMsgsFromContent returns list of parsed delegate messages. It returns an error
+// if provided content is invalid.
+export const parseDelegateMsgsFromContent = (
+ delegator: string,
+ content: string
+): [Msg[], string] => {
+ const messages = content.split("\n");
+
+ if (messages?.length === 0) {
+ return [[], "no messages or invalid file content"];
+ }
+
+ const msgs = [];
+ for (let i = 0; i < messages.length; i++) {
+ try {
+ const msg = parseDelegateMsg(delegator, messages[i]);
+ msgs.push(msg);
+ } catch (error: any) {
+ return [[], error?.message || `failed to parse message at ${i}`];
+ }
+ }
+
+ return [msgs, ""];
+};
+
+const parseDelegateMsg = (delegator: string, msg: string): Msg => {
+ const values = msg.split(",");
+ if (values?.length !== 2) {
+ throw new Error("invalid message");
+ }
+
+ const validator = values[0];
+ const amount = parseCoins(values[1]);
+
+ if (amount.length === 0) {
+ throw new Error("amount cannot be empty");
+ }
+
+ return {
+ typeUrl: DELEGATE_TYPE_URL,
+ value: {
+ delegatorAddress: delegator,
+ validatorAddress: validator,
+ amount: amount,
+ },
+ };
+};
+
+// parseUnDelegateMsgsFromContent returns list of parsed un-delegate messages. It returns an error
+// if provided content is invalid.
+export const parseUnDelegateMsgsFromContent = (
+ delegator: string,
+ content: string
+): [Msg[], string] => {
+ const messages = content.split("\n");
+
+ if (messages?.length === 0) {
+ return [[], "no messages or invalid file content"];
+ }
+
+ const msgs = [];
+ for (let i = 0; i < messages.length; i++) {
+ try {
+ const msg = parseUnDelegateMsg(delegator, messages[i]);
+ msgs.push(msg);
+ } catch (error: any) {
+ return [[], error?.message || `failed to parse message at ${i}`];
+ }
+ }
+
+ return [msgs, ""];
+};
+
+const parseUnDelegateMsg = (delegator: string, msg: string): Msg => {
+ const values = msg.split(",");
+ if (values?.length !== 2) {
+ throw new Error("invalid message");
+ }
+
+ const validator = values[0];
+ const amount = parseCoins(values[1]);
+
+ if (amount.length === 0) {
+ throw new Error("amount cannot be empty");
+ }
+
+ return {
+ typeUrl: UNDELEGATE_TYPE_URL,
+ value: {
+ delegatorAddress: delegator,
+ validatorAddress: validator,
+ amount: amount,
+ },
+ };
+};
+
+// parseReDelegateMsgsFromContent returns list of parsed re-delegate messages. It returns an error
+// if provided content is invalid.
+export const parseReDelegateMsgsFromContent = (
+ delegator: string,
+ content: string
+): [Msg[], string] => {
+ const messages = content.split("\n");
+
+ if (messages?.length === 0) {
+ return [[], "no messages or invalid file content"];
+ }
+
+ const msgs = [];
+ for (let i = 0; i < messages.length; i++) {
+ try {
+ const msg = parseReDelegateMsg(delegator, messages[i]);
+ msgs.push(msg);
+ } catch (error: any) {
+ return [[], error?.message || `failed to parse message at ${i}`];
+ }
+ }
+
+ return [msgs, ""];
+};
+
+const parseReDelegateMsg = (delegator: string, msg: string): Msg => {
+ const values = msg.split(",");
+ if (values?.length !== 3) {
+ throw new Error("invalid message");
+ }
+
+ const src = values[0];
+ const dest = values[1];
+ const amount = parseCoins(values[2]);
+
+ if (amount.length === 0) {
+ throw new Error("amount cannot be empty");
+ }
+
+ return {
+ typeUrl: REDELEGATE_TYPE_URL,
+ value: {
+ validatorDstAddress: dest,
+ validatorSrcAddress: src,
+ delegatorAddress: delegator,
+ amount: amount,
+ },
+ };
+};
diff --git a/src/txns/execute.js b/src/txns/execute.js
index 73f1dc764..fb23603df 100644
--- a/src/txns/execute.js
+++ b/src/txns/execute.js
@@ -6,7 +6,7 @@ import {
import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx";
import { Registry } from "@cosmjs/proto-signing";
import { MsgClaim } from "./passage/msg_claim";
-import { MsgCreateGroup, MsgCreateGroupWithPolicy } from "./group/v1/tx";
+import { MsgCreateGroup, MsgCreateGroupPolicy, MsgCreateGroupWithPolicy, MsgExec, MsgLeaveGroup, MsgSubmitProposal, MsgUpdateGroupAdmin, MsgUpdateGroupMembers, MsgUpdateGroupMetadata, MsgUpdateGroupPolicyAdmin, MsgUpdateGroupPolicyDecisionPolicy, MsgUpdateGroupPolicyMetadata, MsgVote } from "./group/v1/tx";
import { AirdropAminoConverter } from "../features/airdrop/amino";
import { MsgUnjail } from "./slashing/tx";
import { SlashingAminoConverter } from "../features/slashing/slashing";
@@ -44,6 +44,360 @@ export async function signAndBroadcastGroupMsg(
return await client.signAndBroadcast(signer, msgs, fee, memo);
}
+export async function signAndBroadcastUpdateGroupMembers(
+ signer,
+ msgs,
+ fee,
+ chainId,
+ rpcURL,
+ memo = ""
+) {
+ await window.keplr.enable(chainId);
+ const offlineSigner =
+ window.getOfflineSigner && window.keplr.getOfflineSigner(chainId);
+ let registry = new Registry();
+
+
+ registry.register(
+ "/cosmos.group.v1.MsgUpdateGroupMembers",
+ MsgUpdateGroupMembers
+ );
+
+ const client = await SigningStargateClient.connectWithSigner(
+ rpcURL,
+ offlineSigner,
+ {
+ registry: registry,
+ }
+ );
+
+ return await client.signAndBroadcast(signer, msgs, fee, memo);
+}
+
+export async function signAndBroadcastUpdateGroupPolicy(
+ signer,
+ msgs,
+ fee,
+ chainId,
+ rpcURL,
+ memo = ""
+) {
+ await window.keplr.enable(chainId);
+ const offlineSigner =
+ window.getOfflineSigner && window.keplr.getOfflineSigner(chainId);
+ let registry = new Registry();
+
+
+ registry.register(
+ "/cosmos.group.v1.MsgUpdateGroupPolicyDecisionPolicy",
+ MsgUpdateGroupPolicyDecisionPolicy
+ );
+
+ const client = await SigningStargateClient.connectWithSigner(
+ rpcURL,
+ offlineSigner,
+ {
+ registry: registry,
+ }
+ );
+
+ return await client.signAndBroadcast(signer, msgs, fee, memo);
+}
+
+export async function signAndBroadcastUpdateGroupPolicyMetadata(
+ signer,
+ msgs,
+ fee,
+ chainId,
+ rpcURL,
+ memo = ""
+) {
+ await window.keplr.enable(chainId);
+ const offlineSigner =
+ window.getOfflineSigner && window.keplr.getOfflineSigner(chainId);
+ let registry = new Registry();
+
+
+ registry.register(
+ "/cosmos.group.v1.MsgUpdateGroupPolicyMetadata",
+ MsgUpdateGroupPolicyMetadata
+ );
+
+ const client = await SigningStargateClient.connectWithSigner(
+ rpcURL,
+ offlineSigner,
+ {
+ registry: registry,
+ }
+ );
+
+ return await client.signAndBroadcast(signer, msgs, fee, memo);
+}
+
+export async function signAndBroadcastUpdateGroupPolicyAdmin(
+ signer,
+ msgs,
+ fee,
+ chainId,
+ rpcURL,
+ memo = ""
+) {
+ await window.keplr.enable(chainId);
+ const offlineSigner =
+ window.getOfflineSigner && window.keplr.getOfflineSigner(chainId);
+ let registry = new Registry();
+
+
+ registry.register(
+ "/cosmos.group.v1.MsgUpdateGroupPolicyAdmin",
+ MsgUpdateGroupPolicyAdmin
+ );
+
+ const client = await SigningStargateClient.connectWithSigner(
+ rpcURL,
+ offlineSigner,
+ {
+ registry: registry,
+ }
+ );
+
+ return await client.signAndBroadcast(signer, msgs, fee, memo);
+}
+
+export async function signAndBroadcastAddGroupPolicy(
+ signer,
+ msgs,
+ fee,
+ chainId,
+ rpcURL,
+ memo = ""
+) {
+ await window.keplr.enable(chainId);
+ const offlineSigner =
+ window.getOfflineSigner && window.keplr.getOfflineSigner(chainId);
+ let registry = new Registry();
+
+
+ registry.register(
+ "/cosmos.group.v1.MsgCreateGroupPolicy",
+ MsgCreateGroupPolicy
+ );
+
+ const client = await SigningStargateClient.connectWithSigner(
+ rpcURL,
+ offlineSigner,
+ {
+ registry: registry,
+ }
+ );
+
+ return await client.signAndBroadcast(signer, msgs, fee, memo);
+}
+
+export async function signAndBroadcastLeaveGroup(
+ signer,
+ msgs,
+ fee,
+ chainId,
+ rpcURL,
+ memo = ""
+) {
+ await window.keplr.enable(chainId);
+ const offlineSigner =
+ window.getOfflineSigner && window.keplr.getOfflineSigner(chainId);
+ let registry = new Registry();
+
+
+ registry.register(
+ "/cosmos.group.v1.MsgLeaveGroup",
+ MsgLeaveGroup
+ );
+
+ const client = await SigningStargateClient.connectWithSigner(
+ rpcURL,
+ offlineSigner,
+ {
+ registry: registry,
+ }
+ );
+
+ return await client.signAndBroadcast(signer, msgs, fee, memo);
+}
+
+export async function signAndBroadcastGroupProposalVote(
+ signer,
+ msgs,
+ fee,
+ chainId,
+ rpcURL,
+ memo = ""
+) {
+ await window.keplr.enable(chainId);
+ const offlineSigner =
+ window.getOfflineSigner && window.keplr.getOfflineSigner(chainId);
+ let registry = new Registry();
+
+ const aTypes = new AminoTypes({
+ ...MsgVote,
+ });
+
+ registry.register(
+ "/cosmos.group.v1.MsgVote",
+ MsgVote
+ );
+
+ const client = await SigningStargateClient.connectWithSigner(
+ rpcURL,
+ offlineSigner,
+ {
+ registry: registry,
+ aminoTypes: aTypes,
+ }
+ );
+
+ return await client.signAndBroadcast(signer, msgs, fee, memo);
+}
+
+export async function signAndBroadcastGroupProposalExecute(
+ signer,
+ msgs,
+ fee,
+ chainId,
+ rpcURL,
+ memo = ""
+) {
+ await window.keplr.enable(chainId);
+ const offlineSigner =
+ window.getOfflineSigner && window.keplr.getOfflineSigner(chainId);
+ let registry = new Registry();
+
+ const aTypes = new AminoTypes({
+ ...MsgExec,
+ });
+
+ registry.register(
+ "/cosmos.group.v1.MsgExec",
+ MsgExec
+ );
+
+ const client = await SigningStargateClient.connectWithSigner(
+ rpcURL,
+ offlineSigner,
+ {
+ registry: registry,
+ aminoTypes: aTypes,
+ }
+ );
+
+ return await client.signAndBroadcast(signer, msgs, fee, memo);
+}
+
+export async function signAndBroadcastGroupProposal(
+ signer,
+ msgs,
+ fee,
+ chainId,
+ rpcURL,
+ memo = ""
+) {
+ await window.keplr.enable(chainId);
+ const offlineSigner =
+ window.getOfflineSigner && window.keplr.getOfflineSigner(chainId);
+ let registry = new Registry();
+
+ const aTypes = new AminoTypes({
+ ...MsgSubmitProposal,
+ });
+
+ defaultRegistryTypes.forEach((v) => {
+ registry.register(v[0], v[1]);
+ });
+
+ registry.register(
+ "/cosmos.group.v1.MsgSubmitProposal",
+ MsgSubmitProposal
+ );
+
+ const client = await SigningStargateClient.connectWithSigner(
+ rpcURL,
+ offlineSigner,
+ {
+ registry: registry,
+ aminoTypes: aTypes,
+ }
+ );
+
+ return await client.signAndBroadcast(signer, msgs, fee, memo);
+}
+
+export async function signAndBroadcastUpdateGroupAdmin(
+ signer,
+ msgs,
+ fee,
+ chainId,
+ rpcURL,
+ memo = ""
+) {
+ await window.keplr.enable(chainId);
+ const offlineSigner =
+ window.getOfflineSigner && window.keplr.getOfflineSigner(chainId);
+ let registry = new Registry();
+
+ const aTypes = new AminoTypes({
+ ...MsgUpdateGroupAdmin,
+ });
+
+ registry.register(
+ "/cosmos.group.v1.MsgUpdateGroupAdmin",
+ MsgUpdateGroupAdmin
+ );
+
+ const client = await SigningStargateClient.connectWithSigner(
+ rpcURL,
+ offlineSigner,
+ {
+ registry: registry,
+ aminoTypes: aTypes,
+ }
+ );
+
+ return await client.signAndBroadcast(signer, msgs, fee, memo);
+}
+
+export async function signAndBroadcastUpdateGroupMetadata(
+ signer,
+ msgs,
+ fee,
+ chainId,
+ rpcURL,
+ memo = ""
+) {
+ await window.keplr.enable(chainId);
+ const offlineSigner =
+ window.getOfflineSigner && window.keplr.getOfflineSigner(chainId);
+ let registry = new Registry();
+
+ const aTypes = new AminoTypes({
+ ...MsgUpdateGroupMetadata,
+ });
+
+ registry.register(
+ "/cosmos.group.v1.MsgUpdateGroupMetadata",
+ MsgUpdateGroupMetadata
+ );
+
+ const client = await SigningStargateClient.connectWithSigner(
+ rpcURL,
+ offlineSigner,
+ {
+ registry: registry,
+ aminoTypes: aTypes,
+ }
+ );
+
+ return await client.signAndBroadcast(signer, msgs, fee, memo);
+}
+
export async function signAndBroadcastClaimMsg(
signer,
msgs,
diff --git a/src/txns/group/group.ts b/src/txns/group/group.ts
new file mode 100644
index 000000000..7d85aaba0
--- /dev/null
+++ b/src/txns/group/group.ts
@@ -0,0 +1,406 @@
+import {
+ MsgCreateGroup, MsgCreateGroupWithPolicy,
+ MsgVote,
+ MsgSubmitProposal,
+ MsgExec,
+ MsgUpdateGroupMembers,
+ MsgLeaveGroup,
+ MsgUpdateGroupPolicyDecisionPolicy,
+ MsgUpdateGroupAdmin,
+ MsgUpdateGroupMetadata,
+ MsgCreateGroupPolicy,
+ MsgUpdateGroupPolicyMetadata,
+ MsgUpdateGroupPolicyAdmin,
+} from "./v1/tx";
+import { MsgSend } from "cosmjs-types/cosmos/bank/v1beta1/tx";
+import {
+ MsgDelegate
+} from "cosmjs-types/cosmos/staking/v1beta1/tx";
+import { Duration } from "cosmjs-types/google/protobuf/duration";
+import {
+ PercentageDecisionPolicy, ThresholdDecisionPolicy,
+ DecisionPolicyWindows,
+} from "./v1/types";
+import Long from "long";
+
+// group
+
+const msgCreateGroup = "/cosmos.group.v1.MsgCreateGroup";
+const msgCreateGroupWithPolicy = "/cosmos.group.v1.MsgCreateGroupWithPolicy";
+const msgCreateGroupWithThresholdPolicy = `/cosmos.group.v1.ThresholdDecisionPolicy`;
+const msgCreateGroupWithPercentagePolicy = `/cosmos.group.v1.PercentageDecisionPolicy`;
+const msgCreateGroupProposal = `/cosmos.group.v1.MsgSubmitProposal`;
+const msgGroupProposalVote = `/cosmos.group.v1.MsgVote`;
+const msgGroupProposalExecute = `/cosmos.group.v1.MsgExec`;
+const msgUpdateGroupMember = `/cosmos.group.v1.MsgUpdateGroupMembers`;
+const msgLeaveGroupMember = `/cosmos.group.v1.MsgLeaveGroup`;
+const msgUpdateGroupPolicy = `/cosmos.group.v1.MsgUpdateGroupPolicyDecisionPolicy`;
+const msgUpdateGroupAdmin = `/cosmos.group.v1.MsgUpdateGroupAdmin`;
+const msgUpdateGroupMetadata = `/cosmos.group.v1.MsgUpdateGroupMetadata`;
+const msgAddGroupPolicy = `/cosmos.group.v1.MsgCreateGroupPolicy`;
+const msgUpatePolicyMetdata = `/cosmos.group.v1.MsgUpdateGroupPolicyMetadata`;
+const msgUpatePolicyAdmin = `/cosmos.group.v1.MsgUpdateGroupPolicyAdmin`;
+
+export function CreateGroup(admin: any, metadata: any, members: any) {
+ try {
+ return {
+ typeUrl: msgCreateGroup,
+ value: MsgCreateGroup.fromPartial({
+ admin: admin,
+ members: members,
+ metadata: metadata,
+ }),
+ };
+ } catch (error) {
+ console.log('Error while creating obj for group and members', error)
+ throw error;
+ }
+
+}
+
+export function CreateProposalVote(
+ proposalId: string,
+ voter: string,
+ option: any,
+ metadata?: string
+) {
+ return {
+ typeUrl: msgGroupProposalVote,
+ value: MsgVote.fromPartial({
+ proposalId,
+ voter,
+ option: option,
+ metadata
+ })
+ }
+}
+
+export function CreateProposalExecute(
+ proposalId: string,
+ executor: string
+) {
+ return {
+ typeUrl: msgGroupProposalExecute,
+ value: MsgExec.fromPartial({
+ proposalId,
+ executor
+ })
+ }
+}
+
+export function CreateGroupProposal(groupPolicyAddress = '',
+ proposers: any = [], metadata = '', messages = []) {
+ var msgs: any = [];
+ let length = messages?.length || 0;
+
+ for (let i = 0; i < length; i++) {
+ let msg = messages[i];
+ let type = msg['typeUrl'];
+ switch (type) {
+ case '/cosmos.bank.v1beta1.MsgSend':
+ msgs = [...msgs, {
+ typeUrl: type,
+ value: MsgSend.encode(msg['value']).finish()
+ }]
+ break;
+ case '/cosmos.staking.v1beta1.MsgDelegate':
+ msgs = [...msgs, {
+ typeUrl: type,
+ value: MsgDelegate.encode(msg['value']).finish(),
+ }]
+ break;
+ }
+ }
+
+ return {
+ typeUrl: msgCreateGroupProposal,
+ value: MsgSubmitProposal.fromPartial({
+ groupPolicyAddress,
+ proposers,
+ metadata,
+ messages: msgs
+ })
+ }
+}
+
+export function CreateGroupWithPolicy(
+ admin: any,
+ groupMetadata: any,
+ members: any,
+ decisionPolicy: any,
+ policyMetadata: any,
+ policyAsAdmin: any = false
+) {
+ console.log('policy metada--------', policyMetadata, members, admin, groupMetadata, policyAsAdmin)
+
+ try {
+
+ if (Object.keys(policyMetadata)?.length) {
+ console.log('here----')
+ const obj = {
+ typeUrl: msgCreateGroupWithPolicy,
+ value: MsgCreateGroupWithPolicy.fromPartial({
+ admin: admin,
+ members: members,
+ groupMetadata: groupMetadata,
+ groupPolicyAsAdmin: policyAsAdmin,
+ groupPolicyMetadata: policyMetadata,
+ decisionPolicy: {
+ typeUrl: policyMetadata?.percentage && msgCreateGroupWithPercentagePolicy ||
+ msgCreateGroupWithThresholdPolicy,
+ value: policyMetadata?.percentage &&
+ PercentageDecisionPolicy.encode({
+ percentage: parseFloat(policyMetadata?.percentage || 0).toFixed(2).toString(),
+ windows: DecisionPolicyWindows.fromPartial({
+ votingPeriod: Duration.fromPartial({
+ seconds: Long.fromNumber(policyMetadata?.votingPeriod),
+ nanos: Number(policyMetadata?.votingPeriod)
+ }),
+ minExecutionPeriod: Duration.fromPartial({
+ seconds: Long.fromNumber(policyMetadata?.minExecPeriod),
+ nanos: Number(policyMetadata?.minExecPeriod)
+ }),
+ })
+ }).finish() ||
+ ThresholdDecisionPolicy.encode({
+ threshold: policyMetadata?.threshold?.toString(),
+ windows: DecisionPolicyWindows.fromPartial({
+ votingPeriod: Duration.fromPartial({
+ seconds: Long.fromNumber(policyMetadata?.votingPeriod),
+ nanos: Number(policyMetadata?.votingPeriod)
+ }),
+ minExecutionPeriod: Duration.fromPartial({
+ seconds: Long.fromNumber(policyMetadata?.minExecPeriod),
+ nanos: Number(policyMetadata?.minExecPeriod)
+ }),
+ })
+ }).finish()
+ },
+ }),
+ };
+
+ return obj;
+ } else {
+ const obj = {
+ typeUrl: msgCreateGroupWithPolicy,
+ value: MsgCreateGroupWithPolicy.fromPartial({
+ admin: admin,
+ members: members,
+ decisionPolicy: {},
+ groupMetadata: groupMetadata,
+ groupPolicyAsAdmin: policyAsAdmin,
+ groupPolicyMetadata: policyMetadata,
+ }),
+ };
+
+ return obj;
+ }
+ } catch (error) {
+ console.log('Error while creating the obj -- ', error)
+ throw error;
+ }
+}
+
+export function UpdateGroupMembers(
+ admin: any,
+ members: any,
+ groupId: any,
+) {
+
+ const obj = {
+ typeUrl: msgUpdateGroupMember,
+ value: MsgUpdateGroupMembers.fromPartial({
+ admin: admin,
+ memberUpdates: members,
+ groupId: groupId,
+ }),
+ };
+
+ return obj;
+}
+
+export function UpdateGroupAdmin(
+ admin: any,
+ groupId: any,
+ newAdmin: any,
+) {
+
+ const obj = {
+ typeUrl: msgUpdateGroupAdmin,
+ value: MsgUpdateGroupAdmin.fromPartial({
+ admin,
+ groupId,
+ newAdmin,
+ })
+ };
+
+ return obj;
+}
+
+export function UpdateGroupMetadata(
+ admin: any,
+ groupId: any,
+ metadata: any,
+) {
+
+ const obj = {
+ typeUrl: msgUpdateGroupMetadata,
+ value: MsgUpdateGroupMetadata.fromPartial({
+ admin,
+ groupId,
+ metadata,
+ })
+ };
+
+ return obj;
+}
+
+
+export function CreateGroupPolicy(
+ admin: any,
+ groupId: any,
+ policyMetadata: any,
+) {
+
+ const obj = {
+ typeUrl: msgAddGroupPolicy,
+ value: MsgCreateGroupPolicy.fromPartial({
+ admin: admin,
+ groupId,
+ decisionPolicy: {
+ typeUrl: policyMetadata?.percentage && msgCreateGroupWithPercentagePolicy ||
+ msgCreateGroupWithThresholdPolicy,
+ value: policyMetadata?.percentage &&
+ PercentageDecisionPolicy.encode({
+ percentage: parseFloat(policyMetadata?.percentage || 0).toFixed(2).toString(),
+ windows: DecisionPolicyWindows.fromPartial({
+ votingPeriod: Duration.fromPartial({
+ seconds: Long.fromNumber(policyMetadata?.votingPeriod),
+ nanos: Number(policyMetadata?.votingPeriod)
+ }),
+ minExecutionPeriod: Duration.fromPartial({
+ seconds: Long.fromNumber(policyMetadata?.minExecPeriod),
+ nanos: Number(policyMetadata?.minExecPeriod)
+ }),
+ })
+ }).finish() ||
+ ThresholdDecisionPolicy.encode({
+ threshold: policyMetadata?.threshold?.toString(),
+ windows: DecisionPolicyWindows.fromPartial({
+ votingPeriod: Duration.fromPartial({
+ seconds: Long.fromNumber(policyMetadata?.votingPeriod),
+ nanos: Number(policyMetadata?.votingPeriod)
+ }),
+ minExecutionPeriod: Duration.fromPartial({
+ seconds: Long.fromNumber(policyMetadata?.minExecPeriod),
+ nanos: Number(policyMetadata?.minExecPeriod)
+ }),
+ })
+ }).finish()
+ },
+ }),
+ };
+
+ return obj;
+}
+
+export function UpdateGroupPolicy(
+ admin: any,
+ groupPolicyAddress: any,
+ policyMetadata: any,
+) {
+
+ const obj = {
+ typeUrl: msgUpdateGroupPolicy,
+ value: MsgUpdateGroupPolicyDecisionPolicy.fromPartial({
+ admin: admin,
+ groupPolicyAddress: groupPolicyAddress,
+ decisionPolicy: {
+ typeUrl: policyMetadata?.percentage && msgCreateGroupWithPercentagePolicy ||
+ msgCreateGroupWithThresholdPolicy,
+ value: policyMetadata?.percentage &&
+ PercentageDecisionPolicy.encode({
+ percentage: parseFloat(policyMetadata?.percentage || 0).toFixed(2).toString(),
+ windows: DecisionPolicyWindows.fromPartial({
+ votingPeriod: Duration.fromPartial({
+ seconds: Long.fromNumber(policyMetadata?.votingPeriod),
+ nanos: Number(policyMetadata?.votingPeriod)
+ }),
+ minExecutionPeriod: Duration.fromPartial({
+ seconds: Long.fromNumber(policyMetadata?.minExecPeriod),
+ nanos: Number(policyMetadata?.minExecPeriod)
+ }),
+ })
+ }).finish() ||
+ ThresholdDecisionPolicy.encode({
+ threshold: policyMetadata?.threshold?.toString(),
+ windows: DecisionPolicyWindows.fromPartial({
+ votingPeriod: Duration.fromPartial({
+ seconds: Long.fromNumber(policyMetadata?.votingPeriod),
+ nanos: Number(policyMetadata?.votingPeriod)
+ }),
+ minExecutionPeriod: Duration.fromPartial({
+ seconds: Long.fromNumber(policyMetadata?.minExecPeriod),
+ nanos: Number(policyMetadata?.minExecPeriod)
+ }),
+ })
+ }).finish()
+ },
+ }),
+ };
+
+ return obj;
+}
+
+export function CreateLeaveGroupMember(
+ memberAddress: any,
+ groupId: any,
+) {
+
+ const obj = {
+ typeUrl: msgLeaveGroupMember,
+ value: MsgLeaveGroup.fromPartial({
+ groupId: groupId,
+ address: memberAddress
+ }),
+ };
+
+ return obj;
+}
+
+export function UpdatePolicyMetadata(
+ admin: any,
+ address: any,
+ metadata: any
+) {
+
+ const obj = {
+ typeUrl: msgUpatePolicyMetdata,
+ value: MsgUpdateGroupPolicyMetadata.fromPartial({
+ admin: admin,
+ groupPolicyAddress: address,
+ metadata: metadata
+ }),
+ };
+
+ return obj;
+}
+
+export function UpdatePolicyAdmin(
+ admin: any,
+ address: any,
+ newAdmin: any
+) {
+
+ const obj = {
+ typeUrl: msgUpatePolicyAdmin,
+ value: MsgUpdateGroupPolicyAdmin.fromPartial({
+ admin: admin,
+ groupPolicyAddress: address,
+ newAdmin: newAdmin
+ }),
+ };
+
+ return obj;
+}
+
diff --git a/src/txns/types.tsx b/src/txns/types.tsx
index 64d2bbd7e..dfe362359 100644
--- a/src/txns/types.tsx
+++ b/src/txns/types.tsx
@@ -1,5 +1,5 @@
export interface Msg {
- typeUrl: string
- value: any
+ typeUrl?: string
+ value?: any
}
\ No newline at end of file
diff --git a/src/utils/util.js b/src/utils/util.js
index effe8ea42..8634d15a1 100644
--- a/src/utils/util.js
+++ b/src/utils/util.js
@@ -1,4 +1,6 @@
import Chip from "@mui/material/Chip";
+import { Decimal } from "@cosmjs/math";
+
export function getTypeURLName(url) {
if (!url) {
@@ -201,3 +203,86 @@ export function mainValueToMinimum(amount, coinInfo) {
export function amountToMinimalValue(amount, coinInfo) {
return Number(amount) * 10 ** coinInfo.coinDecimals;
}
+
+export const setLocalStorage = (key, value, type) => {
+ if (type === 'object') {
+ localStorage.setItem(key, JSON.stringify(value))
+ return
+ } else {
+ localStorage.setItem(key, value);
+ return
+ }
+}
+
+export const getLocalStorage = (key, type) => {
+ if (type === 'object') {
+ let obj = JSON.parse(localStorage.getItem(key));
+ return obj
+ } else {
+ let value = localStorage.getItem(key)
+ return value;
+ }
+}
+
+export const getAmountObj = (amount = 0, chainInfo) => {
+ const amountInAtomics = Decimal.fromUserInput(
+ amount,
+ Number(chainInfo.config.currencies[0].coinDecimals)
+ ).atomics;
+
+ return {
+ amount: amountInAtomics,
+ denom: chainInfo.config.currencies[0].coinMinimalDenom
+ }
+}
+
+export const proposalStatus = {
+ 'PROPOSAL_EXECUTOR_RESULT_NOT_RUN': {
+ label: 'Result not run',
+ bgColor: '#e3bbbb',
+ textColor: '#ad3131',
+ color: 'error'
+ },
+ 'PROPOSAL_STATUS_UNSPECIFIED': {
+ label: 'Unspecified',
+ bgColor: '#e3bbbb',
+ textColor: '#ad3131',
+ color: 'error'
+ },
+ 'PROPOSAL_STATUS_SUBMITTED': {
+ label: 'Submitted',
+ bgColor: '#c5c9e3',
+ textColor: '#3049d3',
+ color: 'primary'
+ }, 'PROPOSAL_STATUS_ACCEPTED': {
+ label: 'Accepted',
+ bgColor: '#d8dfd3',
+ textColor: '#30b448',
+ color: 'success'
+ }, 'PROPOSAL_STATUS_REJECTED': {
+ label: 'Rejected',
+ bgColor: '#c5c9e3',
+ textColor: '#3049d3',
+ color: 'error'
+ }, 'PROPOSAL_STATUS_ABORTED': {
+ label: 'Aborted',
+ bgColor: '#c5c9e3',
+ textColor: '#3049d3',
+ color: 'error'
+ },
+ 'PROPOSAL_STATUS_WITHDRAWN': {
+ label: 'Withdrawn',
+ bgColor: '#e7d4ca',
+ textColor: '#e56a11'
+ },
+}
+
+export const ThresholdDecisionPolicy = `/cosmos.group.v1.ThresholdDecisionPolicy`;
+export const PercentageDecisionPolicy = `/cosmos.group.v1.PercentageDecisionPolicy`;
+
+export const PoliciesTypes = {
+ '/cosmos.group.v1.PercentageDecisionPolicy': 'Percentage Decision Policy',
+ '/cosmos.group.v1.ThresholdDecisionPolicy': 'Threshold Decision Policy'
+}
+
+