Skip to content

Commit

Permalink
dTWAP and dLIMIT by Orbs
Browse files Browse the repository at this point in the history
  • Loading branch information
denis-orbs committed Aug 12, 2024
1 parent 7f5f013 commit 39b6f43
Show file tree
Hide file tree
Showing 9 changed files with 603 additions and 159 deletions.
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@next/bundle-analyzer": "^14.2.3",
"@octokit/auth-app": "4.0.7",
"@pontem/wallet-adapter-plugin": "^0.2.1",
"@orbs-network/twap-ui-sushiswap": "1.1.21",
"@radix-ui/react-slot": "1.0.2",
"@rainbow-me/rainbowkit": "2.1.3",
"@rise-wallet/wallet-adapter": "^0.1.2",
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/app/(evm)/swap/limit/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from "react";
import SimpleSwapLoading from "../(simple)/loading";

export default function Loader() {
return <SimpleSwapLoading />;
}
18 changes: 18 additions & 0 deletions apps/web/src/app/(evm)/swap/limit/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Container } from '@sushiswap/ui'
import dynamic from 'next/dynamic'
import { Providers } from '../(simple)/providers'
const LimitPanel = dynamic(() => import('src/ui/swap/twap/twap').then(it => it.LimitPanel), {ssr: false})

export const metadata = {
title: 'SushiSwap',
}

export default function SwapLimitPage() {
return (
<Providers>
<Container maxWidth="lg" className="px-4">
<LimitPanel />
</Container>
</Providers>
)
}
7 changes: 7 additions & 0 deletions apps/web/src/app/(evm)/swap/twap/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from "react";
import SimpleSwapLoading from "../(simple)/loading";

export default function Loader() {
return <SimpleSwapLoading />;

}
21 changes: 21 additions & 0 deletions apps/web/src/app/(evm)/swap/twap/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Container } from '@sushiswap/ui'
import { Providers } from '../(simple)/providers'
import dynamic from 'next/dynamic'


const TWAPPanel = dynamic(() => import('src/ui/swap/twap/twap').then(it => it.TWAPPanel), {ssr: false})

export const metadata = {
title: 'SushiSwap',
}

export default function SwapTwapPage() {
return (
<Providers>
<Container maxWidth="lg" className="px-4">
<TWAPPanel />
</Container>
</Providers>
)
}

10 changes: 10 additions & 0 deletions apps/web/src/ui/swap/swap-mode-buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ export const SwapModeButtons = () => {
Swap
</PathnameButton>
</Link>
<Link href="/swap/limit">
<PathnameButton pathname="/swap/limit" size="sm">
Limit
</PathnameButton>
</Link>
<Link href="/swap/twap">
<PathnameButton pathname="/swap/twap" size="sm">
TWAP
</PathnameButton>
</Link>
<HoverCard>
<Link href="/swap/cross-chain">
<PathnameButton pathname="/swap/cross-chain" size="sm">
Expand Down
267 changes: 267 additions & 0 deletions apps/web/src/ui/swap/twap/twap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
"use client";

import {
TWAP as TwapContainer,
supportedChains,
} from "@orbs-network/twap-ui-sushiswap";
import { useConnectModal } from "@rainbow-me/rainbowkit";
import { useAccount, useChainId, useSwitchChain} from "wagmi";
import { ChainId } from 'sushi/chain'
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
Tooltip as SushiTooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
NetworkSelector,
Button
} from "@sushiswap/ui";
import {
ExclamationCircleIcon,
} from '@heroicons/react/20/solid'
import { Currency } from "sushi/currency";
import { ReactNode, useCallback, useEffect, useMemo } from "react";
import { useSortedTokenList } from "src/lib/wagmi/components/token-selector/hooks/useSortedTokenList";
import { useCustomTokens } from "@sushiswap/hooks";
import {
useOtherTokenListsQuery,
usePrice,
useTokens,
} from "@sushiswap/react-query";
import { useTheme } from "next-themes";

import { Address } from "viem";
import { TokenSelector } from "src/lib/wagmi/components/token-selector/TokenSelector";
import { SimpleSwapHeader } from "../simple/simple-swap-header";
import { SimpleSwapBridgeBanner } from "../simple/simple-swap-bridge-banner";
import { SwapModeButtons } from "../swap-mode-buttons";
import { SimpleSwapSettingsOverlay } from "../simple/simple-swap-settings-overlay";
import { useDerivedStateSimpleSwap, useSimpleSwapTrade } from "../simple/derivedstate-simple-swap-provider";


const Modal = ({
open,
onClose,
title,
children,
header,
}: {
open: boolean;
onClose: () => void;
title?: string;
children?: ReactNode;
header?: ReactNode;
}) => {
return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent>
<DialogHeader>
{header}
{title && <DialogTitle>{title}</DialogTitle>}
</DialogHeader>
{children}
</DialogContent>
</Dialog>
);
};

