diff --git a/src/modules/explorer/components/ArbitraryContractInteractionForm.tsx b/src/modules/explorer/components/ArbitraryContractInteractionForm.tsx index 0842eea49..452fb679b 100644 --- a/src/modules/explorer/components/ArbitraryContractInteractionForm.tsx +++ b/src/modules/explorer/components/ArbitraryContractInteractionForm.tsx @@ -27,12 +27,12 @@ import ProposalExecuteForm from "./ProposalExecuteForm" import { Expr, MichelsonData } from "@taquito/michel-codec" import { getContract } from "services/contracts/baseDAO" import { TezosToolkit } from "@taquito/taquito" +import { BigNumber } from "bignumber.js" import { useDAO } from "services/services/dao/hooks/useDAO" import { useDAOID } from "../pages/DAO/router" import AppConfig from "config" import { Link } from "react-router-dom" import { Button } from "components/ui/Button" -import { ResponsiveDialog } from "./ResponsiveDialog" import { useNotification } from "modules/common/hooks/useNotification" // Base ACI Lambda @@ -250,13 +250,22 @@ const ContractInteractionForm = ({ } if (!isAciDeployerDeployed && !isLoading) { + // the DAO has no aci_executor handler yet: explain and point to the add-function proposal (one dialog, closable) return ( -
- We need to deploy the ACI Deployer Contract - - Deploy ACI Deployer Contract - -
+ + + In order to use open-ended Contract Calls, the DAO contract must be amended. + + + If you have the minimum amount of tokens for the proposal fee, click Submit to create a proposal for adding + the Contract Call capability. Once that proposal is executed, this form becomes available. + + + + + + + ) } @@ -474,11 +483,16 @@ const ContractInteractionForm = ({ }) console.log("Lambda Param", param) + // the form takes tez, the lambda pushes mutez + const amountMutez = new BigNumber(values.amount || 0) + .multipliedBy(1_000_000) + .integerValue(BigNumber.ROUND_DOWN) + .toNumber() const lambda = generateExecuteContractMichelson("1.0.0", { address: values.destination_contract_address, entrypoint, type, - amount: values.amount, + amount: amountMutez, param }) @@ -534,7 +548,6 @@ export const ArbitraryContractInteractionForm: React.FC<{ showHeader: (state: boolean) => void }> = ({ daoLambdas, showHeader }) => { const daoId = useDAOID() - const [aciProposalOpen, setAciProposalOpen] = useState(false) const isInvalidKtOrTzAddress = (address: string) => validateContractAddress(address) !== 3 const initialValue: ACIValues = { @@ -578,8 +591,6 @@ export const ArbitraryContractInteractionForm: React.FC<{ console.log({ daoLambdas }) }, [daoLambdas]) - const isAciDeployerDeployed = daoLambdas?.find((lambda: any) => lambda.key === AppConfig.ACI.EXECUTOR_FUNCTION_NAME) - return ( - {daoLambdas !== undefined && ( - setAciProposalOpen(false)} - title={"Proposal to Enable Arbitrary Contract Interaction"} - template="sm" - > - In order to use open-ended Contract Calls, the DAO contract must be amended. - - If you have the minimum amount of tokens for the proposal fee, click Submit to create a proposal for - adding the ACI capability. - - - - - - )} ) }} diff --git a/src/modules/explorer/components/VoteDialog.tsx b/src/modules/explorer/components/VoteDialog.tsx index 252e2584e..eef8b4bcb 100644 --- a/src/modules/explorer/components/VoteDialog.tsx +++ b/src/modules/explorer/components/VoteDialog.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useMemo } from "react" import { Grid, Typography, TextField, Button, useTheme, styled } from "@mui/material" -import { useParams } from "react-router-dom" +import { Link, useParams } from "react-router-dom" import { useVote } from "services/contracts/baseDAO/hooks/useVote" import BigNumber from "bignumber.js" import { useDAO } from "services/services/dao/hooks/useDAO" @@ -13,6 +13,8 @@ import { ProposalFormInput } from "./ProposalFormInput" import { getUserTokenBalance } from "services/bakingBad/tokenBalances" import { useTezos } from "services/beacon/hooks/useTezos" import { useTezosBalance } from "services/contracts/baseDAO/hooks/useTezosBalance" +import { useProposals } from "services/services/dao/hooks/useProposals" +import { ProposalStatus } from "services/services/dao/mappers/proposal/types" const CustomLabelsContainer = styled(Grid)({ marginBottom: 12 @@ -31,6 +33,14 @@ const CustomMaxLabel = styled(Typography)({ marginLeft: 12 }) +const HintText = styled(Typography)(({ theme }) => ({ + "fontSize": 14, + "marginTop": 12, + "color": theme.palette.text.primary, + "opacity": 0.75, + "& a": { color: "inherit", textDecoration: "underline" } +})) + const ErrorText = styled(Typography)({ fontSize: 13, marginTop: 8, @@ -82,6 +92,17 @@ export const VoteDialog: React.FC<{ }, [amount, dao, mutate, onClose, proposalId, support, validate]) const { account } = useTezos() + // votes stay staked after a proposal is executed or dropped until the holder releases them + const { data: executedProposals } = useProposals(daoId, ProposalStatus.EXECUTED) + const { data: droppedProposals } = useProposals(daoId, ProposalStatus.DROPPED) + const releasableStake = useMemo( + () => + (executedProposals ?? []).concat(droppedProposals ?? []).reduce((acc, finishedProposal) => { + const vote = finishedProposal.voters.find(voter => voter.address.toLowerCase() === account?.toLowerCase()) + return vote && vote.staked ? acc.plus(vote.value) : acc + }, new BigNumber(0)), + [executedProposals, droppedProposals, account] + ) useMemo(() => { const found = ledger?.find(l => l.holder.address.toLowerCase() === account.toLowerCase()) @@ -157,6 +178,12 @@ export const VoteDialog: React.FC<{ /> {error ? Amount cannot be greater than max available : null} + {releasableStake.gt(0) ? ( + + {releasableStake.toFormat()} {dao?.data.token.symbol} are still staked on finished proposals. Release + them with Unstake on the User page to vote with them. + + ) : null} diff --git a/src/modules/explorer/pages/ProposalDetails/index.tsx b/src/modules/explorer/pages/ProposalDetails/index.tsx index 70c70b507..c34e1eb4d 100644 --- a/src/modules/explorer/pages/ProposalDetails/index.tsx +++ b/src/modules/explorer/pages/ProposalDetails/index.tsx @@ -160,6 +160,19 @@ const formatConfig = { trimMantissa: true } +const proposalKindLabel = (proposal: Proposal | undefined): string => { + if (!proposal || !(proposal instanceof LambdaProposal)) { + return "Treasury Proposal" + } + const { lambdaType, lambdaHandler } = proposal.metadata + if (lambdaType === "add_handler") return "Add Function Proposal" + if (lambdaType === "remove_handler") return "Remove Function Proposal" + if (lambdaType === "execute_handler") { + return lambdaHandler?.handler_name === "aci_executor" ? "Contract Call Proposal" : "Execute Function Proposal" + } + return "Proposal" +} + export const ProposalDetails: React.FC = () => { const { proposalId } = useParams<{ proposalId: string @@ -333,7 +346,7 @@ export const ProposalDetails: React.FC = () => { - Treasury Proposal • Created by + {proposalKindLabel(proposal)} • Created by {proposal && cycleInfo && ( { - const [isLoading, setIsLoading] = useState(true) - const [proposalData, setProposalData] = useState([]) - const { data: daoData, isLoading: isLoadingDAO, error, cycleInfo } = useDAO(contractAddress) + const { data: daoData, error, cycleInfo } = useDAO(contractAddress) const queryResults = useQuery({ - queryKey: ["proposals", contractAddress, status], - queryFn: async () => { - const dao = daoData as BaseDAO - const proposals = await getProposals(dao) - - if (!status) { - return proposals - } - - return proposals.filter( - proposalData => proposalData.getStatus((cycleInfo as CycleInfo).currentLevel).status === status - ) - }, + queryKey: ["proposals", contractAddress], + queryFn: async () => getProposals(daoData as BaseDAO), refetchInterval: 30000, - enabled: !!daoData && !!cycleInfo + enabled: !!daoData }) - useEffect(() => { - if (queryResults.data) { - setProposalData(queryResults.data) + const data = useMemo(() => { + const proposals = queryResults.data ?? [] + if (!status) { + return proposals + } + if (!cycleInfo) { + return [] } - }, [queryResults.data]) + return proposals.filter(proposal => proposal.getStatus((cycleInfo as CycleInfo).currentLevel).status === status) + }, [queryResults.data, status, cycleInfo]) return { - data: proposalData, - isLoading: isLoading || queryResults.isLoading, + data, + isLoading: queryResults.isLoading, error: error || queryResults.error } }