Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/332 use create proposal hook #364

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/davi/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -266,12 +266,14 @@
"invalidContentHash": "Invalid content hash",
"titleRequired": "Title is required",
"atLeastOneOptionRequired": "At least one option is required",
"mustBe2Options": "There must be 2 options",
"atLeastOneActionPerOptionRequired": "At least one action per option is required",
"optionsArrayIsEmpty": "Options array is empty",
"metadataUploadError": "Metadata upload error",
"genericProposalError": "We ran into an error.",
"couldntFindTheProposal": "We couldn't find that proposal",
"probablyNonExistent": "It probably doesn't exist"
"probablyNonExistent": "It probably doesn't exist",
"errorFetchingScheme": "We ran into an error fetching the dao's Scheme"
}
},
"tokenPicker": {
Expand Down
4 changes: 3 additions & 1 deletion apps/davi/public/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -264,12 +264,14 @@
"invalidContentHash": "Contenido hash inválido",
"titleRequired": "El título es requerido",
"atLeastOneOptionRequired": "Por lo menos una opción es requerida",
"mustBe2Options": "Deben ser 2 opciones",
"atLeastOneActionPerOptionRequired": "Por lo menos una acción por opción es requerida",
"optionsArrayIsEmpty": "Las opciones están vacías",
"metadataUploadError": "Error en la carga de Metadatos",
"genericProposalError": "Nos encontramos con un error.",
"couldntFindTheProposal": "No pudimos encontrar esa propuesta",
"probablyNonExistent": "Probablemente no exista"
"probablyNonExistent": "Probablemente no exista",
"errorFetchingScheme": "Nos encontramos con un error buscando el esquema de la dao"
}
},
"tokenPicker": {
Expand Down
2 changes: 1 addition & 1 deletion apps/davi/src/Modules/Guilds/pages/CreateProposal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const CreateProposalPage: React.FC = () => {
} = useHookStoreProvider();
const { orbis } = useOrbisContext();

const createProposal = useCreateProposal(guildId, discussionId);
const createProposal = useCreateProposal(guildId, subdaoId, discussionId);
const navigate = useNavigate();
const location = useLocation();
const { t } = useTranslation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { AddOptionWrapper, SimulationButton } from './OptionsList.styled';
import { SimulationModal } from './SimulationModal';
import { OptionsListProps, SimulationState } from './types';
import { BigNumber } from 'ethers';
import { useHookStoreProvider } from 'stores';

export const OptionsList: React.FC<OptionsListProps> = ({
isEditable,
Expand All @@ -51,6 +52,8 @@ export const OptionsList: React.FC<OptionsListProps> = ({
const [clonedOptions, setClonedOptions] = useState<Option[]>(null);
const recentlyMovedToNewContainer = useRef(false);
const theme = useTheme();
const { name: governanceImplementationName } = useHookStoreProvider();

useEffect(() => {
requestAnimationFrame(() => {
recentlyMovedToNewContainer.current = false;
Expand Down Expand Up @@ -378,7 +381,7 @@ export const OptionsList: React.FC<OptionsListProps> = ({
</>
)}

{isEditable && (
{isEditable && governanceImplementationName !== 'Governance1_5' && (
<>
<Divider />
<AddOptionWrapper>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
import { createPost } from 'components/Forum';
import { useTransactions } from 'contexts/Guilds';
import { useOrbisContext } from 'contexts/Guilds/orbis';
import { providers } from 'ethers';
import { useSchemeContract } from 'hooks/Guilds/contracts/useContract';
import usePinataIPFS from 'hooks/Guilds/ipfs/usePinataIPFS';
import useWeb3Storage from 'hooks/Guilds/ipfs/useWeb3Storage';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { WriterHooksInteface } from 'stores/types';
import { isValid1_5Proposal } from 'utils';
import { useNetwork } from 'wagmi';

type IUseCreateProposal = WriterHooksInteface['useCreateProposal'];
type IHandleCreateProposal = ReturnType<IUseCreateProposal>;

// TODO: placeholder hook to prevent crashing

export const useCreateProposal: IUseCreateProposal = (
daoAddress: string,
subdaoId?: string,
discussionRef?: string
) => {
const { chain } = useNetwork();
const { orbis } = useOrbisContext();
const { pinToPinata } = usePinataIPFS();
const { pinToStorage } = useWeb3Storage();
const { t } = useTranslation();
const { createTransaction } = useTransactions();

const schemeContract = useSchemeContract(subdaoId);

const handleCreateProposal: IHandleCreateProposal = useCallback(
async (
title,
Expand All @@ -23,10 +41,94 @@ export const useCreateProposal: IUseCreateProposal = (
handleMetadataUploadError,
cb
) => {
return;
const { options } = otherFields;

// adding the against option
totalOptions++;

const { isValid, error } = isValid1_5Proposal({
toArray,
dataArray,
valueArray,
totalOptions,
title,
});

const linkToOrbis = (receipt: providers.TransactionReceipt) => {
const link = {
title,
body: `Created Proposal: ${title}`,
context: `DAVI-${daoAddress}-${discussionRef}-proposal`,
master: receipt.logs[0].topics[1],
replyTo: null,
mentions: [],
data: { chain: chain },
};
createPost(orbis, link);
};

const uploadToIPFS = async () => {
const content = {
description: description,
voteOptions: ['', ...options.map(({ label }) => label)],
discussionRef: discussionRef,
};
const pinataPin = pinToPinata(content).then(result => result?.IpfsHash);
const web3storagePin = pinToStorage(content);
const results = await Promise.all([pinataPin, web3storagePin]);
// TODO: Loop through array looking for at least two matching hashes when we have >2 pinning services
if (results[0] !== results[1]) {
console.warn(t('actionBuilder.ens.ipfs.hashNotTheSame'), results);
}
return `ipfs://${results[0]}`;
};

if (!isValid) throw new Error(error);

if (options.length === 0) {
throw new Error(t('proposal.errors.optionsArrayIsEmpty'));
}

let contentHash = '';

if (!skipMetadataUpload) {
try {
contentHash = await uploadToIPFS();
} catch (error) {
handleMetadataUploadError(error);
return;
}
}

if (!isValid) throw new Error(error);

createTransaction(
`${t('createProposal.createProposal')} ${title}`,
async () => {
return schemeContract.proposeCalls(
toArray,
dataArray,
valueArray,
totalOptions,
title,
contentHash
);
},
true,
cb,
linkToOrbis
);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
[
schemeContract,
createTransaction,
t,
pinToPinata,
discussionRef,
daoAddress,
orbis,
]
);

return handleCreateProposal;
Expand Down
1 change: 1 addition & 0 deletions apps/davi/src/stores/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ export interface WriterHooksInteface {
) => (daoTokenVault: string, amount?: string) => Promise<void>;
useCreateProposal: (
daoAddress: string,
subDaoAddress?: string,
linkRef?: string
) => (
title: string,
Expand Down
48 changes: 48 additions & 0 deletions apps/davi/src/utils/validations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,54 @@ export const isValidGuildProposal = ({
};
};

export const isValid1_5Proposal = ({
toArray,
dataArray,
valueArray,
totalOptions,
title,
}: {
toArray: string[];
dataArray: string[];
valueArray: BigNumber[];
totalOptions: number;
title: string;
}): { isValid: boolean; error?: string } => {
if (!title) {
return {
isValid: false,
error: i18next.t('proposal.errors.titleRequired'),
};
}
if (totalOptions === 0) {
return {
isValid: false,
error: i18next.t('proposal.errors.atLeastOneOptionRequired'),
};
}
if (totalOptions !== 2) {
return {
isValid: false,
error: i18next.t('proposal.errors.mustBe2Options'),
};
}
if (
toArray.length === 0 ||
dataArray.length === 0 ||
valueArray.length === 0
) {
return {
isValid: false,
error: i18next.t('proposal.errors.atLeastOneActionPerOptionRequired'),
};
}

return {
isValid: true,
error: null,
};
};

export const isEnsName = (
name: string
): { isValid: boolean; validationError: string } => {
Expand Down