From 67a3a21a9f48ebcbda1540a2491b8db73c0f5cf0 Mon Sep 17 00:00:00 2001 From: Gerhard Steenkamp Date: Fri, 20 Sep 2024 14:49:13 +0200 Subject: [PATCH 1/3] get quote --- apps/example/app/globals.css | 49 ++-- apps/example/app/page.tsx | 6 +- apps/example/app/viem/components/Bridge.tsx | 189 +++++++++++++ .../app/viem/components/ConnectButton.tsx | 2 +- apps/example/app/viem/components/Header.tsx | 6 +- apps/example/app/viem/components/index.ts | 1 + apps/example/app/viem/providers.tsx | 17 +- apps/example/components/ChainSelect.tsx | 65 +++++ apps/example/components/Divider.tsx | 24 ++ apps/example/components/TokenSelect.tsx | 61 +++++ apps/example/components/ui/button.tsx | 8 +- apps/example/components/ui/dropdown-menu.tsx | 200 ++++++++++++++ apps/example/components/ui/index.ts | 8 + apps/example/components/ui/input.tsx | 25 ++ apps/example/components/ui/label.tsx | 26 ++ apps/example/components/ui/select.tsx | 163 +++++++++++ apps/example/components/ui/skeleton.tsx | 15 + apps/example/components/ui/sonner.tsx | 31 +++ apps/example/components/ui/tooltip.tsx | 2 +- apps/example/lib/across.tsx | 23 ++ apps/example/lib/chains.ts | 9 + apps/example/lib/hooks/useAvailableRoutes.tsx | 27 ++ apps/example/lib/hooks/useInputTokens.tsx | 25 ++ apps/example/lib/hooks/useOutputTokens.tsx | 25 ++ apps/example/lib/hooks/useQuote.tsx | 35 +++ .../lib/hooks/useSupportedAcrossChains.tsx | 28 ++ apps/example/lib/utils.ts | 35 +++ apps/example/lib/wagmi.ts | 4 +- apps/example/package.json | 5 + apps/example/tailwind.config.ts | 13 +- packages/sdk/src/utils/getSupportedChains.ts | 6 +- packages/sdk/src/utils/logger.ts | 3 +- pnpm-lock.yaml | 259 ++++++++++++++++++ 33 files changed, 1349 insertions(+), 46 deletions(-) create mode 100644 apps/example/app/viem/components/Bridge.tsx create mode 100644 apps/example/components/ChainSelect.tsx create mode 100644 apps/example/components/Divider.tsx create mode 100644 apps/example/components/TokenSelect.tsx create mode 100644 apps/example/components/ui/dropdown-menu.tsx create mode 100644 apps/example/components/ui/index.ts create mode 100644 apps/example/components/ui/input.tsx create mode 100644 apps/example/components/ui/label.tsx create mode 100644 apps/example/components/ui/select.tsx create mode 100644 apps/example/components/ui/skeleton.tsx create mode 100644 apps/example/components/ui/sonner.tsx create mode 100644 apps/example/lib/across.tsx create mode 100644 apps/example/lib/chains.ts create mode 100644 apps/example/lib/hooks/useAvailableRoutes.tsx create mode 100644 apps/example/lib/hooks/useInputTokens.tsx create mode 100644 apps/example/lib/hooks/useOutputTokens.tsx create mode 100644 apps/example/lib/hooks/useQuote.tsx create mode 100644 apps/example/lib/hooks/useSupportedAcrossChains.tsx diff --git a/apps/example/app/globals.css b/apps/example/app/globals.css index cc12b1d..c75e022 100644 --- a/apps/example/app/globals.css +++ b/apps/example/app/globals.css @@ -5,6 +5,10 @@ :root { --background: 0 0% 98%; --foreground: 0 0% 100%; + --muted: 0 0% 100%; + + --popover: 0 0% 100%; + --text-color: 93 17% 10%; /* across teal */ @@ -12,33 +16,44 @@ /* 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; + } } diff --git a/apps/example/app/page.tsx b/apps/example/app/page.tsx index 96aff5d..fe1b32f 100644 --- a/apps/example/app/page.tsx +++ b/apps/example/app/page.tsx @@ -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 (
-
-
+
+
); diff --git a/apps/example/app/viem/components/Bridge.tsx b/apps/example/app/viem/components/Bridge.tsx new file mode 100644 index 0000000..67a25b4 --- /dev/null +++ b/apps/example/app/viem/components/Bridge.tsx @@ -0,0 +1,189 @@ +"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( + defaultOriginChainId, + ); + + // FROM TOKEN + const { inputTokens } = useInputTokens(originChainId); + const [fromTokenAddress, setFromTokenAddress] = useState
( + 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(); + + useEffect(() => { + const _outputTokens = outputTokensForChain?.filter((token) => + outputTokensForRoute?.includes(token.address.toLowerCase()), + ); + setOutputTokens(_outputTokens); + }, [availableRoutes]); + + const [toTokenAddress, setToTokenAddress] = useState
( + 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(); + const debouncedInputAmount = useDebounce(inputAmount, 300); + const route = availableRoutes?.find( + (route) => + route.outputToken.toLocaleLowerCase() === toTokenAddress?.toLowerCase(), + ); + + const quoteConfig = + route && address && debouncedInputAmount && inputToken + ? { + route, + recipient: address, + inputAmount: parseUnits(debouncedInputAmount, inputToken?.decimals), + } + : undefined; + + const { quote, isLoading: quoteLoading } = useQuote(quoteConfig); + + return ( + <> +
+
+ +
+ { + setDestinationChainId(undefined); + setOriginChainId(parseInt(chainId)); + }} + /> + + setFromTokenAddress(value as Address)} + value={fromTokenAddress} + /> +
+ + + + +
+ + setDestinationChainId(parseInt(chainId)) + } + /> + + 1) : true} + tokens={outputTokens} + onValueChange={(value) => setToTokenAddress(value as Address)} + value={toTokenAddress} + /> +
+ + + + + setInputAmount(e.currentTarget.value)} + /> +
+
+ +
+ + {!quote && quoteLoading && ( + + fetching quote... + + )} + {quote && toToken && ( +

+ {parseFloat( + formatUnits(quote.deposit.outputAmount, toToken.decimals), + ).toFixed(3)} +

+ )} + +
+ + ); +} diff --git a/apps/example/app/viem/components/ConnectButton.tsx b/apps/example/app/viem/components/ConnectButton.tsx index 5eeff6d..5d45147 100644 --- a/apps/example/app/viem/components/ConnectButton.tsx +++ b/apps/example/app/viem/components/ConnectButton.tsx @@ -73,7 +73,7 @@ const HamburgerButton = (props: ButtonProps) => {