Skip to content

Commit

Permalink
(govern) feat: add proposals
Browse files Browse the repository at this point in the history
  • Loading branch information
Tanya-atatakai committed Sep 27, 2024
1 parent b316a1e commit 7e98392
Show file tree
Hide file tree
Showing 19 changed files with 2,067 additions and 78 deletions.
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,10 @@ NEXT_PUBLIC_OPTIMISM_BALANCER_URL=__URL__
NEXT_PUBLIC_GNOSIS_BALANCER_URL=__URL__
NEXT_PUBLIC_POLYGON_BALANCER_URL=__URL__
NEXT_PUBLIC_ARBITRUM_BALANCER_URL=__URL__

# govern
NEXT_PUBLIC_MAINNET_TEST_RPC=__URL__
NEXT_PUBLIC_GNOSIS_TEST_RPC=__URL__
NEXT_PUBLIC_POLYGON_TEST_RPC=__URL__
NEXT_PUBLIC_IS_CONNECTED_TO_TEST_NET=__TRUE_OR_FALSE__
NEXT_PUBLIC_GOVERNOR_SUBGRAPH_URL=__URL__
2 changes: 2 additions & 0 deletions apps/govern/common-util/constants/time.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// approx time when Ethereum blockchain produces a new block
export const SECONDS_PER_BLOCK = 12;
28 changes: 28 additions & 0 deletions apps/govern/common-util/functions/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { RPC_URLS } from 'common-util/constants/rpcs';
import { getAddressFromBytes32 } from './addresses';
import { getUnixNextWeekStartTimestamp } from './time';
import {
getGovernorContract,
getOlasContract,
getTokenomicsContract,
getTreasuryContract,
Expand All @@ -30,6 +31,7 @@ type VoteForNomineeWeightsParams = {
chainIds: number[];
weights: string[];
};

export const voteForNomineeWeights = async ({
account,
nominees,
Expand Down Expand Up @@ -382,3 +384,29 @@ export const depositServiceDonationRequest = async ({
throw error;
}
};

/**
* Vote for a proposal
*/
export const voteForProposal = async ({
account,
proposalId,
support,
}: {
account: Address;
proposalId: string;
support: number;
}) => {
const contract = getGovernorContract();
const voteFn = contract.methods.castVote(proposalId, support);

const estimatedGas = await getEstimatedGasLimit(voteFn, account);
const fn = voteFn.send({ from: account, estimatedGas });

const result = await sendTransaction(fn, account, {
supportedChains: SUPPORTED_CHAINS,
rpcUrls: RPC_URLS,
});

return result;
};
19 changes: 19 additions & 0 deletions apps/govern/common-util/functions/time.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Block } from 'viem';

import { NA } from 'libs/util-constants/src';

import { SECONDS_PER_BLOCK } from 'common-util/constants/time';

// Returns the closest Thursday in the future
// which is the start of the next week by Unix time
export const getUnixNextWeekStartTimestamp = () => {
Expand Down Expand Up @@ -102,3 +106,18 @@ export const getRemainingTimeInSeconds = (unlockTime?: number) => {
const todayDateInTimeStamp = new Date().getTime();
return Math.round((futureDateInTimeStamp - todayDateInTimeStamp) / 1000);
};

/**
* Returns estimated time in future based on provided block
*/
export const estimateFutureBlockTimestamp = (
currentBlock: Block | undefined,
futureBlockNumber: bigint,
) => {
if (!currentBlock) return null;
const currentBlockNumber = currentBlock.number as bigint;
const currentBlockTimestamp = currentBlock.timestamp;
const blockDifference = futureBlockNumber - currentBlockNumber;
const estimatedTimestamp = currentBlockTimestamp + blockDifference * BigInt(SECONDS_PER_BLOCK);
return estimatedTimestamp;
};
8 changes: 8 additions & 0 deletions apps/govern/common-util/functions/web3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Web3 from 'web3';
import { AbiItem } from 'web3-utils';

import {
GOVERNOR_OLAS,
OLAS,
TOKENOMICS,
TREASURY,
Expand Down Expand Up @@ -66,3 +67,10 @@ export const getTreasuryContract = () => {
const contract = getContract(abi, address);
return contract;
};

export const getGovernorContract = () => {
const abi = GOVERNOR_OLAS.abi as AbiItem[];
const address = GOVERNOR_OLAS.addresses[mainnet.id];
const contract = getContract(abi, address);
return contract;
};
36 changes: 36 additions & 0 deletions apps/govern/common-util/graphql/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { gql, request } from 'graphql-request';

import { Proposals } from './types';

const GOVERNOR_SUBGRAPH_URL = process.env.NEXT_PUBLIC_GOVERNOR_SUBGRAPH_URL || '';

const getProposalsQuery = gql`
query GetProposalCreateds {
proposalCreateds(first: 1000, orderBy: blockTimestamp, orderDirection: desc) {
id
blockNumber
blockTimestamp
proposalId
startBlock
endBlock
isQueued
isExecuted
isCancelled
description
votesFor
votesAgainst
quorum
proposer
transactionHash
voteCasts {
id
weight
support
voter
}
}
}
`;

export const getProposals = async () =>
request<Proposals>(GOVERNOR_SUBGRAPH_URL, getProposalsQuery);
29 changes: 29 additions & 0 deletions apps/govern/common-util/graphql/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Address } from 'viem';

export type Proposal = {
id: string;
proposer: Address;
blockNumber: string;
blockTimestamp: string;
proposalId: string;
startBlock: string;
endBlock: string;
isQueued: boolean;
isExecuted: boolean;
isCancelled: boolean;
description: string;
votesFor: string;
votesAgainst: string;
quorum: string;
transactionHash: string;
voteCasts: {
id: string;
weight: string;
support: number;
voter: Address;
}[];
};

export type Proposals = {
proposalCreateds: Proposal[];
};
103 changes: 103 additions & 0 deletions apps/govern/components/Proposals/ProposalDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { Col, Flex, Row, Skeleton, Typography } from 'antd';
import { Block } from 'viem';
import { mainnet } from 'viem/chains';
import { useAccount, useBlock } from 'wagmi';

import { Caption } from 'libs/ui-components/src';
import { EXPLORER_URLS, UNICODE_SYMBOLS } from 'libs/util-constants/src';
import { areAddressesEqual } from 'libs/util-functions/src';

import {
estimateFutureBlockTimestamp,
getFullFormattedDate,
truncateAddress,
} from 'common-util/functions';
import { Proposal } from 'common-util/graphql/types';

import { VOTES_SUPPORT, getFormattedValue } from './utils';

const { Paragraph, Text } = Typography;

const useBlockTimestamp = (currentBlock: Block | undefined, block: bigint) => {
// we can't get block data in the future, can only estimate instead
const canLoadBlockData =
currentBlock && currentBlock.number ? currentBlock.number > BigInt(block) : false;

const blockData = useBlock({
blockNumber: BigInt(block),
chainId: mainnet.id,
query: {
enabled: canLoadBlockData,
},
});

return {
timestamp:
canLoadBlockData && blockData.data
? blockData.data.timestamp
: estimateFutureBlockTimestamp(currentBlock, block),
isLoading: canLoadBlockData ? blockData.isLoading : false,
};
};

const AddressLink = ({ address, className }: { address: string; className?: string }) => (
<a
href={`${EXPLORER_URLS[mainnet.id]}/address/${address}`}
target="_blank"
rel="noreferrer"
className={className}
>
{`${truncateAddress(address)} ${UNICODE_SYMBOLS.EXTERNAL_LINK}`}
</a>
);

export const ProposalDetails = ({
item,
currentBlock,
}: {
item: Proposal;
currentBlock: Block | undefined;
}) => {
const { address } = useAccount();

const startDateBlock = useBlockTimestamp(currentBlock, BigInt(item.startBlock));
const endDateBlock = useBlockTimestamp(currentBlock, BigInt(item.endBlock));

return (
<Flex vertical>
<Caption>Proposal description</Caption>
<Paragraph className="mb-16">{item.description}</Paragraph>
<Caption>Owner</Caption>
<AddressLink address={item.proposer} className="mb-16" />
<Flex gap={24} className="mb-16">
<Flex vertical>
<Caption>Start Date</Caption>
{startDateBlock.isLoading && <Skeleton.Input active />}
{startDateBlock.timestamp !== null && (
<Text>{getFullFormattedDate(Number(startDateBlock.timestamp) * 1000)}</Text>
)}
</Flex>
<Flex vertical>
<Caption>End Date</Caption>
{endDateBlock.isLoading && <Skeleton.Input active />}
{endDateBlock.timestamp !== null && (
<Text>{getFullFormattedDate(Number(endDateBlock.timestamp) * 1000)}</Text>
)}
</Flex>
</Flex>
<Flex vertical gap={8}>
<Caption>Voters ({item.voteCasts?.length})</Caption>
{item.voteCasts.map((vote, index) => (
<Row key={vote.id} gutter={[0, 8]}>
<Col span={5}>
<AddressLink address={vote.voter} />
{address && areAddressesEqual(vote.voter, address) && ' (you)'}
</Col>
<Col span={2}>{getFormattedValue(vote.weight)}</Col>
<Col>({VOTES_SUPPORT[vote.support]})</Col>
</Row>
))}
</Flex>
</Flex>
);
};
Loading

0 comments on commit 7e98392

Please sign in to comment.