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

Bugfix | EditStreams #2321

Merged
merged 8 commits into from
Sep 3, 2024
Merged
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
49 changes: 14 additions & 35 deletions src/components/pages/Roles/RolePaymentDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { Box, Button, Flex, Grid, GridItem, Icon, Image, Text } from '@chakra-ui/react';
import { Calendar, Download } from '@phosphor-icons/react';
import { format } from 'date-fns';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { Address, getAddress, getContract } from 'viem';
import { useWalletClient, useAccount } from 'wagmi';
import { SablierV2LockupLinearAbi } from '../../../assets/abi/SablierV2LockupLinear';
import { Address, getAddress } from 'viem';
import { useAccount, usePublicClient } from 'wagmi';
import { DETAILS_SHADOW } from '../../../constants/common';
import { DAO_ROUTES } from '../../../constants/routes';
import { convertStreamIdToBigInt } from '../../../hooks/streams/useCreateSablierStream';
import { useFractal } from '../../../providers/App/AppProvider';
import { useNetworkConfig } from '../../../providers/NetworkConfig/NetworkConfigProvider';
import { useRolesStore } from '../../../store/roles';
import { BigIntValuePair } from '../../../types';
import { DEFAULT_DATE_FORMAT, formatCoin, formatUSD } from '../../../utils';
import { ModalType } from '../../ui/modals/ModalProvider';
Expand Down Expand Up @@ -78,6 +77,7 @@ interface RolePaymentDetailsProps {
endDate: Date;
cliffDate?: Date;
isStreaming: () => boolean;
withdrawableAmount?: bigint;
};
onClick?: () => void;
showWithdraw?: boolean;
Expand All @@ -95,46 +95,24 @@ export function RolePaymentDetails({
treasury: { assetsFungible },
} = useFractal();
const { address: connectedAccount } = useAccount();
const { data: walletClient } = useWalletClient();
const { addressPrefix } = useNetworkConfig();
const { refreshWithdrawableAmount } = useRolesStore();
const navigate = useNavigate();

const [withdrawableAmount, setWithdrawableAmount] = useState(0n);

const publicClient = usePublicClient();
const canWithdraw = useMemo(() => {
if (connectedAccount && connectedAccount === roleHatWearerAddress && !!showWithdraw) {
return true;
}
return false;
}, [connectedAccount, showWithdraw, roleHatWearerAddress]);

const loadAmounts = useCallback(async () => {
if (walletClient && payment.streamId && payment.contractAddress && canWithdraw) {
const streamContract = getContract({
abi: SablierV2LockupLinearAbi,
address: payment.contractAddress,
client: walletClient,
});

const bigintStreamId = convertStreamIdToBigInt(payment.streamId);

const newWithdrawableAmount = await streamContract.read.withdrawableAmountOf([
bigintStreamId,
]);
setWithdrawableAmount(newWithdrawableAmount);
}
}, [walletClient, payment, canWithdraw]);

useEffect(() => {
loadAmounts();
}, [loadAmounts]);

const [modalType, props] = useMemo(() => {
if (
!payment.streamId ||
!payment.contractAddress ||
!roleHatWearerAddress ||
!roleHatSmartAddress
!roleHatSmartAddress ||
!publicClient
) {
return [ModalType.NONE] as const;
}
Expand All @@ -146,15 +124,16 @@ export function RolePaymentDetails({
paymentAssetDecimals: payment.asset.decimals,
paymentStreamId: payment.streamId,
paymentContractAddress: payment.contractAddress,
onSuccess: loadAmounts,
onSuccess: () =>
refreshWithdrawableAmount(roleHatSmartAddress, payment.streamId!, publicClient),
withdrawInformation: {
withdrawableAmount,
withdrawableAmount: payment.withdrawableAmount,
roleHatWearerAddress,
roleHatSmartAddress,
},
},
] as const;
}, [payment, roleHatSmartAddress, roleHatWearerAddress, loadAmounts, withdrawableAmount]);
}, [payment, roleHatSmartAddress, roleHatWearerAddress, refreshWithdrawableAmount, publicClient]);

const withdraw = useDecentModal(modalType, props);