const TokenSelectModal = ({
selected,
children,
onSelect,
}: {
selected: Currency;
children: ReactNode;
onSelect: (currency: Currency) => void;
}) => {
const { state: { chainId } } = useDerivedStateSimpleSwap()

return (
<TokenSelector
id={`twap-token-selector`}
selected={selected}
chainId={chainId}
onSelect={onSelect}
includeNative={true}
hidePinnedTokens={false}
hideSearch={false}
>
{children}
</TokenSelector>
);
};


const useTrade = () => {
const { data: trade, isInitialLoading } = useSimpleSwapTrade();

return {
isLoading: isInitialLoading,
outAmount: trade?.amountOut?.quotient.toString()
};
};

const usePriceUSD = (address?: Address) => {
const { state: { chainId } } = useDerivedStateSimpleSwap()

const { data: price } = usePrice({
chainId,
enabled: true,
address,
});

return price?.toSignificant(6);
};

const getTokenLogo = (currency: Currency) => {
try {
const src = currency.isNative
? `native-currency/ethereum.svg`
: `tokens/${currency.chainId}/${currency.wrapped.address}.jpg`;
const params = ["f_auto", "c_limit", `w_${64}`];
return `https://cdn.sushi.com/image/upload/${params.join(
","
)}/d_unknown.png/${src}`;
} catch (error) {
return "";
}
};

const useTokenList = () => {
const { data: customTokenMap } = useCustomTokens();
const { state: { chainId } } = useDerivedStateSimpleSwap()

const { data: otherTokenMap } = useOtherTokenListsQuery({
chainId,
query: undefined,
});
const { data: defaultTokenMap } = useTokens({
chainId,
});

const tokenMap = useMemo(() => {
return {
...defaultTokenMap,
...otherTokenMap,
};
}, [defaultTokenMap, otherTokenMap]);

const { data: sortedTokenList } = useSortedTokenList({
query: "",
customTokenMap,
tokenMap,
chainId,
includeNative: true,
});

return sortedTokenList;
};

const Tooltip = ({tooltipText}:any) => {
return <TooltipProvider>
<SushiTooltip delayDuration={0}>
<TooltipTrigger asChild>
<ExclamationCircleIcon
width={16}
height={16}
/>
</TooltipTrigger>
<TooltipContent className="bg-background !p-4 shadow-xl w-80">
<p>{tooltipText}</p>
</TooltipContent>
</SushiTooltip>
</TooltipProvider>
}



const TwapNetworkSelector = ({children}:{children: ReactNode}) => {
const { switchChain } = useSwitchChain()
const chainId = useChainId()
const onSelect = useCallback(
(chainId: number) => {
switchChain({chainId: chainId as ChainId})
},
[switchChain],
)


return <NetworkSelector selected={chainId} networks={supportedChains} onSelect={onSelect}>
{children}
</NetworkSelector>
}


const TwapButton = ({disabled, children}:{disabled?: boolean, children: ReactNode}) => {

return <Button
size="xl"
disabled={Boolean(disabled)}
color={'blue'}
fullWidth
testId="swap-twap"
>
{children}
</Button>
}

function Provider({ isLimit }: { isLimit?: boolean }) {
const { openConnectModal } = useConnectModal();
const { connector } = useAccount();
const { state, mutate } = useDerivedStateSimpleSwap();
const { resolvedTheme } = useTheme();
const tokens = useTokenList();
const connectedChainId = useChainId()

useEffect(() => {
// we do this to get an indication of market price for single token
if (state.swapAmountString !== "1") {
mutate.setSwapAmount("1");
}
}, [state.swapAmountString]);

return (
<div className="flex flex-col gap-4">
<SimpleSwapBridgeBanner />
<SimpleSwapHeader />
<div className="flex items-center justify-between">
<SwapModeButtons />
<SimpleSwapSettingsOverlay />
</div>

<TwapContainer
TokenSelectModal={TokenSelectModal}
Modal={Modal}
connect={openConnectModal}
account={state.recipient}
limit={isLimit}
useTrade={useTrade}
connector={connector}
dappTokens={tokens}
srcToken={state.token0}
dstToken={state.token1}
getTokenLogo={getTokenLogo}
onSrcTokenSelected={mutate.setToken0}
onDstTokenSelected={mutate.setToken1}
useUSD={usePriceUSD}
isDarkTheme={resolvedTheme === "dark"}
onSwitchTokens={mutate.switchTokens}
configChainId={state.chainId}
connectedChainId={connectedChainId}
Tooltip={Tooltip}
NetworkSelector={TwapNetworkSelector}
Button={TwapButton}
/>
</div>
);
}
export const LimitPanel = () => {
return <Provider isLimit={true} />;
};

export const TWAPPanel = () => {
return <Provider />;
};
1 change: 1 addition & 0 deletions config/nextjs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const defaultNextConfig = {
config.plugins.push(
new webpack.IgnorePlugin({
resourceRegExp: /^(lokijs|pino-pretty|encoding)$/,
resourceRegExp: /^(lokijs|pino-pretty|encoding|keyv)$/,
}),
)
}
Expand Down
Loading

0 comments on commit 39b6f43

Please sign in to comment.