swap-ui/src/swap/components/Swap.tsx

299 lines
7.1 KiB
TypeScript
Raw Normal View History

2021-05-13 00:28:32 -07:00
import { useState } from "react";
2021-05-12 13:10:52 -07:00
import { PublicKey } from "@solana/web3.js";
import { BN } from "@project-serum/anchor";
2021-05-12 13:10:52 -07:00
import {
makeStyles,
Card,
Button,
Paper,
Typography,
TextField,
} from "@material-ui/core";
2021-05-14 11:30:21 -07:00
import { ExpandMore } from "@material-ui/icons";
import { useSwapContext } from "../context/Swap";
import {
useDexContext,
useOpenOrders,
2021-05-16 15:52:38 -07:00
useRouteVerbose,
useMarket,
} from "../context/Dex";
2021-05-17 12:01:35 -07:00
import { useTokenMap } from "../context/TokenList";
2021-05-17 17:21:25 -07:00
import { useMint, useOwnedTokenAccount } from "../context/Token";
2021-05-18 01:26:03 -07:00
import { useCanSwap, useReferral } from "../context/Swap";
2021-05-13 00:28:32 -07:00
import TokenDialog from "./TokenDialog";
2021-05-13 21:04:15 -07:00
import { SettingsButton } from "./Settings";
2021-05-14 11:30:21 -07:00
import { InfoLabel } from "./Info";
2021-05-12 13:10:52 -07:00
2021-05-13 00:28:32 -07:00
const useStyles = makeStyles(() => ({
2021-05-12 13:10:52 -07:00
card: {
width: "450px",
borderRadius: "10px",
2021-05-13 00:28:32 -07:00
border: "solid 1pt #e0e0e0",
2021-05-12 13:10:52 -07:00
},
cardContent: {
marginLeft: "6px",
marginRight: "6px",
marginBottom: "6px",
},
tab: {
width: "50%",
},
settingsButton: {
padding: 0,
},
swapButton: {
width: "100%",
borderRadius: "15px",
},
swapToFromButton: {
display: "block",
marginLeft: "auto",
marginRight: "auto",
},
}));
export default function SwapCard({ style }: { style?: any }) {
2021-05-12 13:10:52 -07:00
const styles = useStyles();
return (
<div style={style}>
<Card className={styles.card}>
<SwapHeader />
<div className={styles.cardContent}>
<SwapFromForm />
2021-05-14 11:30:21 -07:00
<ArrowButton />
2021-05-12 13:10:52 -07:00
<SwapToForm />
2021-05-14 11:30:21 -07:00
<InfoLabel />
2021-05-12 13:10:52 -07:00
<SwapButton />
</div>
</Card>
</div>
);
}
function SwapHeader() {
return (
<div
style={{
display: "flex",
justifyContent: "space-between",
margin: "8px",
}}
>
<Typography
style={{
fontWeight: "bold",
display: "flex",
justifyContent: "center",
flexDirection: "column",
}}
>
Swap
</Typography>
<SettingsButton />
</div>
);
}
2021-05-14 11:30:21 -07:00
export function ArrowButton() {
2021-05-12 13:10:52 -07:00
const styles = useStyles();
const { swapToFromMints } = useSwapContext();
return (
<Button className={styles.swapToFromButton} onClick={swapToFromMints}>
</Button>
);
}
function SwapFromForm() {
const { fromMint, setFromMint, fromAmount, setFromAmount } = useSwapContext();
return (
<SwapTokenForm
mint={fromMint}
setMint={setFromMint}
amount={fromAmount}
setAmount={setFromAmount}
/>
);
}
function SwapToForm() {
const { toMint, setToMint, toAmount, setToAmount } = useSwapContext();
return (
<SwapTokenForm
mint={toMint}
setMint={setToMint}
amount={toAmount}
setAmount={setToAmount}
/>
);
}
function SwapTokenForm({
mint,
setMint,
amount,
setAmount,
}: {
mint: PublicKey;
setMint: (m: PublicKey) => void;
amount: number;
setAmount: (a: number) => void;
}) {
2021-05-13 00:28:32 -07:00
const [showTokenDialog, setShowTokenDialog] = useState(false);
2021-05-12 13:10:52 -07:00
const tokenAccount = useOwnedTokenAccount(mint);
2021-05-13 01:11:13 -07:00
const mintAccount = useMint(mint);
2021-05-12 13:10:52 -07:00
return (
2021-05-13 01:11:13 -07:00
<Paper elevation={0} variant="outlined" style={{ borderRadius: "10px" }}>
2021-05-12 13:10:52 -07:00
<div
style={{
height: "50px",
display: "flex",
justifyContent: "space-between",
}}
>
2021-05-13 00:28:32 -07:00
<TokenButton mint={mint} onClick={() => setShowTokenDialog(true)} />
2021-05-12 13:10:52 -07:00
<TextField
type="number"
value={amount}
onChange={(e) => setAmount(parseFloat(e.target.value))}
style={{
display: "flex",
justifyContent: "center",
flexDirection: "column",
}}
/>
</div>
<div style={{ marginLeft: "10px", height: "30px" }}>
<Typography color="textSecondary" style={{ fontSize: "14px" }}>
{tokenAccount && mintAccount
? `Balance: ${(
tokenAccount.account.amount.toNumber() /
10 ** mintAccount.decimals
).toFixed(mintAccount.decimals)}`
: `-`}
</Typography>
</div>
2021-05-13 00:28:32 -07:00
<TokenDialog
setMint={setMint}
open={showTokenDialog}
onClose={() => setShowTokenDialog(false)}
/>
2021-05-12 13:10:52 -07:00
</Paper>
);
}
2021-05-13 00:28:32 -07:00
function TokenButton({
mint,
onClick,
}: {
mint: PublicKey;
onClick: () => void;
}) {
2021-05-12 13:10:52 -07:00
return (
2021-05-15 21:20:11 -07:00
<Button onClick={onClick} style={{ minWidth: "116px" }}>
2021-05-13 01:11:13 -07:00
<TokenIcon mint={mint} style={{ width: "25px", borderRadius: "13px" }} />
2021-05-12 13:10:52 -07:00
<TokenName mint={mint} />
<ExpandMore />
</Button>
);
}
2021-05-13 00:28:32 -07:00
export function TokenIcon({ mint, style }: { mint: PublicKey; style: any }) {
2021-05-15 16:16:28 -07:00
const tokenMap = useTokenMap();
let tokenInfo = tokenMap.get(mint.toString());
2021-05-12 13:10:52 -07:00
return (
<div
style={{
display: "flex",
justifyContent: "center",
flexDirection: "column",
}}
>
2021-05-15 16:16:28 -07:00
{tokenInfo?.logoURI ? (
2021-05-15 21:20:11 -07:00
<img alt="Logo" style={style} src={tokenInfo?.logoURI} />
2021-05-13 00:28:32 -07:00
) : (
<div style={style}></div>
)}
2021-05-12 13:10:52 -07:00
</div>
);
}
function TokenName({ mint }: { mint: PublicKey }) {
2021-05-15 16:16:28 -07:00
const tokenMap = useTokenMap();
let tokenInfo = tokenMap.get(mint.toString());
2021-05-12 13:10:52 -07:00
return (
2021-05-15 16:16:28 -07:00
<Typography style={{ marginLeft: "5px" }}>{tokenInfo?.symbol}</Typography>
2021-05-12 13:10:52 -07:00
);
}
function SwapButton() {
const styles = useStyles();
2021-05-15 12:58:05 -07:00
const {
fromMint,
toMint,
fromAmount,
toAmount,
slippage,
isClosingNewAccounts,
} = useSwapContext();
2021-05-15 00:39:56 -07:00
const { swapClient } = useDexContext();
const fromMintInfo = useMint(fromMint);
const toMintInfo = useMint(toMint);
const openOrders = useOpenOrders();
2021-05-16 15:52:38 -07:00
const route = useRouteVerbose(fromMint, toMint);
const fromMarket = useMarket(
route && route.markets ? route.markets[0] : undefined
);
const toMarket = useMarket(
route && route.markets ? route.markets[1] : undefined
);
2021-05-17 12:01:35 -07:00
const canSwap = useCanSwap();
2021-05-18 01:26:03 -07:00
const referral = useReferral(fromMarket);
2021-05-12 13:10:52 -07:00
2021-05-17 12:01:35 -07:00
// Click handler.
2021-05-12 13:10:52 -07:00
const sendSwapTransaction = async () => {
2021-05-15 00:39:56 -07:00
if (!fromMintInfo || !toMintInfo) {
throw new Error("Unable to calculate mint decimals");
}
2021-05-16 15:52:38 -07:00
const amount = new BN(fromAmount).mul(
new BN(10).pow(new BN(fromMintInfo.decimals))
);
const minExpectedSwapAmount = new BN(toAmount)
.mul(new BN(10).pow(new BN(toMintInfo.decimals)))
2021-05-15 12:58:05 -07:00
.muln(100 - slippage)
.divn(100);
const fromOpenOrders = fromMarket
? openOrders.get(fromMarket?.address.toString())
: undefined;
const toOpenOrders = toMarket
? openOrders.get(toMarket?.address.toString())
: undefined;
2021-05-15 00:39:56 -07:00
await swapClient.swap({
fromMint,
toMint,
amount,
minExpectedSwapAmount,
2021-05-18 01:26:03 -07:00
referral,
// Pass in the below parameters so that the client doesn't perform
// wasteful network requests when we already have the data.
fromMarket,
toMarket,
fromOpenOrders: fromOpenOrders ? fromOpenOrders[0].address : undefined,
toOpenOrders: toOpenOrders ? toOpenOrders[0].address : undefined,
2021-05-15 12:58:05 -07:00
// Auto close newly created open orders accounts.
close: isClosingNewAccounts,
2021-05-15 00:39:56 -07:00
});
2021-05-12 13:10:52 -07:00
};
return (
<Button
variant="contained"
className={styles.swapButton}
onClick={sendSwapTransaction}
2021-05-17 12:01:35 -07:00
disabled={!canSwap}
2021-05-12 13:10:52 -07:00
>
Swap
</Button>
);
}