From e131f4ee3f0bbdb90a996a76a7ed47726ecf1abd Mon Sep 17 00:00:00 2001 From: fabiolalombardim Date: Sun, 9 Jul 2023 22:18:51 +0200 Subject: [PATCH 1/3] initial commit- banner added --- .../explorer/components/UserBalances.tsx | 11 +-- .../User/components/DelegationBanner.tsx | 75 +++++++++++++++++++ src/modules/explorer/pages/User/index.tsx | 11 +-- 3 files changed, 87 insertions(+), 10 deletions(-) create mode 100644 src/modules/explorer/pages/User/components/DelegationBanner.tsx diff --git a/src/modules/explorer/components/UserBalances.tsx b/src/modules/explorer/components/UserBalances.tsx index e54f70c5..3df01b86 100644 --- a/src/modules/explorer/components/UserBalances.tsx +++ b/src/modules/explorer/components/UserBalances.tsx @@ -75,7 +75,7 @@ const BalanceToken = styled(Typography)({ fontWeight: 300 }) -export const UserBalances: React.FC<{ daoId: string }> = ({ daoId, children }) => { +export const UserBalances: React.FC<{ daoId: string; setVoteWeight: any }> = ({ daoId, children, setVoteWeight }) => { const { account } = useTezos() const { data: dao, ledger } = useDAO(daoId) const theme = useTheme() @@ -104,6 +104,7 @@ export const UserBalances: React.FC<{ daoId: string }> = ({ daoId, children }) = userBalances.available.balance = "-" userBalances.pending.balance = "-" userBalances.staked.balance = "-" + setVoteWeight("-") return userBalances } @@ -111,9 +112,9 @@ export const UserBalances: React.FC<{ daoId: string }> = ({ daoId, children }) = userBalances.available.balance = userLedger.available_balance.dp(10, 1).toString() userBalances.pending.balance = userLedger.pending_balance.dp(10, 1).toString() userBalances.staked.balance = userLedger.staked.dp(10, 1).toString() - + setVoteWeight(userBalances.staked.balance) return userBalances - }, [account, ledger]) + }, [account, ledger, setVoteWeight]) const balancesList = Object.keys(balances).map(key => balances[key as keyof Balances]) @@ -147,10 +148,10 @@ export const UserBalances: React.FC<{ daoId: string }> = ({ daoId, children }) = ) } -export const UserBalancesBox: React.FC<{ daoId: string }> = ({ daoId }) => { +export const UserBalancesBox: React.FC<{ daoId: string; setVoteWeight: any }> = ({ daoId, setVoteWeight }) => { return ( - + ) } diff --git a/src/modules/explorer/pages/User/components/DelegationBanner.tsx b/src/modules/explorer/pages/User/components/DelegationBanner.tsx new file mode 100644 index 00000000..93813285 --- /dev/null +++ b/src/modules/explorer/pages/User/components/DelegationBanner.tsx @@ -0,0 +1,75 @@ +import React, { useState } from "react" +import { Grid, Theme, Typography, styled } from "@material-ui/core" +import { useDAO } from "services/services/dao/hooks/useDAO" + +export enum DelegationsType { + ACCEPTING_DELEGATION = "ACCEPTING_DELEGATION", + NOT_ACCEPTING_DELEGATION = "NOT_ACCEPTING_DELEGATION", + DELEGATING = "DELEGATING" +} + +interface DelegationStatus { + status: DelegationsType.ACCEPTING_DELEGATION | DelegationsType.NOT_ACCEPTING_DELEGATION | DelegationsType.DELEGATING +} + +const DelegationBox = styled(Grid)(({ theme }: { theme: Theme }) => ({ + minHeight: "178px", + padding: "46px 55px", + background: theme.palette.primary.main, + boxSizing: "border-box", + borderRadius: 8, + boxShadow: "none", + gap: 32 +})) + +const Subtitle = styled(Typography)({ + fontWeight: 200, + color: "#fff", + fontSize: 16 +}) + +const Balance = styled(Typography)({ + fontSize: 24, + fontWeight: 200 +}) + +export const Delegation: React.FC<{ voteWeight: any; daoId: string }> = ({ voteWeight, daoId }) => { + const { data: dao } = useDAO(daoId) + const [delegationStatus, setDelegationStatus] = useState(DelegationsType.NOT_ACCEPTING_DELEGATION) + + const matchTextToStatus = (value: DelegationsType) => { + switch (value) { + case DelegationsType.ACCEPTING_DELEGATION: + return "Accepting delegations" + case DelegationsType.NOT_ACCEPTING_DELEGATION: + return "Not currently accepting delegations or delegating" + case DelegationsType.DELEGATING: + return "Delegating to " + default: + return + } + } + + return ( + + + + Off-chain Delegation + + These settings only affect your participation in off-chain polls + + {dao && ( + + Voting Weight + + {voteWeight} {voteWeight !== "-" ? dao.data.token.symbol : ""} + + + )} + + Delegation Status + {matchTextToStatus(delegationStatus)} + + + ) +} diff --git a/src/modules/explorer/pages/User/index.tsx b/src/modules/explorer/pages/User/index.tsx index 08e332c7..3eba9b9e 100644 --- a/src/modules/explorer/pages/User/index.tsx +++ b/src/modules/explorer/pages/User/index.tsx @@ -1,7 +1,7 @@ import { Box, Grid, Theme, Typography, styled } from "@material-ui/core" import dayjs from "dayjs" import { useDAOID } from "modules/explorer/pages/DAO/router" -import React, { useCallback, useEffect, useMemo } from "react" +import React, { useCallback, useEffect, useMemo, useState } from "react" import { useHistory } from "react-router" import { useAgoraTopic } from "services/agora/hooks/useTopic" import { useTezos } from "services/beacon/hooks/useTezos" @@ -18,6 +18,7 @@ import { UserBalances } from "../../components/UserBalances" import { UserProfileName } from "../../components/UserProfileName" import { DropButton } from "../Proposals" import { usePolls } from "modules/lite/explorer/hooks/usePolls" +import { Delegation } from "./components/DelegationBanner" const ContentBlockItem = styled(Grid)({ padding: "35px 52px", @@ -103,14 +104,12 @@ export const User: React.FC = () => { const { data, cycleInfo } = useDAO(daoId) const { data: proposals } = useProposals(daoId) const history = useHistory() - const { data: activeProposals } = useProposals(daoId, ProposalStatus.ACTIVE) - const { data: executableProposals } = useProposals(daoId, ProposalStatus.EXECUTABLE) - const { data: expiredProposals } = useProposals(daoId, ProposalStatus.EXPIRED) const { data: executedProposals } = useProposals(daoId, ProposalStatus.EXECUTED) const { data: droppedProposals } = useProposals(daoId, ProposalStatus.DROPPED) const { mutate: unstakeFromAllProposals } = useUnstakeFromAllProposals() const polls = usePolls(data?.liteDAOData?._id) const pollsPosted = polls?.filter(p => p.author === account) + const [voteWeight, setVoteWeight] = useState() useEffect(() => { if (!account) { @@ -169,7 +168,7 @@ export const User: React.FC = () => { - + @@ -208,6 +207,8 @@ export const User: React.FC = () => { + + {proposalsCreated && cycleInfo && ( Date: Thu, 13 Jul 2023 22:23:37 +0200 Subject: [PATCH 2/3] modal & options added --- src/modules/common/SmallButton.tsx | 2 + .../explorer/components/ResponsiveDialog.tsx | 1 + .../User/components/DelegationBanner.tsx | 71 ++++++--- .../pages/User/components/DelegationModal.tsx | 146 ++++++++++++++++++ 4 files changed, 201 insertions(+), 19 deletions(-) create mode 100644 src/modules/explorer/pages/User/components/DelegationModal.tsx diff --git a/src/modules/common/SmallButton.tsx b/src/modules/common/SmallButton.tsx index 39d060bb..e3dd4d6b 100644 --- a/src/modules/common/SmallButton.tsx +++ b/src/modules/common/SmallButton.tsx @@ -7,6 +7,8 @@ export const SmallButton = styled(Button)({ "transition": ".15s ease-out", "textTransform": "capitalize", "borderRadius": 8, + "backgroundColor": "#81feb7 !important", + "color": "#1c1f23", "&$disabled": { boxShadow: "none" diff --git a/src/modules/explorer/components/ResponsiveDialog.tsx b/src/modules/explorer/components/ResponsiveDialog.tsx index eaddf628..7af33113 100644 --- a/src/modules/explorer/components/ResponsiveDialog.tsx +++ b/src/modules/explorer/components/ResponsiveDialog.tsx @@ -13,6 +13,7 @@ const TitleText = styled(Typography)(({ theme }) => ({ fontWeight: 550, lineHeight: ".80", textTransform: "capitalize", + fontSize: 20, [theme.breakpoints.down("sm")]: { fontSize: 18 } diff --git a/src/modules/explorer/pages/User/components/DelegationBanner.tsx b/src/modules/explorer/pages/User/components/DelegationBanner.tsx index 93813285..003447f4 100644 --- a/src/modules/explorer/pages/User/components/DelegationBanner.tsx +++ b/src/modules/explorer/pages/User/components/DelegationBanner.tsx @@ -1,6 +1,8 @@ -import React, { useState } from "react" +import React, { Fragment, useEffect, useState } from "react" import { Grid, Theme, Typography, styled } from "@material-ui/core" import { useDAO } from "services/services/dao/hooks/useDAO" +import { Edit } from "@material-ui/icons" +import { DelegationDialog } from "./DelegationModal" export enum DelegationsType { ACCEPTING_DELEGATION = "ACCEPTING_DELEGATION", @@ -8,10 +10,6 @@ export enum DelegationsType { DELEGATING = "DELEGATING" } -interface DelegationStatus { - status: DelegationsType.ACCEPTING_DELEGATION | DelegationsType.NOT_ACCEPTING_DELEGATION | DelegationsType.DELEGATING -} - const DelegationBox = styled(Grid)(({ theme }: { theme: Theme }) => ({ minHeight: "178px", padding: "46px 55px", @@ -33,26 +31,37 @@ const Balance = styled(Typography)({ fontWeight: 200 }) +export const matchTextToStatus = (value: DelegationsType | undefined) => { + switch (value) { + case DelegationsType.ACCEPTING_DELEGATION: + return "Accepting delegations" + case DelegationsType.NOT_ACCEPTING_DELEGATION: + return "Not currently accepting delegations or delegating" + case DelegationsType.DELEGATING: + return "Delegating to " + default: + return + } +} + export const Delegation: React.FC<{ voteWeight: any; daoId: string }> = ({ voteWeight, daoId }) => { const { data: dao } = useDAO(daoId) - const [delegationStatus, setDelegationStatus] = useState(DelegationsType.NOT_ACCEPTING_DELEGATION) + const [delegationStatus, setDelegationStatus] = useState( + DelegationsType.NOT_ACCEPTING_DELEGATION + ) + const [openModal, setOpenModal] = useState(false) - const matchTextToStatus = (value: DelegationsType) => { - switch (value) { - case DelegationsType.ACCEPTING_DELEGATION: - return "Accepting delegations" - case DelegationsType.NOT_ACCEPTING_DELEGATION: - return "Not currently accepting delegations or delegating" - case DelegationsType.DELEGATING: - return "Delegating to " - default: - return - } + const onCloseAction = () => { + setOpenModal(false) } + useEffect(() => { + console.log("se actualizó", delegationStatus) + }, [delegationStatus]) + return ( - + Off-chain Delegation @@ -67,9 +76,33 @@ export const Delegation: React.FC<{ voteWeight: any; daoId: string }> = ({ voteW )} - Delegation Status + + + Delegation Status + + + setOpenModal(true)} /> + setOpenModal(true)}> + Edit + + + {matchTextToStatus(delegationStatus)} + ) } diff --git a/src/modules/explorer/pages/User/components/DelegationModal.tsx b/src/modules/explorer/pages/User/components/DelegationModal.tsx new file mode 100644 index 00000000..b2859e71 --- /dev/null +++ b/src/modules/explorer/pages/User/components/DelegationModal.tsx @@ -0,0 +1,146 @@ +import { Grid, Radio, TextField, Typography, styled } from "@material-ui/core" +import React, { useEffect, useState } from "react" +import { DelegationsType, matchTextToStatus } from "./DelegationBanner" +import { ResponsiveDialog } from "modules/explorer/components/ResponsiveDialog" +import { SmallButton } from "modules/common/SmallButton" + +const AddressTextField = styled(TextField)({ + "backgroundColor": "#2f3438", + "borderRadius": 8, + "height": 56, + "padding": "0px 24px", + "alignItems": "flex-start", + "boxSizing": "border-box", + "justifyContent": "center", + "display": "flex", + "& .MuiInputBase-root": { + "width": "100%", + "& input": { + textAlign: "initial" + } + } +}) + +export enum ActionTypes { + ACCEPT_DELEGATIONS = "ACCEPT_DELEGATIONS", + DELEGATE = "DELEGATE", + CHANGE_DELEGATE = "CHANGE_DELEGATE", + STOP_ACCEPTING_DELEGATIONS = "STOP_ACCEPTING_DELEGATIONS", + STOP_DELEGATING = "STOP_DELEGATING" +} + +const matchTextToAction = (value: ActionTypes) => { + switch (value) { + case ActionTypes.ACCEPT_DELEGATIONS: + return "Accept Delegations" + case ActionTypes.DELEGATE: + return "Delegate" + case ActionTypes.CHANGE_DELEGATE: + return "Change Delegate" + case ActionTypes.STOP_ACCEPTING_DELEGATIONS: + return "Stop Accepting Delegations" + case ActionTypes.STOP_DELEGATING: + return "Stop Delegating" + default: + return + } +} + +export const DelegationDialog: React.FC<{ + open: boolean + onClose: () => void + status: DelegationsType | undefined + setDelegationStatus: (value: DelegationsType | undefined) => void +}> = ({ status, onClose, open, setDelegationStatus }) => { + const [options, setOptions] = useState([]) + const [selectedOption, setSelectedOption] = useState() + + useEffect(() => { + getOptionsByStatus(status) + }, [status]) + + const closeDialog = () => { + setSelectedOption(undefined) + onClose() + } + + const saveInfo = () => { + updateStatus() + closeDialog() + } + + const updateStatus = () => { + if (selectedOption === ActionTypes.DELEGATE || selectedOption === ActionTypes.CHANGE_DELEGATE) { + return setDelegationStatus(DelegationsType.DELEGATING) + } + if (selectedOption === ActionTypes.STOP_ACCEPTING_DELEGATIONS || ActionTypes.STOP_DELEGATING) { + return setDelegationStatus(DelegationsType.NOT_ACCEPTING_DELEGATION) + } + if (selectedOption === ActionTypes.ACCEPT_DELEGATIONS) { + return setDelegationStatus(DelegationsType.ACCEPTING_DELEGATION) + } + } + + const getOptionsByStatus = (status: DelegationsType | undefined) => { + switch (status) { + case DelegationsType.NOT_ACCEPTING_DELEGATION: + const optionsOne = [ActionTypes.ACCEPT_DELEGATIONS, ActionTypes.DELEGATE] + setOptions(optionsOne) + break + case DelegationsType.ACCEPTING_DELEGATION: + const optionsTwo = [ActionTypes.STOP_ACCEPTING_DELEGATIONS, ActionTypes.DELEGATE] + setOptions(optionsTwo) + break + case DelegationsType.DELEGATING: + const optionsThree = [ActionTypes.CHANGE_DELEGATE, ActionTypes.STOP_DELEGATING, ActionTypes.ACCEPT_DELEGATIONS] + setOptions(optionsThree) + break + } + } + + return ( + + + + Current Status + + {matchTextToStatus(status)} + + + + {options.map(item => { + return ( + <> + + {matchTextToAction(item)} + setSelectedOption(e.target.value)} + value={item} + name="radio-buttons" + inputProps={{ "aria-label": "A" }} + /> + {item === selectedOption && + (selectedOption === ActionTypes.DELEGATE || selectedOption === ActionTypes.CHANGE_DELEGATE) ? ( + + ) : null} + + + ) + })} + + + Submit + + + + ) +} From db3003338fd7024944a37015472c39a51b3526b2 Mon Sep 17 00:00:00 2001 From: Manank Patni Date: Sun, 16 Jul 2023 04:34:42 +0530 Subject: [PATCH 3/3] Add interaction with contracts Signed-off-by: Manank Patni --- .../explorer/components/UserBalances.tsx | 10 ++- .../User/components/DelegationBanner.tsx | 49 +++++++++++--- .../pages/User/components/DelegationModal.tsx | 49 ++++++++++---- src/modules/explorer/pages/User/index.tsx | 5 +- src/services/bakingBad/delegations/index.ts | 64 ++++++++++++++++++- src/services/bakingBad/delegations/types.ts | 16 +++++ .../token/hooks/useDelegationStatus.ts | 27 ++++++++ .../token/hooks/useDelegationVoteWeight.ts | 25 ++++++++ .../contracts/token/hooks/useTokenDelegate.ts | 63 ++++++++++++++++++ src/services/contracts/token/index.ts | 19 ++++++ 10 files changed, 297 insertions(+), 30 deletions(-) create mode 100644 src/services/contracts/token/hooks/useDelegationStatus.ts create mode 100644 src/services/contracts/token/hooks/useDelegationVoteWeight.ts create mode 100644 src/services/contracts/token/hooks/useTokenDelegate.ts diff --git a/src/modules/explorer/components/UserBalances.tsx b/src/modules/explorer/components/UserBalances.tsx index 3df01b86..6022b413 100644 --- a/src/modules/explorer/components/UserBalances.tsx +++ b/src/modules/explorer/components/UserBalances.tsx @@ -75,7 +75,7 @@ const BalanceToken = styled(Typography)({ fontWeight: 300 }) -export const UserBalances: React.FC<{ daoId: string; setVoteWeight: any }> = ({ daoId, children, setVoteWeight }) => { +export const UserBalances: React.FC<{ daoId: string }> = ({ daoId, children }) => { const { account } = useTezos() const { data: dao, ledger } = useDAO(daoId) const theme = useTheme() @@ -104,7 +104,6 @@ export const UserBalances: React.FC<{ daoId: string; setVoteWeight: any }> = ({ userBalances.available.balance = "-" userBalances.pending.balance = "-" userBalances.staked.balance = "-" - setVoteWeight("-") return userBalances } @@ -112,9 +111,8 @@ export const UserBalances: React.FC<{ daoId: string; setVoteWeight: any }> = ({ userBalances.available.balance = userLedger.available_balance.dp(10, 1).toString() userBalances.pending.balance = userLedger.pending_balance.dp(10, 1).toString() userBalances.staked.balance = userLedger.staked.dp(10, 1).toString() - setVoteWeight(userBalances.staked.balance) return userBalances - }, [account, ledger, setVoteWeight]) + }, [account, ledger]) const balancesList = Object.keys(balances).map(key => balances[key as keyof Balances]) @@ -148,10 +146,10 @@ export const UserBalances: React.FC<{ daoId: string; setVoteWeight: any }> = ({ ) } -export const UserBalancesBox: React.FC<{ daoId: string; setVoteWeight: any }> = ({ daoId, setVoteWeight }) => { +export const UserBalancesBox: React.FC<{ daoId: string }> = ({ daoId }) => { return ( - + ) } diff --git a/src/modules/explorer/pages/User/components/DelegationBanner.tsx b/src/modules/explorer/pages/User/components/DelegationBanner.tsx index 003447f4..dcc3775a 100644 --- a/src/modules/explorer/pages/User/components/DelegationBanner.tsx +++ b/src/modules/explorer/pages/User/components/DelegationBanner.tsx @@ -3,6 +3,11 @@ import { Grid, Theme, Typography, styled } from "@material-ui/core" import { useDAO } from "services/services/dao/hooks/useDAO" import { Edit } from "@material-ui/icons" import { DelegationDialog } from "./DelegationModal" +import { useDelegationStatus } from "services/contracts/token/hooks/useDelegationStatus" +import { useTezos } from "services/beacon/hooks/useTezos" +import { useDelegationVoteWeight } from "services/contracts/token/hooks/useDelegationVoteWeight" +import BigNumber from "bignumber.js" +import { parseUnits } from "services/contracts/utils" export enum DelegationsType { ACCEPTING_DELEGATION = "ACCEPTING_DELEGATION", @@ -44,20 +49,39 @@ export const matchTextToStatus = (value: DelegationsType | undefined) => { } } -export const Delegation: React.FC<{ voteWeight: any; daoId: string }> = ({ voteWeight, daoId }) => { +export const Delegation: React.FC<{ daoId: string }> = ({ daoId }) => { const { data: dao } = useDAO(daoId) - const [delegationStatus, setDelegationStatus] = useState( - DelegationsType.NOT_ACCEPTING_DELEGATION - ) + const { network, tezos, account, connect } = useTezos() + + const { data: delegatedTo } = useDelegationStatus(dao?.data.token.contract) + const [delegationStatus, setDelegationStatus] = useState(DelegationsType.NOT_ACCEPTING_DELEGATION) const [openModal, setOpenModal] = useState(false) + const { data: delegateVoteBalances } = useDelegationVoteWeight(dao?.data.token.contract) + const [voteWeight, setVoteWeight] = useState(new BigNumber(0)) + console.log("voteWeight: ", voteWeight.toString()) const onCloseAction = () => { setOpenModal(false) } useEffect(() => { - console.log("se actualizó", delegationStatus) - }, [delegationStatus]) + if (delegatedTo === account) { + setDelegationStatus(DelegationsType.ACCEPTING_DELEGATION) + } else if (delegatedTo && delegatedTo !== account) { + setDelegationStatus(DelegationsType.DELEGATING) + } else { + setDelegationStatus(DelegationsType.NOT_ACCEPTING_DELEGATION) + } + }, [delegatedTo, account]) + + useEffect(() => { + let totalVoteWeight = new BigNumber(0) + delegateVoteBalances?.forEach(delegatedVote => { + const balance = new BigNumber(delegatedVote.balance) + totalVoteWeight = totalVoteWeight.plus(balance) + }) + setVoteWeight(totalVoteWeight) + }, [delegateVoteBalances]) return ( @@ -71,7 +95,11 @@ export const Delegation: React.FC<{ voteWeight: any; daoId: string }> = ({ voteW Voting Weight - {voteWeight} {voteWeight !== "-" ? dao.data.token.symbol : ""} + {!voteWeight || voteWeight.eq(new BigNumber(0)) ? ( + "-" + ) : ( + <>{`${parseUnits(voteWeight, dao.data.token.decimals).toString()} ${dao.data.token.symbol}`} + )} )} @@ -95,13 +123,18 @@ export const Delegation: React.FC<{ voteWeight: any; daoId: string }> = ({ voteW - {matchTextToStatus(delegationStatus)} + + {matchTextToStatus(delegationStatus)} + {delegationStatus === DelegationsType.DELEGATING ? delegatedTo : null} + ) diff --git a/src/modules/explorer/pages/User/components/DelegationModal.tsx b/src/modules/explorer/pages/User/components/DelegationModal.tsx index b2859e71..d5b10b82 100644 --- a/src/modules/explorer/pages/User/components/DelegationModal.tsx +++ b/src/modules/explorer/pages/User/components/DelegationModal.tsx @@ -3,6 +3,10 @@ import React, { useEffect, useState } from "react" import { DelegationsType, matchTextToStatus } from "./DelegationBanner" import { ResponsiveDialog } from "modules/explorer/components/ResponsiveDialog" import { SmallButton } from "modules/common/SmallButton" +import { useTokenDelegate } from "services/contracts/token/hooks/useTokenDelegate" +import { useDAO } from "services/services/dao/hooks/useDAO" +import { useDAOID } from "../../DAO/router" +import { useTezos } from "services/beacon/hooks/useTezos" const AddressTextField = styled(TextField)({ "backgroundColor": "#2f3438", @@ -50,10 +54,17 @@ export const DelegationDialog: React.FC<{ open: boolean onClose: () => void status: DelegationsType | undefined - setDelegationStatus: (value: DelegationsType | undefined) => void -}> = ({ status, onClose, open, setDelegationStatus }) => { + setDelegationStatus: (value: DelegationsType) => void + delegationStatus: DelegationsType + delegatedTo: string | null | undefined +}> = ({ status, onClose, open, setDelegationStatus, delegationStatus, delegatedTo }) => { const [options, setOptions] = useState([]) const [selectedOption, setSelectedOption] = useState() + const { mutate: delegateToken } = useTokenDelegate() + const daoId = useDAOID() + const { data, cycleInfo } = useDAO(daoId) + const { tezos, connect, network, account } = useTezos() + const [newDelegate, setNewDelegate] = useState("") useEffect(() => { getOptionsByStatus(status) @@ -71,13 +82,20 @@ export const DelegationDialog: React.FC<{ const updateStatus = () => { if (selectedOption === ActionTypes.DELEGATE || selectedOption === ActionTypes.CHANGE_DELEGATE) { - return setDelegationStatus(DelegationsType.DELEGATING) - } - if (selectedOption === ActionTypes.STOP_ACCEPTING_DELEGATIONS || ActionTypes.STOP_DELEGATING) { - return setDelegationStatus(DelegationsType.NOT_ACCEPTING_DELEGATION) - } - if (selectedOption === ActionTypes.ACCEPT_DELEGATIONS) { - return setDelegationStatus(DelegationsType.ACCEPTING_DELEGATION) + if (newDelegate && data?.data.token.contract) { + delegateToken({ tokenAddress: data?.data.token.contract, delegateAddress: newDelegate }) + } + } else if ( + selectedOption === ActionTypes.STOP_ACCEPTING_DELEGATIONS || + selectedOption === ActionTypes.STOP_DELEGATING + ) { + if (data?.data.token.contract) { + delegateToken({ tokenAddress: data?.data.token.contract, delegateAddress: null }) + } + } else if (selectedOption === ActionTypes.ACCEPT_DELEGATIONS) { + if (data?.data.token.contract && account) { + delegateToken({ tokenAddress: data?.data.token.contract, delegateAddress: account }) + } } } @@ -88,7 +106,7 @@ export const DelegationDialog: React.FC<{ setOptions(optionsOne) break case DelegationsType.ACCEPTING_DELEGATION: - const optionsTwo = [ActionTypes.STOP_ACCEPTING_DELEGATIONS, ActionTypes.DELEGATE] + const optionsTwo = [ActionTypes.STOP_ACCEPTING_DELEGATIONS] setOptions(optionsTwo) break case DelegationsType.DELEGATING: @@ -104,7 +122,7 @@ export const DelegationDialog: React.FC<{ Current Status - {matchTextToStatus(status)} + {matchTextToStatus(status)} {delegationStatus === DelegationsType.DELEGATING ? delegatedTo : null} @@ -130,7 +148,14 @@ export const DelegationDialog: React.FC<{ /> {item === selectedOption && (selectedOption === ActionTypes.DELEGATE || selectedOption === ActionTypes.CHANGE_DELEGATE) ? ( - + { + setNewDelegate(e.target.value) + }} + type="text" + placeholder="Enter Address" + InputProps={{ disableUnderline: true }} + /> ) : null} diff --git a/src/modules/explorer/pages/User/index.tsx b/src/modules/explorer/pages/User/index.tsx index 3eba9b9e..ec243953 100644 --- a/src/modules/explorer/pages/User/index.tsx +++ b/src/modules/explorer/pages/User/index.tsx @@ -109,7 +109,6 @@ export const User: React.FC = () => { const { mutate: unstakeFromAllProposals } = useUnstakeFromAllProposals() const polls = usePolls(data?.liteDAOData?._id) const pollsPosted = polls?.filter(p => p.author === account) - const [voteWeight, setVoteWeight] = useState() useEffect(() => { if (!account) { @@ -168,7 +167,7 @@ export const User: React.FC = () => { - + @@ -208,7 +207,7 @@ export const User: React.FC = () => { - + {proposalsCreated && cycleInfo && ( { const url = `https://api.${networkNameMap[network]}.tzkt.io/v1/operations/delegations?sender=${daoAddress}&status=applied` @@ -17,3 +18,64 @@ export const getLatestDelegation = async (daoAddress: string, network: Network) return resultingDelegations[0] } + +export const getTokenDelegation = async (tokenAddress: string, account: string, network: Network) => { + const url = `https://api.${networkNameMap[network]}.tzkt.io/v1/contracts/${tokenAddress}/bigmaps/delegates/keys?key.eq=${account}&active=true` + const response = await fetch(url) + + if (!response.ok) { + throw new Error("Failed to fetch token delegations from TZKT API") + } + + const resultingDelegations: TokenDelegationDTO[] = await response.json() + + if (resultingDelegations.length === 0) { + return null + } + + const delegatedTo = resultingDelegations[0].value + + return delegatedTo +} + +export const getTokenDelegationVoteWeight = async (tokenAddress: string, account: string, network: Network) => { + const selfBalance = await getUserTokenBalance(account, network, tokenAddress) + + if (!selfBalance) { + throw new Error("Could not fetch delegate token balance from the TZKT API") + } + + const url = `https://api.${networkNameMap[network]}.tzkt.io/v1/contracts/${tokenAddress}/bigmaps/delegates/keys?value.eq=${account}&active=true` + const response = await fetch(url) + + if (!response.ok) { + throw new Error("Failed to fetch token delegations from TZKT API") + } + + const resultingDelegations: TokenDelegationDTO[] = await response.json() + + const delegateBalance: UserDelegateBalance = { + address: account, + balance: selfBalance + } + + if (resultingDelegations.length === 0) { + return [delegateBalance] + } + + const delegatedAddressBalances: UserDelegateBalance[] = [] + + await Promise.all( + resultingDelegations.map(async del => { + const balance = await getUserTokenBalance(del.key, network, tokenAddress) + if (balance) { + delegatedAddressBalances.push({ + address: del.key, + balance: balance + }) + } + }) + ) + + return delegatedAddressBalances +} diff --git a/src/services/bakingBad/delegations/types.ts b/src/services/bakingBad/delegations/types.ts index 21b8ffea..a22e35a3 100644 --- a/src/services/bakingBad/delegations/types.ts +++ b/src/services/bakingBad/delegations/types.ts @@ -23,3 +23,19 @@ export interface DelegationDTO { } status: string } + +export interface TokenDelegationDTO { + id: number + active: boolean + hash: string + key: string + value: string + firstLevel: number + lastLevel: number + updates: number +} + +export interface UserDelegateBalance { + address: string + balance: string +} diff --git a/src/services/contracts/token/hooks/useDelegationStatus.ts b/src/services/contracts/token/hooks/useDelegationStatus.ts new file mode 100644 index 00000000..9158a87a --- /dev/null +++ b/src/services/contracts/token/hooks/useDelegationStatus.ts @@ -0,0 +1,27 @@ +import { useQuery } from "react-query" +import { getTokenDelegation } from "services/bakingBad/delegations" +import { getDAOBalances } from "services/bakingBad/tokenBalances" +import { useTezos } from "services/beacon/hooks/useTezos" + +export const useDelegationStatus = (tokenAddress: string | undefined) => { + const { network, tezos, account, connect } = useTezos() + + const { data, ...rest } = useQuery( + ["tokenDelegations", tokenAddress], + async () => { + if (!tokenAddress) { + return null + } else { + return await getTokenDelegation(tokenAddress, account, network) + } + }, + { + enabled: !!tokenAddress + } + ) + + return { + data, + ...rest + } +} diff --git a/src/services/contracts/token/hooks/useDelegationVoteWeight.ts b/src/services/contracts/token/hooks/useDelegationVoteWeight.ts new file mode 100644 index 00000000..93a26dff --- /dev/null +++ b/src/services/contracts/token/hooks/useDelegationVoteWeight.ts @@ -0,0 +1,25 @@ +import { useQuery } from "react-query" +import { getTokenDelegationVoteWeight } from "services/bakingBad/delegations" +import { UserDelegateBalance } from "services/bakingBad/delegations/types" +import { useTezos } from "services/beacon/hooks/useTezos" + +export const useDelegationVoteWeight = (tokenAddress: string | undefined) => { + const { network, account } = useTezos() + + const { data, ...rest } = useQuery( + ["delegationVoteWeight", tokenAddress], + async () => { + if (tokenAddress) { + return await getTokenDelegationVoteWeight(tokenAddress, account, network) + } + }, + { + enabled: !!tokenAddress + } + ) + + return { + data, + ...rest + } +} diff --git a/src/services/contracts/token/hooks/useTokenDelegate.ts b/src/services/contracts/token/hooks/useTokenDelegate.ts new file mode 100644 index 00000000..415d5d46 --- /dev/null +++ b/src/services/contracts/token/hooks/useTokenDelegate.ts @@ -0,0 +1,63 @@ +import { useNotification } from "modules/common/hooks/useNotification" +import { useMutation, useQueryClient } from "react-query" +import { useTezos } from "services/beacon/hooks/useTezos" +import { networkNameMap } from "../../../bakingBad" +import { setDelegate } from ".." +import { WalletOperation } from "@taquito/taquito" + +export const useTokenDelegate = () => { + const queryClient = useQueryClient() + const openNotification = useNotification() + const { network, tezos, account, connect } = useTezos() + + return useMutation( + async params => { + const { tokenAddress, delegateAddress } = params + // const { key: flushNotification, closeSnackbar: closeFlushNotification } = openNotification({ + // message: "Please sign the transaction to flush", + // persist: true, + // variant: "info" + // }) + try { + let tezosToolkit = tezos + + if (!account) { + tezosToolkit = await connect() + } + + const tx = await setDelegate({ + tokenAddress, + tezos: tezosToolkit, + delegateAddress + }) + // closeFlushNotification(flushNotification) + + if (!tx) { + throw new Error(`Error making delegate transaction`) + } + + openNotification({ + message: "Delegate transaction confirmed!", + autoHideDuration: 5000, + variant: "success", + detailsLink: `https://${networkNameMap[network]}.tzkt.io/` + (tx as WalletOperation).opHash + }) + + return tx + } catch (e: any) { + // closeFlushNotification(flushNotification) + openNotification({ + message: (e as Error).message, + variant: "error", + autoHideDuration: 5000 + }) + return new Error((e as Error).message) + } + }, + { + onSuccess: () => { + queryClient.resetQueries() + } + } + ) +} diff --git a/src/services/contracts/token/index.ts b/src/services/contracts/token/index.ts index b235a152..cd6acfe6 100644 --- a/src/services/contracts/token/index.ts +++ b/src/services/contracts/token/index.ts @@ -3,6 +3,7 @@ import BigNumber from "bignumber.js" import { TokenContractParams } from "modules/creator/deployment/state/types" import { formatUnits } from "../utils" import fa2_single_asset_delegated from "./assets/fa2_single_asset_delegated" +import { getContract } from "../baseDAO" interface Tezos { tezos: TezosToolkit @@ -89,6 +90,24 @@ export const deployTokenContract = async ({ const contract = await c.contract() return contract + } catch (e) { + console.error(e) + return e + } +} + +export const setDelegate = async ({ + tokenAddress, + delegateAddress, + tezos +}: { + tokenAddress: string + delegateAddress: string | null + tezos: TezosToolkit +}) => { + try { + const contract = await getContract(tezos, tokenAddress) + return contract.methods.set_delegate(delegateAddress).send() } catch (e) { console.error(e) }