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";
|
2021-05-17 11:33:35 -07:00
|
|
|
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";
|
2021-05-17 11:33:35 -07:00
|
|
|
import { useSwapContext } from "../context/Swap";
|
2021-05-15 12:27:13 -07:00
|
|
|
import {
|
|
|
|
useDexContext,
|
|
|
|
useOpenOrders,
|
2021-05-16 15:52:38 -07:00
|
|
|
useRouteVerbose,
|
2021-05-15 12:27:13 -07:00
|
|
|
useMarket,
|
2021-05-17 11:33:35 -07:00
|
|
|
} 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",
|
|
|
|
},
|
|
|
|
}));
|
|
|
|
|
2021-05-17 11:33:35 -07:00
|
|
|
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);
|
2021-05-15 12:27:13 -07:00
|
|
|
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);
|
2021-05-15 12:27:13 -07:00
|
|
|
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,
|
2021-05-15 12:27:13 -07:00
|
|
|
// 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>
|
|
|
|
);
|
|
|
|
}
|