Expand Down Expand Up @@ -315,7 +294,7 @@ export function RolePaymentDetails({
/>
</GridItem>
</Grid>
{canWithdraw && withdrawableAmount > 0n && (
{canWithdraw && !!payment?.withdrawableAmount && payment.withdrawableAmount > 0n && (
<Box
mt={4}
px={4}
Expand Down
2 changes: 2 additions & 0 deletions src/components/pages/Roles/forms/RoleFormCreateProposal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ export default function RoleFormCreateProposal({ close }: { close: () => void })
amount: payment.amount,
asset: payment.asset,
cliffDate: payment.cliffDate,
withdrawableAmount: 0n,
isCancelled: false,
};
})
: [],
Expand Down
3 changes: 3 additions & 0 deletions src/components/pages/Roles/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export interface SablierPayment extends BaseSablierStream {
endDate: Date;
cliffDate: Date | undefined;
isStreaming: () => boolean;
withdrawableAmount: bigint;
isCancelled: boolean;
}

export interface SablierPaymentFormValues extends Partial<SablierPayment> {
Expand Down Expand Up @@ -160,4 +162,5 @@ export type PreparedEditedStreamData = PreparedNewStreamData & {
roleHatId: bigint;
roleHatWearer: Address;
roleHatSmartAddress: Address;
streamContractAddress: Address;
};
132 changes: 81 additions & 51 deletions src/hooks/DAO/loaders/useHatsTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { useApolloClient } from '@apollo/client';
import { HatsSubgraphClient, Tree } from '@hatsprotocol/sdk-v1-subgraph';
import { useEffect } from 'react';
import { toast } from 'react-toastify';
import { formatUnits, getAddress } from 'viem';
import { formatUnits, getAddress, getContract } from 'viem';
import { usePublicClient } from 'wagmi';
import { StreamsQueryDocument } from '../../../../.graphclient';
import { SablierV2LockupLinearAbi } from '../../../assets/abi/SablierV2LockupLinear';
import { SablierPayment } from '../../../components/pages/Roles/types';
import useIPFSClient from '../../../providers/App/hooks/useIPFSClient';
import { useNetworkConfig } from '../../../providers/NetworkConfig/NetworkConfigProvider';
import { DecentHatsError, useRolesStore } from '../../../store/roles';
import { convertStreamIdToBigInt } from '../../streams/useCreateSablierStream';
import { CacheExpiry, CacheKeys } from '../../utils/cache/cacheDefaults';
import { getValue, setValue } from '../../utils/cache/useLocalStorage';

Expand Down Expand Up @@ -154,7 +156,13 @@ const useHatsTree = () => {

useEffect(() => {
async function getHatsStreams() {
if (sablierSubgraph && hatsTree && hatsTree.roleHats.length > 0 && !streamsFetched) {
if (
sablierSubgraph &&
hatsTree &&
hatsTree.roleHats.length > 0 &&
!streamsFetched &&
publicClient
) {
const secondsTimestampToDate = (ts: string) => new Date(Number(ts) * 1000);
const updatedHatsRoles = await Promise.all(
hatsTree.roleHats.map(async hat => {
Expand All @@ -176,56 +184,71 @@ const useHatsTree = () => {
const lockupLinearStreams = streamQueryResult.data.streams.filter(
stream => stream.category === 'LockupLinear',
);
const formattedActiveStreams: SablierPayment[] = lockupLinearStreams.map(
lockupLinearStream => {
const parsedAmount = formatUnits(
BigInt(lockupLinearStream.depositAmount),
lockupLinearStream.asset.decimals,
);

const startDate = secondsTimestampToDate(lockupLinearStream.startTime);
const endDate = secondsTimestampToDate(lockupLinearStream.endTime);
const cliffDate = lockupLinearStream.cliff
? secondsTimestampToDate(lockupLinearStream.cliffTime)
: undefined;

return {
streamId: lockupLinearStream.id,
contractAddress: lockupLinearStream.contract.address,
asset: {
address: getAddress(
lockupLinearStream.asset.address,
lockupLinearStream.asset.chainId,
),
name: lockupLinearStream.asset.name,
symbol: lockupLinearStream.asset.symbol,
decimals: lockupLinearStream.asset.decimals,
logo: '', // @todo - how do we get logo?
},
amount: {
bigintValue: BigInt(lockupLinearStream.depositAmount),
value: parsedAmount,
},
startDate,
endDate,
cliffDate,
isStreaming: () => {
const start = !lockupLinearStream.cliff
? startDate.getTime()
: cliffDate !== undefined
? cliffDate.getTime()
: undefined;
const end = endDate ? endDate.getTime() : undefined;
const cancelled = lockupLinearStream.canceled;
const now = new Date().getTime();

return !cancelled && !!start && !!end && start <= now && end > now;
},
};
},
const formattedLinearStreams = lockupLinearStreams.map(lockupLinearStream => {
const parsedAmount = formatUnits(
BigInt(lockupLinearStream.depositAmount),
lockupLinearStream.asset.decimals,
);

const startDate = secondsTimestampToDate(lockupLinearStream.startTime);
const endDate = secondsTimestampToDate(lockupLinearStream.endTime);
const cliffDate = lockupLinearStream.cliff
? secondsTimestampToDate(lockupLinearStream.cliffTime)
: undefined;

return {
streamId: lockupLinearStream.id,
contractAddress: lockupLinearStream.contract.address,
asset: {
address: getAddress(
lockupLinearStream.asset.address,
lockupLinearStream.asset.chainId,
),
name: lockupLinearStream.asset.name,
symbol: lockupLinearStream.asset.symbol,
decimals: lockupLinearStream.asset.decimals,
logo: '', // @todo - how do we get logo?
},
amount: {
bigintValue: BigInt(lockupLinearStream.depositAmount),
value: parsedAmount,
},
isCancelled: lockupLinearStream.canceled,
startDate,
endDate,
cliffDate,
isStreaming: () => {
const start = !lockupLinearStream.cliff
? startDate.getTime()
: cliffDate !== undefined
? cliffDate.getTime()
: undefined;
const end = endDate ? endDate.getTime() : undefined;
const cancelled = lockupLinearStream.canceled;
const now = new Date().getTime();

return !cancelled && !!start && !!end && start <= now && end > now;
},
};
});

const streamsWithCurrentWithdrawableAmounts: SablierPayment[] = await Promise.all(
formattedLinearStreams.map(async stream => {
const streamContract = getContract({
abi: SablierV2LockupLinearAbi,
address: stream.contractAddress,
client: publicClient,
});
const bigintStreamId = convertStreamIdToBigInt(stream.streamId);

const newWithdrawableAmount = await streamContract.read.withdrawableAmountOf([
bigintStreamId,
]);
return { ...stream, withdrawableAmount: newWithdrawableAmount };
}),
);

return { ...hat, payments: formattedActiveStreams };
return { ...hat, payments: streamsWithCurrentWithdrawableAmounts };
} else {
return hat;
}
Expand All @@ -237,7 +260,14 @@ const useHatsTree = () => {
}

getHatsStreams();
}, [apolloClient, hatsTree, sablierSubgraph, updateRolesWithStreams, streamsFetched]);
}, [
apolloClient,
hatsTree,
sablierSubgraph,
updateRolesWithStreams,
streamsFetched,
publicClient,
]);
};

export { useHatsTree };
Loading