From 938bde8faa17a395e37eee95a3a7d412109576ed Mon Sep 17 00:00:00 2001 From: Iain Nash Date: Fri, 21 Jun 2024 15:47:56 -0400 Subject: [PATCH] Update across the board ownership changes, url updates (#26) Fix URL changes, ownership updates. Allow appending delete add operations for users to the current transaction queue. --- src/app/NewSafeProposal.tsx | 46 +++++++++++------- src/chains.ts | 2 +- src/components/SetOwnerModal.tsx | 65 ++++++++++++++------------ src/hooks/useLoadProposalFromQuery.ts | 18 ++++--- src/hooks/useSetParamsFromQuery.ts | 6 ++- src/hooks/useUpdateProposalViaQuery.ts | 19 ++++++++ 6 files changed, 101 insertions(+), 55 deletions(-) create mode 100644 src/hooks/useUpdateProposalViaQuery.ts diff --git a/src/app/NewSafeProposal.tsx b/src/app/NewSafeProposal.tsx index b6181ef..0d2a061 100644 --- a/src/app/NewSafeProposal.tsx +++ b/src/app/NewSafeProposal.tsx @@ -1,7 +1,13 @@ import { Field, FieldArray, Formik } from "formik"; import { SafeInformation } from "../components/SafeInformation"; import { Card, View, Text, Button, useToast } from "reshaped"; -import { SyntheticEvent, useCallback, useEffect, useState } from "react"; +import { + SyntheticEvent, + createContext, + useCallback, + useEffect, + useState, +} from "react"; import { Address, Hex, formatEther } from "viem"; import { validateAddress, validateETH } from "../utils/validators"; import { GenericField } from "../components/GenericField"; @@ -404,6 +410,8 @@ const EditProposal = ({ ); }; +export const ProposalContext = createContext(undefined); + export const NewSafeProposal = () => { const [proposal, setProposal] = useState( DEFAULT_PROPOSAL, @@ -428,22 +436,24 @@ export const NewSafeProposal = () => { ); return ( - - - {isEditing && ( - - )} - {!isEditing && proposal && ( - - )} - - + + + + {isEditing && ( + + )} + {!isEditing && proposal && ( + + )} + + + ); }; diff --git a/src/chains.ts b/src/chains.ts index 0e8f16b..36a34c6 100644 --- a/src/chains.ts +++ b/src/chains.ts @@ -44,7 +44,7 @@ Object.keys(contractNetworks).map((network) => { return; } const viemChain = Object.values(chains).find( - (chain) => chain.id.toString() === network + (chain) => chain.id.toString() === network, ); if (!viemChain) { diff --git a/src/components/SetOwnerModal.tsx b/src/components/SetOwnerModal.tsx index 9da9d5b..131a7ba 100644 --- a/src/components/SetOwnerModal.tsx +++ b/src/components/SetOwnerModal.tsx @@ -1,4 +1,4 @@ -import { SyntheticEvent } from "react"; +import { SyntheticEvent, useContext } from "react"; import { Button, Modal, Text, View, useToast } from "reshaped"; import { AddressView } from "./AddressView"; import { Address } from "viem"; @@ -6,8 +6,10 @@ import { Field, Form, Formik } from "formik"; import { GenericField } from "./GenericField"; import { yupAddress } from "../utils/validators"; import { number, object } from "yup"; -import { useOutletContext, useSearchParams } from "react-router-dom"; +import { useOutletContext } from "react-router-dom"; import { SafeContext } from "./Contexts"; +import { ProposalContext } from "../app/NewSafeProposal"; +import { useUpdateProposalViaQuery } from "../hooks/useUpdateProposalViaQuery"; export type OwnerAction = | undefined @@ -43,7 +45,9 @@ const ButtonPanel = ({ const AddOwnerModalContent = ({ onClose }: { onClose: () => void }) => { const { safeInformation } = useOutletContext(); const toast = useToast(); - const [, setSearchParams] = useSearchParams(); + const updateProposalQuery = useUpdateProposalViaQuery(); + const currentProposal = useContext(ProposalContext); + return ( void }) => { ownerAddress: address, threshold: threshold, }); - setSearchParams({ - proposal: JSON.stringify({ - actions: [ - { - data: addOwnerTx.data.data, - value: 0, - to: safeInformation.address, - }, - ], - }), + updateProposalQuery({ + data: addOwnerTx.data.data, + value: "0", + to: safeInformation.address, }); } catch (err: any) { toast.show({ title: "Error Updating Safe", text: err.toString() }); @@ -76,7 +74,7 @@ const AddOwnerModalContent = ({ onClose }: { onClose: () => void }) => { }} >
- Add Owner + Add Signer {GenericField({ label: "New User Address" })} @@ -102,25 +100,34 @@ const RemoveOwnerModalContent = ({ onClose: () => void; target: string; }) => { - const [_, setParams] = useSearchParams(); const { safeInformation } = useOutletContext(); + const updateProposalViaQuery = useUpdateProposalViaQuery(); + const toaster = useToast(); const onSubmitClick = async ({ threshold }: any) => { - const removeOwnerTx = await safeInformation?.safeSdk.createRemoveOwnerTx({ - ownerAddress: safeInformation!.address, - threshold: threshold, - }); - if (!removeOwnerTx || !safeInformation) { - return; + try { + const removeOwnerTx = await safeInformation?.safeSdk2.createRemoveOwnerTx( + { + ownerAddress: target, + threshold: threshold, + }, + ); + if (!removeOwnerTx || !safeInformation) { + return; + } + + updateProposalViaQuery({ + data: removeOwnerTx.data.data, + value: "0", + to: safeInformation.address, + }); + onClose(); + } catch (err: any) { + toaster.show({ + title: "Error Removing Owner", + text: `Error setting up transaction: ${err.toString()}`, + }); } - setParams({ - proposal: JSON.stringify({ - actions: [ - { data: removeOwnerTx.data, value: "0", to: safeInformation.address }, - ], - }), - }); - onClose(); }; return ( { const [proposal, setProposal] = useState(); const [params] = useSearchParams(); useEffect(() => { - const targets = params.get(queryKeys.targets)?.split("|"); - const calldatas = params.get(queryKeys.calldatas)?.split("|"); - const values = params.get(queryKeys.values)?.split("|"); + const targets = params.get(queryKeys["targets"])?.split("|"); + const calldatas = params.get(queryKeys["calldatas"])?.split("|"); + const values = params.get(queryKeys["values"])?.split("|"); + const nonce = params.get(queryKeys["nonce"]); + if (targets && calldatas) { // ensure the 3 lengths are the same. check if values also has the same length if its not empty // check the inverse of the above, if inverse is true, return: @@ -32,7 +35,10 @@ export const useLoadProposalFromQuery = () => { data: calldatas[index]!, value: (values && values[index]) || "0", })); - setProposal({ actions }); + + console.log({ actions, txt: "setting proposal" }); + + setProposal({ actions, ...(nonce ? { [queryKeys.nonce]: nonce } : {}) }); } }, [params, setProposal]); diff --git a/src/hooks/useSetParamsFromQuery.ts b/src/hooks/useSetParamsFromQuery.ts index 542127a..ea6cb4c 100644 --- a/src/hooks/useSetParamsFromQuery.ts +++ b/src/hooks/useSetParamsFromQuery.ts @@ -11,11 +11,15 @@ export const useRedirectToProposalWithNewParams = () => { if (!proposal.actions?.length) { return; } - console.log("setting params", proposal.actions); setParams({ [queryKeys.targets]: proposal.actions!.map((action) => action.to).join("|"), [queryKeys.calldatas]: proposal.actions!.map((action) => action.data).join("|"), [queryKeys.values]: proposal.actions!.map((action) => action.value).join("|"), + ...(proposal.nonce + ? { + [queryKeys.nonce]: proposal.nonce.toString(), + } + : {}), }); }, [setParams], diff --git a/src/hooks/useUpdateProposalViaQuery.ts b/src/hooks/useUpdateProposalViaQuery.ts new file mode 100644 index 0000000..4d858a0 --- /dev/null +++ b/src/hooks/useUpdateProposalViaQuery.ts @@ -0,0 +1,19 @@ +import { useCallback, useContext } from "react"; +import { Proposal } from "../schemas/proposal"; +import { ProposalContext } from "../app/NewSafeProposal"; +import { useRedirectToProposalWithNewParams } from "./useSetParamsFromQuery"; + +export const useUpdateProposalViaQuery = () => { + const setParams = useRedirectToProposalWithNewParams(); + const proposal = useContext(ProposalContext); + + return useCallback( + (newAction: NonNullable[0]) => { + setParams({ + actions: [...(proposal?.actions || []), newAction], + nonce: proposal?.nonce, + }); + }, + [proposal, setParams], + ); +};