Skip to content

Commit

Permalink
feat: now user can create safe proposal for payout
Browse files Browse the repository at this point in the history
  • Loading branch information
fonstack committed Sep 19, 2024
1 parent b4ea83e commit 85337f9
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 1 deletion.
2 changes: 2 additions & 0 deletions packages/web/src/languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1271,6 +1271,8 @@
"paymentPerPoint": "Payment per point",
"maxPercentagePerPoint": "Max. percentage per point (%)",
"maxPercentagePerPointPlaceholder": "Max. % of the vault allocated to one point",
"creatingSafeProposal": "Creating safe proposal",
"createProposalOnSafe": "Create proposal on Safe",
"payoutStatusDescriptions": {
"pending": "Your payout multisig transaction was created please share this page between other committee member to sign it. Once all signatures are collected you will be able to execute the payout transaction.",
"readyToExecute": "The needed signatures were collected, you can now execute the payout transaction. Please be aware that the payout can only be executed during safety period.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {
HATSVaultV2_abi,
HATSVaultV3ClaimsManager_abi,
PayoutStatus,
getBaseSafeAppUrl,
getGnosisChainPrefixByChainId,
getSafeHomeLink,
getVaultInfoWithCommittee,
isAddressAMultisigMember,
Expand Down Expand Up @@ -31,14 +33,15 @@ import { useVaultSafeInfo } from "hooks/vaults/useVaultSafeInfo";
import { RoutePaths } from "navigation";
import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "react-router-dom";
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
import { switchNetworkAndValidate } from "utils/switchNetwork.utils";
import { useAccount, useNetwork, useWaitForTransaction } from "wagmi";
import { PayoutsWelcome } from "../PayoutsListPage/PayoutsWelcome";
import { PayoutCard, SignerCard, SinglePayoutAllocation, SplitPayoutAllocation } from "../components";
import { useAddSignature, useDeletePayout, useMarkPayoutAsExecuted, usePayout } from "../payoutsService.hooks";
import { usePayoutStatus } from "../utils/usePayoutStatus";
import { StyledPayoutStatusPage } from "./styles";
import { useCreatePayoutProposal } from "./useCreatePayoutProposal";
import { useSignPayout } from "./useSignPayout";

const DELETABLE_STATUS = [PayoutStatus.Creating, PayoutStatus.Pending, PayoutStatus.ReadyToExecute];
Expand All @@ -52,6 +55,10 @@ export const PayoutStatusPage = () => {
const confirm = useConfirm();
const { tryAuthentication, isAuthenticated } = useSiweAuth();

const [searchParams] = useSearchParams();
const isAdvancedMode = searchParams.get("mode")?.includes("advanced") ?? false;
const [proposalCreatedSuccessfully, setProposalCreatedSuccessfully] = useState<boolean | undefined>();

const { allVaults, allPayouts, withdrawSafetyPeriod } = useVaults();

const { payoutId } = useParams();
Expand All @@ -68,6 +75,7 @@ export const PayoutStatusPage = () => {
const addSignature = useAddSignature();
const markPayoutAsExecuted = useMarkPayoutAsExecuted();
const signPayout = useSignPayout(vault, payout);
const createPayoutProposal = useCreatePayoutProposal(vault, payout);
const executePayout = ExecutePayoutContract.hook(vault, payout);
const waitingPayoutExecution = useWaitForTransaction({
hash: executePayout.data?.hash as `0x${string}`,
Expand Down Expand Up @@ -98,6 +106,7 @@ export const PayoutStatusPage = () => {
const isCollectingSignatures = payoutStatus === PayoutStatus.Pending;
const canBeDeleted = payoutStatus && DELETABLE_STATUS.includes(payoutStatus);
const canBesigned = payoutStatus && SIGNABLE_STATUS.includes(payoutStatus);
const canCreateProposalOnSafe = isAdvancedMode;
const isAnyActivePayout = allPayouts?.some((payout) => payout.vault.id === vault?.id && payout.isActive);
const [isUserCommitteeMember, setIsUserCommitteeMember] = useState(false);

Expand Down Expand Up @@ -174,6 +183,24 @@ export const PayoutStatusPage = () => {
refetchPayout();
};

const handleCreateProposalOnSafe = async () => {
if (!isUserCommitteeMember) return;
if (!payoutId || !payout || !vault) return;

const isOk = await createPayoutProposal.create();
setProposalCreatedSuccessfully(isOk);
};

const goToSafeApp = () => {
if (!vault) return;

const multisig = vault.committee;
window.open(
`${getBaseSafeAppUrl(vault.chainId)}/transactions/queue?safe=${getGnosisChainPrefixByChainId(vault.chainId)}:${multisig}`,
"_blank"
);
};

const handleExecutePayout = async () => {
if (!withdrawSafetyPeriod?.isSafetyPeriod || !isReadyToExecute || !payout || isAnyActivePayout) return;
await executePayout.send();
Expand Down Expand Up @@ -348,6 +375,11 @@ export const PayoutStatusPage = () => {
)}

<div className="sub-container">
{canCreateProposalOnSafe && (
<Button styleType="outlined" disabled={!isUserCommitteeMember} onClick={handleCreateProposalOnSafe}>
{t("Payouts.createProposalOnSafe")}
</Button>
)}
{canBesigned && !userHasAlreadySigned && (
<Button disabled={!isUserCommitteeMember} onClick={handleSignPayout}>
{t("Payouts.signPayout")}
Expand All @@ -360,6 +392,17 @@ export const PayoutStatusPage = () => {
)}
</div>
</div>

{proposalCreatedSuccessfully && (
<Alert type="success" className="mt-4">
<>
<span>{t("proposalCreatedSuccessfully")}</span>
<Button styleType="text" onClick={goToSafeApp}>
{t("goToSafeApp")}
</Button>
</>
</Alert>
)}
</div>
</div>
)}
Expand All @@ -369,6 +412,7 @@ export const PayoutStatusPage = () => {
{(addSignature.isLoading || signPayout.isLoading) && (
<Loading fixed extraText={`${t("Payouts.signingPayoutTransaction")}...`} />
)}
{createPayoutProposal.isLoading && <Loading fixed extraText={`${t("Payouts.creatingSafeProposal")}...`} />}
{(executePayout.isLoading || waitingPayoutExecution.isLoading || markPayoutAsExecuted.isLoading) && (
<Loading fixed extraText={`${t("Payouts.executingPayout")}`} />
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { IPayoutResponse, IVault, getExecutePayoutSafeTransaction, getGnosisSafeTxServiceBaseUrl } from "@hats.finance/shared";
import SafeApiKit from "@safe-global/api-kit";
import Safe, { EthersAdapter } from "@safe-global/protocol-kit";
import { Signer, ethers, utils } from "ethers";
import { useState } from "react";
import { useAccount, useProvider, useSigner } from "wagmi";

export const useCreatePayoutProposal = (vault?: IVault, payout?: IPayoutResponse) => {
const { address: account } = useAccount();
const provider = useProvider();
const { data: signer } = useSigner();

const [isLoading, setIsLoading] = useState(false);

const create = async () => {
try {
if (!vault || !payout || !account) return;
setIsLoading(true);

const multisigAddress = utils.getAddress(vault.committee ?? "");
if (!multisigAddress) {
alert("No vault multisig address. Please contact Hats team with this error.");
return false;
}

const ethAdapter = new EthersAdapter({ ethers, signerOrProvider: signer as Signer });
const txServiceUrl = getGnosisSafeTxServiceBaseUrl(vault.chainId);
const safeService = new SafeApiKit({ txServiceUrl, ethAdapter });
const safeSdk = await Safe.create({ ethAdapter, safeAddress: multisigAddress });

const { tx: safeTransaction } = await getExecutePayoutSafeTransaction(provider, multisigAddress, payout);

const safeTxHash = await safeSdk.getTransactionHash(safeTransaction);
const senderSignature = await safeSdk.signTypedData(safeTransaction);
await safeService.proposeTransaction({
safeAddress: multisigAddress,
safeTransactionData: safeTransaction.data,
safeTxHash,
senderAddress: account,
senderSignature: senderSignature.data,
origin: "https://app.hats.finance",
});

return true;
} catch (error) {
console.log(error);
return false;
} finally {
setIsLoading(false);
}
};

return {
create,
isLoading,
};
};

0 comments on commit 85337f9

Please sign in to comment.