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

Build out Bridge UI #15

Merged
merged 3 commits into from
Sep 20, 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: 32 additions & 17 deletions apps/example/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,55 @@
:root {
--background: 0 0% 98%;
--foreground: 0 0% 100%;
--muted: 0 0% 100%;

--popover: 0 0% 100%;

--text-color: 93 17% 10%;

/* across teal */
--accent: 166 92% 70%;
/* gray */
--border: 220 14% 96%;

--ring: 0 100% 65%;

--black: 93 17% 10%;
--white: 0, 0%, 100%;
--ring: 166 92% 70%;

/* Leaving this here if we want to have different status colors for light/dark */
--success: 119 90% 35%;
--fail: 0 100% 65%;
--loading: 50 52% 66%;
--idle: 0 0% 68%;
--destructive: 0 100% 65%;
}

.dark {
--background: 0 0% 12%;
--foreground: 0 0% 16%;
--text-color: 0 0% 100%;
--background: 230 6% 19%;
--foreground: 231.43 6.31% 21.76%;

/* gray */
--border: 0 0% 21%;
--muted: 231.43 6.31% 21.76%;

--popover: 240 5.88% 13.33%;

--chainlink-blue: #2a5ada;
--text-color: 204 30% 83%;

--ring: 0 100% 65%;
/* gray */
--border: 226.67 5.52% 31.96%;
--border-secondary: 226.67 6.77% 26.08%;

--ring: 226.67 6.77% 60%;

/* Leaving this here if we want to have different status colors for light/dark */
--success: 133 56% 55%;
--fail: 0 95% 66%;
--loading: 50 52% 66%;
--idle: 0 0 68%;
--destructive: 0 95% 66%;
}

* {
font-weight: 200;
}

/* remove arrows from number input */
@layer base {
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button,
input[type="number"] {
-webkit-appearance: none;
-moz-appearance: textfield !important;
}
}
6 changes: 3 additions & 3 deletions apps/example/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Header } from "./viem/components";
import { Header, Bridge } from "./viem/components";
import { Providers } from "./viem/providers";

