Skip to content

Commit

Permalink
cleaned up the bridge component
Browse files Browse the repository at this point in the history
  • Loading branch information
jordaniza committed Jul 4, 2024
1 parent 28bd83e commit 65ca57f
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 92 deletions.
6 changes: 5 additions & 1 deletion context/Alerts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type AlertOptions = {
description?: string;
txHash?: string;
timeout?: number;
explorerLinkOverride?: string;
};

export interface AlertContextProps {
Expand Down Expand Up @@ -49,7 +50,10 @@ export const AlertProvider: React.FC<{ children: React.ReactNode }> = ({ childre
description: alertOptions?.description,
type: alertOptions?.type ?? "info",
};
if (alertOptions?.txHash && client) {
// allow the user to override the explorer link
if (alertOptions?.explorerLinkOverride) {
newAlert.explorerLink = alertOptions.explorerLinkOverride;
} else if (alertOptions?.txHash && client) {
newAlert.explorerLink = client.chain.blockExplorers?.default.url + "/tx/" + alertOptions.txHash;
}
const timeout = alertOptions?.timeout ?? DEFAULT_ALERT_TIMEOUT;
Expand Down
69 changes: 37 additions & 32 deletions plugins/toucanVoting/components/bridge/BridgeToL2.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,45 @@
import { useQueryClient } from "@tanstack/react-query";
import { useVotingTokenBalance, useVotingTokenL2Balance } from "../../hooks/useVotingToken";
import { useVotingToken, useVotingTokenBalance, useVotingTokenL2Balance } from "../../hooks/useVotingToken";
import { useEffect, useState } from "react";
import { useBridge, useBridgeQuote } from "../../hooks/useBridge";
import { useAllowance, useApproveTokens } from "../../hooks/useApproveTokens";
import { PUB_CHAIN_NAME, PUB_OFT_ADAPTER_ADDRESSS, PUB_TOKEN_L1_ADDRESS } from "@/constants";
import { useCrossChainTransaction } from "../../hooks/useCrossChainTransactions";
import { formatEther, parseEther } from "viem";
import { Button, Card, Heading, InputNumber } from "@aragon/ods";
import { Button, Card, Heading, InputNumber, Spinner } from "@aragon/ods";
import { Else, If, Then } from "@/components/if";
import Link from "next/link";
import { PleaseWaitSpinner } from "@/components/please-wait";

function isNumeric(value: string): boolean {
if (!value) return true;
return !!value.match(/^[0-9]+$/);
}

function SplitRow({ left, right }: { left: string; right: string }) {
return (
<div className="flex justify-between">
<p>{left}</p>
<p>{right}</p>
</div>
);
}

// for a given quote, if it's below 0.0001 ETH, simply write it as < 0.0001 ETH
function formatQuote(quote: string): string {
return parseFloat(quote) < 0.0001 ? "< 0.0001" : quote;
}

export default function Bridge() {
const queryClient = useQueryClient();

const { balance, status } = useVotingTokenBalance();
const { symbol } = useVotingToken();
const [tokensToSend, setTokensToSend] = useState("0");
const { balance: l2Balance, status: l2Status } = useVotingTokenL2Balance();
const { quote, status: quoteStatus } = useBridgeQuote(BigInt(tokensToSend));
const { bridgeTokens, bridgingStatus, bridgeTxHash } = useBridge();
const { approveTokens, approveStatus, isConfirmed } = useApproveTokens(PUB_TOKEN_L1_ADDRESS);
const { approveTokens, approveStatus, isConfirmed, isConfirming } = useApproveTokens(PUB_TOKEN_L1_ADDRESS);
const { allowance, queryKey } = useAllowance(PUB_TOKEN_L1_ADDRESS, PUB_OFT_ADAPTER_ADDRESSS);

const [isAllowanceEnough, setIsAllowanceEnough] = useState(false);
Expand All @@ -48,50 +64,39 @@ export default function Bridge() {
const compactQuote = formatEther(fee);

return (
<Card className="flex grow-0 flex-col gap-y-4 p-6 shadow-neutral">
<Heading size="h3">Bridge Votes</Heading>
<div className="flex flex-col gap-y-4">L1 Balance {compact}</div>
<div className="flex flex-col gap-y-4">
<p>L2 Balance {compactL2}</p>
{l2Status.isError && <p>L2 Err: {String(l2Status?.error) ?? ""}</p>}
</div>
<div className="mb-6">
<Card className="flex flex-col gap-y-4 p-6 shadow-neutral">
<Heading size="h3">Bridge Voting Tokens</Heading>
<Card className="flex flex-col gap-2 p-4 shadow-neutral">
<SplitRow left="L1 Token" right={`${compact} ${symbol ?? ""}`} />
<SplitRow left="L2 Token" right={`${compactL2} ${symbol ?? ""}`} />
</Card>
<div className="mt-4">
<InputNumber
label="Amount"
placeholder="1.234 ETH"
label="Amount To Bridge"
placeholder={`1234 ${symbol ?? ""}`}
min={0}
variant={typeof tokensToSend === "undefined" || isNumeric(tokensToSend) ? "default" : "critical"}
// max={Number(parseEther(String(balance) ?? "0"))}
onChange={(val) => setTokensToSend(parseEther(val).toString())}
/>
</div>
<div className="flex flex-col gap-y-4">
<p>Quote {compactQuote} ETH</p>
{quoteStatus.isError && <p>Quote Err: {String(quoteStatus?.error) ?? ""}</p>}
</div>

<If condition={isAllowanceEnough}>
<Then>
<Button className="max-w-xs" onClick={() => bridgeTokens(BigInt(tokensToSend), fee)}>
Bridge
</Button>
<SplitRow left="Bridge Fee" right={`${formatQuote(compactQuote)} ETH`} />
</Then>
<Else>
<Button className="max-w-xs" onClick={() => approveTokens(BigInt(tokensToSend), PUB_OFT_ADAPTER_ADDRESSS)}>
Approve
<Button
disabled={isConfirming || balance === BigInt(0)}
className="max-w-xs"
onClick={() => approveTokens(BigInt(tokensToSend), PUB_OFT_ADAPTER_ADDRESSS)}
>
{isConfirming ? <Spinner size="lg" variant="neutral" /> : "Approve"}
</Button>
</Else>
</If>
<p>Message Status: {message?.status}</p>
<p>Dst Tx Hash: {message?.dstTxHash}</p>
<If condition={scanUrl}>
<Link target="_blank" href={scanUrl} className="text-blue-500 cursor-pointer underline">
See Crosschain Transaction
</Link>
</If>
<p>Src Tx Hash: {bridgeTxHash}</p>

<If condition={bridgingStatus === "pending"}>
<p>Waiting for confirmation</p>
</If>
</Card>
);
}
4 changes: 2 additions & 2 deletions plugins/toucanVoting/hooks/useBridge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,14 @@ export function useBridge() {
// success
if (!bridgeTxHash) return;
else if (isConfirming) {
addAlert("Vote submitted", {
addAlert("Bridge request submitted", {
description: "Waiting for the transaction to be validated",
txHash: bridgeTxHash,
});
return;
} else if (!isConfirmed) return;

addAlert("Vote registered", {
addAlert("Tokens sent for bridging", {
description: "The transaction has been validated",
type: "success",
txHash: bridgeTxHash,
Expand Down
41 changes: 40 additions & 1 deletion plugins/toucanVoting/hooks/useCrossChainTransactions.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useState, useEffect } from "react";
import { getEid } from "../utils/layer-zero";
import { getMessagesBySrcTxHash, Message } from "@layerzerolabs/scan-client";
import { getMessagesBySrcTxHash, Message, MessageStatus } from "@layerzerolabs/scan-client";
import { ChainName, isTestnet } from "@/utils/chains";
import { AlertContextProps, useAlerts } from "@/context/Alerts";

function layerZeroScanURL(chainName: ChainName) {
return isTestnet(chainName) ? `https://testnet.layerzeroscan.com` : `https://layerzeroscan.com`;
Expand All @@ -10,6 +11,7 @@ function layerZeroScanURL(chainName: ChainName) {
export function useCrossChainTransaction(srcTx: `0x${string}` | undefined, srcChainName: ChainName) {
const [message, setMessage] = useState<Message | null>(null);
const [scanUrl, setScanUrl] = useState("");
useCrossChainNotifications({ message, scanUrl });
const srcEid = getEid(srcChainName);

useEffect(() => {
Expand Down Expand Up @@ -38,3 +40,40 @@ export function useCrossChainTransaction(srcTx: `0x${string}` | undefined, srcCh

return { message, scanUrl };
}

export function useCrossChainNotifications({ message, scanUrl }: { message: Message | null; scanUrl: string }) {
const { addAlert } = useAlerts() as AlertContextProps;
useEffect(() => {
if (!message || !scanUrl) return;

if (message.status === MessageStatus.INFLIGHT) {
addAlert("Cross Chain Message in Flight", {
type: "info",
timeout: 6 * 1000,
explorerLinkOverride: scanUrl,
});
return;
}

// success
if (message.status === MessageStatus.DELIVERED) {
addAlert("Crosschain message delivered", {
type: "success",
description: "The transaction has been validated on the destination chain",
explorerLinkOverride: scanUrl,
timeout: 10 * 1000,
});
return;
}

if (message.status === MessageStatus.FAILED) {
addAlert("Crosschain message unsuccessful", {
description: "There was an error in the crosschain message",
type: "error",
explorerLinkOverride: scanUrl,
timeout: 10 * 1000,
});
return;
}
}, [message, scanUrl]);
}
1 change: 0 additions & 1 deletion plugins/toucanVoting/hooks/useUserCanVote.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export function useUserCanVote(proposalId: string) {

useEffect(() => {
refreshCanVote();
console.log("CanVote:", canVote);
}, [blockNumber]);

return canVote;
Expand Down
117 changes: 62 additions & 55 deletions plugins/toucanVoting/pages/proposal-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,71 +89,78 @@ export default function Proposals() {
</div>
</div>

<div className="flex w-full flex-col gap-x-12 gap-y-6 md:flex-row">
<If condition={proposalCount}>
<Then>
<DataList.Root
entityLabel={entityLabel}
itemsCount={proposalCount}
pageSize={DEFAULT_PAGE_SIZE}
state={dataListState}
//onLoadMore={fetchNextPage}
>
<DataList.Container
SkeletonElement={ProposalDataListItemSkeleton}
errorState={errorState}
emptyFilteredState={emptyFilteredState}
<div
className="mx-auto flex w-full max-w-screen-xl flex-col justify-center
gap-5 md:flex-row md:pb-20 "
>
<div className="flex w-full flex-col gap-x-12 gap-y-6 md:w-auto md:flex-row">
<If condition={proposalCount}>
<Then>
<DataList.Root
entityLabel={entityLabel}
itemsCount={proposalCount}
pageSize={DEFAULT_PAGE_SIZE}
state={dataListState}
//onLoadMore={fetchNextPage}
>
{proposalCount &&
Array.from(Array(proposalCount)?.keys())
.reverse()
?.map((proposalIndex, index) => (
// TODO: update with router agnostic ODS DataListItem
<ProposalCard key={proposalIndex} proposalId={BigInt(proposalIndex)} />
))}
</DataList.Container>
<DataList.Pagination />
</DataList.Root>
</Then>
<Else>
<div className="w-full">
<p className="text-md text-neutral-400">
No proposals have been created yet. Here you will see the proposals created by the Security Council
before they can be submitted to the{" "}
<Link href="/plugins/community-proposals/#/" className="underline">
community voting stage
</Link>
.
</p>
<IllustrationHuman
className="mx-auto mb-10 max-w-72"
body="BLOCKS"
expression="SMILE_WINK"
hairs="CURLY"
/>
<If condition={isConnected && canCreate}>
<div className="flex justify-center">
<Link href="#/new">
<Button iconLeft={IconType.PLUS} size="md" variant="primary">
Submit Proposal
</Button>
<DataList.Container
SkeletonElement={ProposalDataListItemSkeleton}
errorState={errorState}
emptyFilteredState={emptyFilteredState}
>
{proposalCount &&
Array.from(Array(proposalCount)?.keys())
.reverse()
?.map((proposalIndex, index) => (
// TODO: update with router agnostic ODS DataListItem
<ProposalCard key={proposalIndex} proposalId={BigInt(proposalIndex)} />
))}
</DataList.Container>
<DataList.Pagination />
</DataList.Root>
</Then>
<Else>
<div className="w-full">
<p className="text-md text-neutral-400">
No proposals have been created yet. Here you will see the proposals created by the Security Council
before they can be submitted to the{" "}
<Link href="/plugins/community-proposals/#/" className="underline">
community voting stage
</Link>
</div>
</If>
</div>
</Else>
</If>
.
</p>
<IllustrationHuman
className="mx-auto mb-10 max-w-72"
body="BLOCKS"
expression="SMILE_WINK"
hairs="CURLY"
/>
<If condition={isConnected && canCreate}>
<div className="flex justify-center">
<Link href="#/new">
<Button iconLeft={IconType.PLUS} size="md" variant="primary">
Submit Proposal
</Button>
</Link>
</div>
</If>
</div>
</Else>
</If>
</div>
<div>
<Bridge />
</div>
</div>
</SectionView>
<Bridge />
</MainSection>
);
}

function MainSection({ children }: { children: ReactNode }) {
return <main className="w-full md:px-6 md:pb-20 xl:pt-10">{children}</main>;
return <main className="w-full p-4 md:px-6 md:pb-20 xl:pt-10">{children}</main>;
}

function SectionView({ children }: { children: ReactNode }) {
return <div className="mx-auto flex w-full max-w-[768px] flex-col items-center gap-y-6 md:px-6">{children}</div>;
return <div className="mx-auto flex w-full flex-col items-center gap-y-6 md:px-6">{children}</div>;
}

0 comments on commit 65ca57f

Please sign in to comment.