export default function Home() {
return (
<Providers>
<Header />
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div></div>
<main className="flex min-h-screen flex-col items-center justify-start px-3 sm:px-6 pt-36">
<Bridge />
</main>
</Providers>
);
Expand Down
187 changes: 187 additions & 0 deletions apps/example/app/viem/components/Bridge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
"use client";

import { ChainSelect } from "@/components/ChainSelect";
import { Divider } from "@/components/Divider";
import { TokenSelect } from "@/components/TokenSelect";
import { Button, Input, Label, Skeleton } from "@/components/ui";
import { useAvailableRoutes } from "@/lib/hooks/useAvailableRoutes";
import { useInputTokens } from "@/lib/hooks/useInputTokens";
import { useOutputTokens } from "@/lib/hooks/useOutputTokens";
import { useQuote } from "@/lib/hooks/useQuote";
import { useSupportedAcrossChains } from "@/lib/hooks/useSupportedAcrossChains";
import { cn, reduceAcrossChains } from "@/lib/utils";
import { TokenInfo } from "@across-toolkit/sdk";
import { useEffect, useState } from "react";
import { Address, formatUnits, parseUnits } from "viem";
import { useAccount, useChains } from "wagmi";
import { useDebounce } from "@uidotdev/usehooks";

export function Bridge() {
const { address } = useAccount();
const chains = useChains();
// CHAINS

const { supportedChains } = useSupportedAcrossChains({});

// use only token data for chains we support
const acrossChains = reduceAcrossChains(supportedChains, [...chains]);

// Optimism default input chain
const defaultOriginChainId = chains.find((chain) => chain.id === 10)?.id;
const [originChainId, setOriginChainId] = useState<number | undefined>(
defaultOriginChainId,
);

// FROM TOKEN
const { inputTokens } = useInputTokens(originChainId);
const [fromTokenAddress, setFromTokenAddress] = useState<Address | undefined>(
inputTokens?.[2]?.address,
);
const inputToken = inputTokens?.find(
(token) => token.address.toLowerCase() === fromTokenAddress?.toLowerCase(),
);

const [destinationChainId, setDestinationChainId] = useState<
number | undefined
>(chains.find((chain) => chain.id !== originChainId)?.id);

const { availableRoutes } = useAvailableRoutes({
originChainId,
destinationChainId,
originToken: fromTokenAddress,
});

const outputTokensForRoute = availableRoutes?.map((route) =>
route.outputToken.toLowerCase(),
);

const { outputTokens: outputTokensForChain } =
useOutputTokens(destinationChainId);

const [outputTokens, setOutputTokens] = useState<TokenInfo[] | undefined>();

useEffect(() => {
const _outputTokens = outputTokensForChain?.filter((token) =>
outputTokensForRoute?.includes(token.address.toLowerCase()),
);
setOutputTokens(_outputTokens);
}, [availableRoutes]);

const [toTokenAddress, setToTokenAddress] = useState<Address | undefined>(
outputTokens?.[0]?.address,
);
const toToken = outputTokens?.find(
(token) => token.address.toLowerCase() === toTokenAddress?.toLowerCase(),
);

useEffect(() => {
if (outputTokens) {
setToTokenAddress(outputTokens?.[0]?.address);
}
}, [outputTokens]);

const [inputAmount, setInputAmount] = useState<string>();
const debouncedInputAmount = useDebounce(inputAmount, 300);
const route = availableRoutes?.find(
(route) =>
route.outputToken.toLocaleLowerCase() === toTokenAddress?.toLowerCase(),
);

const quoteConfig =
route && debouncedInputAmount && inputToken
? {
route,
recipient: address,
inputAmount: parseUnits(debouncedInputAmount, inputToken?.decimals),
}
: undefined;

const { quote, isLoading: quoteLoading } = useQuote(quoteConfig);

return (
<>
<div className="bg-foreground border border-border-secondary p-6 w-full max-w-[600px] rounded-[10px]">
<div className="flex flex-col gap-4">
<Label htmlFor="origin-chain">From</Label>
<div className="w-full flex flex-col sm:flex-row justify-start items-center gap-2">
<ChainSelect
disabled={!supportedChains}
className="flex-[5]"
id="origin-chain"
chains={acrossChains}
chain={originChainId}
onChainChange={(chainId) => {
setDestinationChainId(undefined);
setOriginChainId(chainId);
}}
/>

<TokenSelect
className="flex-[3]"
tokens={inputTokens}
onTokenChange={setFromTokenAddress}
token={fromTokenAddress}
/>
</div>

<Divider className="my-2" />

<Label htmlFor="destination-chain">To</Label>
<div className="w-full flex flex-col sm:flex-row justify-start items-center gap-2">
<ChainSelect
disabled={!supportedChains}
className="flex-[5]"
id="destination-chain"
chains={acrossChains}
chain={destinationChainId}
onChainChange={setDestinationChainId}
/>

<TokenSelect
className={cn("flex-[3]")}
disabled={outputTokens ? !(outputTokens?.length > 1) : true}
tokens={outputTokens}
onTokenChange={setToTokenAddress}
token={toTokenAddress}
/>
</div>

<Divider className="my-4" />

<Label htmlFor="input-amount">Send</Label>
<Input
className="flex-[5]"
id="input-amount"
placeholder="Enter amount"
type="number"
value={inputAmount}
onChange={(e) => setInputAmount(e.currentTarget.value)}
/>
</div>
</div>

<div className="mt-4 flex flex-col items-start gap-2 bg-foreground border border-border-secondary p-6 w-full max-w-[600px] rounded-[10px]">
<Label>Receive</Label>
{!quote && quoteLoading && (
<Skeleton className="text-md font-normal text-text/80">
fetching quote...
</Skeleton>
)}
{quote && toToken && (
<p className="text-md font-normal text-text">
{parseFloat(
formatUnits(quote.deposit.outputAmount, toToken.decimals),
).toFixed(3)}
</p>
)}
<Button
disabled={!(quote && toToken)}
className="mt-2"
variant="accent"
>
Confirm Transaction
</Button>
</div>
</>
);
}
2 changes: 1 addition & 1 deletion apps/example/app/viem/components/ConnectButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const HamburgerButton = (props: ButtonProps) => {
<Button
size="icon"
variant="bordered"
className="h-[40px] w-[40px]"
className="h-[40px] w-[40px] rounded-full"
{...props}
>
<Icon name="hamburger" className="h-[20px] w-[20px] text-text/75" />
Expand Down
6 changes: 4 additions & 2 deletions apps/example/app/viem/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ export const Header = ({
return (
<div
className={cn(
"fixed top-0 z-20 flex w-full items-center justify-between bg-transparent p-4 md:p-5",
"fixed top-0 z-20 h-[72px] flex w-full items-center justify-between bg-transparent px-4 md:px-6",
className,
)}
{...props}
>
<div className="relative flex items-center justify-start gap-2">
<Icon name="across-logo" className="h-8 w-8 text-accent" />
<h2 className="text-gradient-oval text-2xl">SDK Example - Viem</h2>
<h2 className="text-text font-extralight text-xl">
Integrator Toolkit Demo
</h2>
</div>

<ConnectButton className="relative" />
Expand Down
1 change: 1 addition & 0 deletions apps/example/app/viem/components/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./Header";
export * from "./Bridge";
17 changes: 10 additions & 7 deletions apps/example/app/viem/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,23 @@ import {
} from "@rainbow-me/rainbowkit";
import { ThemeProvider, useTheme } from "next-themes";
import { config } from "@/lib/wagmi";
import { AcrossProvider } from "@/lib/across";

const queryClient = new QueryClient();

export function Providers({ children }: { children: React.ReactNode }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem={true}
>
<RainbowProvider>{children}</RainbowProvider>
</ThemeProvider>
<AcrossProvider>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem={true}
>
<RainbowProvider>{children}</RainbowProvider>
</ThemeProvider>
</AcrossProvider>
</QueryClientProvider>
</WagmiProvider>
);
Expand Down
Loading