2021-05-13 00:28:32 -07:00
|
|
|
import { useState } from "react";
|
2021-06-24 15:32:34 -07:00
|
|
|
import {
|
|
|
|
PublicKey,
|
|
|
|
Keypair,
|
|
|
|
Transaction,
|
|
|
|
SystemProgram,
|
|
|
|
Signer,
|
|
|
|
} from "@solana/web3.js";
|
|
|
|
import { Token, TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
|
|
|
import { BN, Provider } from "@project-serum/anchor";
|
2021-05-12 13:10:52 -07:00
|
|
|
import {
|
|
|
|
makeStyles,
|
|
|
|
Card,
|
|
|
|
Button,
|
|
|
|
Typography,
|
|
|
|
TextField,
|
2021-05-26 09:47:53 -07:00
|
|
|
useTheme,
|
2021-05-12 13:10:52 -07:00
|
|
|
} from "@material-ui/core";
|
2021-05-26 09:47:53 -07:00
|
|
|
import { ExpandMore, ImportExportRounded } from "@material-ui/icons";
|
2021-05-19 18:59:29 -07:00
|
|
|
import { useSwapContext, useSwapFair } 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-06-15 10:49:17 -07:00
|
|
|
FEE_MULTIPLIER,
|
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-06-24 19:01:38 -07:00
|
|
|
import { SOL_MINT, WRAPPED_SOL_MINT } from "../utils/pubkeys";
|
2021-05-12 13:10:52 -07:00
|
|
|
|
2021-05-26 09:47:53 -07:00
|
|
|
const useStyles = makeStyles((theme) => ({
|
2021-05-12 13:10:52 -07:00
|
|
|
card: {
|
2021-06-15 12:02:50 -07:00
|
|
|
width: theme.spacing(50),
|
|
|
|
borderRadius: theme.spacing(2),
|
2021-05-26 09:47:53 -07:00
|
|
|
boxShadow: "0px 0px 30px 5px rgba(0,0,0,0.075)",
|
2021-06-15 12:02:50 -07:00
|
|
|
padding: theme.spacing(2),
|
2021-05-12 13:10:52 -07:00
|
|
|
},
|
|
|
|
tab: {
|
|
|
|
width: "50%",
|
|
|
|
},
|
|
|
|
settingsButton: {
|
|
|
|
padding: 0,
|
|
|
|
},
|
|
|
|
swapButton: {
|
|
|
|
width: "100%",
|
2021-06-15 12:02:50 -07:00
|
|
|
borderRadius: theme.spacing(2),
|
2021-05-26 09:47:53 -07:00
|
|
|
backgroundColor: theme.palette.primary.main,
|
|
|
|
color: theme.palette.primary.contrastText,
|
|
|
|
fontSize: 16,
|
|
|
|
fontWeight: 700,
|
2021-06-15 12:02:50 -07:00
|
|
|
padding: theme.spacing(1.5),
|
2021-05-12 13:10:52 -07:00
|
|
|
},
|
|
|
|
swapToFromButton: {
|
|
|
|
display: "block",
|
2021-05-26 09:47:53 -07:00
|
|
|
margin: "10px auto 10px auto",
|
|
|
|
cursor: "pointer",
|
|
|
|
},
|
|
|
|
amountInput: {
|
|
|
|
fontSize: 22,
|
|
|
|
fontWeight: 600,
|
|
|
|
},
|
|
|
|
input: {
|
|
|
|
textAlign: "right",
|
|
|
|
},
|
|
|
|
swapTokenFormContainer: {
|
2021-06-15 12:02:50 -07:00
|
|
|
borderRadius: theme.spacing(2),
|
2021-05-26 09:47:53 -07:00
|
|
|
boxShadow: "0px 0px 15px 2px rgba(33,150,243,0.1)",
|
|
|
|
display: "flex",
|
|
|
|
justifyContent: "space-between",
|
2021-06-15 12:02:50 -07:00
|
|
|
padding: theme.spacing(1),
|
2021-05-26 09:47:53 -07:00
|
|
|
},
|
|
|
|
swapTokenSelectorContainer: {
|
2021-06-15 12:02:50 -07:00
|
|
|
marginLeft: theme.spacing(1),
|
2021-05-26 09:47:53 -07:00
|
|
|
display: "flex",
|
|
|
|
flexDirection: "column",
|
2021-05-26 15:33:53 -07:00
|
|
|
width: "50%",
|
2021-05-26 09:47:53 -07:00
|
|
|
},
|
|
|
|
balanceContainer: {
|
|
|
|
display: "flex",
|
|
|
|
alignItems: "center",
|
|
|
|
fontSize: "14px",
|
|
|
|
},
|
|
|
|
maxButton: {
|
2021-06-15 12:02:50 -07:00
|
|
|
marginLeft: theme.spacing(1),
|
2021-05-26 09:47:53 -07:00
|
|
|
color: theme.palette.primary.main,
|
|
|
|
fontWeight: 700,
|
|
|
|
fontSize: "12px",
|
|
|
|
cursor: "pointer",
|
|
|
|
},
|
|
|
|
tokenButton: {
|
|
|
|
display: "flex",
|
|
|
|
alignItems: "center",
|
|
|
|
cursor: "pointer",
|
2021-06-15 12:02:50 -07:00
|
|
|
marginBottom: theme.spacing(1),
|
2021-05-12 13:10:52 -07:00
|
|
|
},
|
|
|
|
}));
|
|
|
|
|
2021-05-26 09:47:53 -07:00
|
|
|
export default function SwapCard({
|
|
|
|
containerStyle,
|
|
|
|
contentStyle,
|
|
|
|
swapTokenContainerStyle,
|
|
|
|
}: {
|
|
|
|
containerStyle?: any;
|
|
|
|
contentStyle?: any;
|
|
|
|
swapTokenContainerStyle?: any;
|
|
|
|
}) {
|
2021-05-12 13:10:52 -07:00
|
|
|
const styles = useStyles();
|
|
|
|
return (
|
2021-05-26 09:47:53 -07:00
|
|
|
<Card className={styles.card} style={containerStyle}>
|
|
|
|
<SwapHeader />
|
|
|
|
<div style={contentStyle}>
|
|
|
|
<SwapFromForm style={swapTokenContainerStyle} />
|
|
|
|
<ArrowButton />
|
|
|
|
<SwapToForm style={swapTokenContainerStyle} />
|
|
|
|
<InfoLabel />
|
|
|
|
<SwapButton />
|
|
|
|
</div>
|
|
|
|
</Card>
|
2021-05-12 13:10:52 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-06-13 10:32:34 -07:00
|
|
|
export function SwapHeader() {
|
2021-05-12 13:10:52 -07:00
|
|
|
return (
|
|
|
|
<div
|
|
|
|
style={{
|
|
|
|
display: "flex",
|
|
|
|
justifyContent: "space-between",
|
2021-06-15 12:02:50 -07:00
|
|
|
marginBottom: "16px",
|
2021-05-12 13:10:52 -07:00
|
|
|
}}
|
|
|
|
>
|
|
|
|
<Typography
|
|
|
|
style={{
|
2021-05-26 09:47:53 -07:00
|
|
|
fontSize: 18,
|
|
|
|
fontWeight: 700,
|
2021-05-12 13:10:52 -07:00
|
|
|
}}
|
|
|
|
>
|
2021-05-26 09:47:53 -07:00
|
|
|
SWAP
|
2021-05-12 13:10:52 -07:00
|
|
|
</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();
|
2021-05-26 09:47:53 -07:00
|
|
|
const theme = useTheme();
|
2021-05-12 13:10:52 -07:00
|
|
|
const { swapToFromMints } = useSwapContext();
|
|
|
|
return (
|
2021-05-26 09:47:53 -07:00
|
|
|
<ImportExportRounded
|
|
|
|
className={styles.swapToFromButton}
|
|
|
|
fontSize="large"
|
|
|
|
htmlColor={theme.palette.primary.main}
|
|
|
|
onClick={swapToFromMints}
|
|
|
|
/>
|
2021-05-12 13:10:52 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-05-26 09:47:53 -07:00
|
|
|
function SwapFromForm({ style }: { style?: any }) {
|
2021-05-12 13:10:52 -07:00
|
|
|
const { fromMint, setFromMint, fromAmount, setFromAmount } = useSwapContext();
|
|
|
|
return (
|
|
|
|
<SwapTokenForm
|
2021-05-26 09:47:53 -07:00
|
|
|
from
|
|
|
|
style={style}
|
2021-05-12 13:10:52 -07:00
|
|
|
mint={fromMint}
|
|
|
|
setMint={setFromMint}
|
|
|
|
amount={fromAmount}
|
|
|
|
setAmount={setFromAmount}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-05-26 09:47:53 -07:00
|
|
|
function SwapToForm({ style }: { style?: any }) {
|
2021-05-12 13:10:52 -07:00
|
|
|
const { toMint, setToMint, toAmount, setToAmount } = useSwapContext();
|
|
|
|
return (
|
|
|
|
<SwapTokenForm
|
2021-05-26 09:47:53 -07:00
|
|
|
from={false}
|
|
|
|
style={style}
|
2021-05-12 13:10:52 -07:00
|
|
|
mint={toMint}
|
|
|
|
setMint={setToMint}
|
|
|
|
amount={toAmount}
|
|
|
|
setAmount={setToAmount}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-06-13 10:32:34 -07:00
|
|
|
export function SwapTokenForm({
|
2021-05-26 09:47:53 -07:00
|
|
|
from,
|
|
|
|
style,
|
2021-05-12 13:10:52 -07:00
|
|
|
mint,
|
|
|
|
setMint,
|
|
|
|
amount,
|
|
|
|
setAmount,
|
|
|
|
}: {
|
2021-05-26 09:47:53 -07:00
|
|
|
from: boolean;
|
|
|
|
style?: any;
|
2021-05-12 13:10:52 -07:00
|
|
|
mint: PublicKey;
|
|
|
|
setMint: (m: PublicKey) => void;
|
|
|
|
amount: number;
|
|
|
|
setAmount: (a: number) => void;
|
|
|
|
}) {
|
2021-05-26 09:47:53 -07:00
|
|
|
const styles = useStyles();
|
|
|
|
|
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
|
|
|
|
2021-05-26 09:47:53 -07:00
|
|
|
const balance =
|
|
|
|
tokenAccount &&
|
|
|
|
mintAccount &&
|
|
|
|
tokenAccount.account.amount.toNumber() / 10 ** mintAccount.decimals;
|
|
|
|
|
|
|
|
const formattedAmount =
|
|
|
|
mintAccount && amount
|
|
|
|
? amount.toLocaleString("fullwide", {
|
|
|
|
maximumFractionDigits: mintAccount.decimals,
|
|
|
|
useGrouping: false,
|
|
|
|
})
|
|
|
|
: amount;
|
|
|
|
|
2021-05-12 13:10:52 -07:00
|
|
|
return (
|
2021-05-26 09:47:53 -07:00
|
|
|
<div className={styles.swapTokenFormContainer} style={style}>
|
|
|
|
<div className={styles.swapTokenSelectorContainer}>
|
2021-05-13 00:28:32 -07:00
|
|
|
<TokenButton mint={mint} onClick={() => setShowTokenDialog(true)} />
|
2021-05-26 09:47:53 -07:00
|
|
|
<Typography color="textSecondary" className={styles.balanceContainer}>
|
2021-05-12 13:10:52 -07:00
|
|
|
{tokenAccount && mintAccount
|
2021-05-26 09:47:53 -07:00
|
|
|
? `Balance: ${balance?.toFixed(mintAccount.decimals)}`
|
2021-05-12 13:10:52 -07:00
|
|
|
: `-`}
|
2021-05-26 09:47:53 -07:00
|
|
|
{from && !!balance ? (
|
|
|
|
<span
|
|
|
|
className={styles.maxButton}
|
|
|
|
onClick={() => setAmount(balance)}
|
|
|
|
>
|
|
|
|
MAX
|
|
|
|
</span>
|
|
|
|
) : null}
|
2021-05-12 13:10:52 -07:00
|
|
|
</Typography>
|
|
|
|
</div>
|
2021-05-26 09:47:53 -07:00
|
|
|
<TextField
|
|
|
|
type="number"
|
|
|
|
value={formattedAmount}
|
|
|
|
onChange={(e) => setAmount(parseFloat(e.target.value))}
|
|
|
|
InputProps={{
|
|
|
|
disableUnderline: true,
|
|
|
|
classes: {
|
|
|
|
root: styles.amountInput,
|
|
|
|
input: styles.input,
|
|
|
|
},
|
|
|
|
}}
|
|
|
|
/>
|
2021-05-13 00:28:32 -07:00
|
|
|
<TokenDialog
|
|
|
|
setMint={setMint}
|
|
|
|
open={showTokenDialog}
|
|
|
|
onClose={() => setShowTokenDialog(false)}
|
|
|
|
/>
|
2021-05-26 09:47:53 -07:00
|
|
|
</div>
|
2021-05-12 13:10:52 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-05-13 00:28:32 -07:00
|
|
|
function TokenButton({
|
|
|
|
mint,
|
|
|
|
onClick,
|
|
|
|
}: {
|
|
|
|
mint: PublicKey;
|
|
|
|
onClick: () => void;
|
|
|
|
}) {
|
2021-05-26 09:47:53 -07:00
|
|
|
const styles = useStyles();
|
2021-06-15 12:02:50 -07:00
|
|
|
const theme = useTheme();
|
2021-05-26 09:47:53 -07:00
|
|
|
|
2021-05-12 13:10:52 -07:00
|
|
|
return (
|
2021-05-26 09:47:53 -07:00
|
|
|
<div onClick={onClick} className={styles.tokenButton}>
|
2021-06-15 12:02:50 -07:00
|
|
|
<TokenIcon mint={mint} style={{ width: theme.spacing(4) }} />
|
2021-05-26 09:47:53 -07:00
|
|
|
<TokenName mint={mint} style={{ fontSize: 14, fontWeight: 700 }} />
|
2021-05-12 13:10:52 -07:00
|
|
|
<ExpandMore />
|
2021-05-26 09:47:53 -07:00
|
|
|
</div>
|
2021-05-12 13:10:52 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
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>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-05-26 09:47:53 -07:00
|
|
|
function TokenName({ mint, style }: { mint: PublicKey; style: any }) {
|
2021-05-15 16:16:28 -07:00
|
|
|
const tokenMap = useTokenMap();
|
2021-06-15 12:02:50 -07:00
|
|
|
const theme = useTheme();
|
2021-05-15 16:16:28 -07:00
|
|
|
let tokenInfo = tokenMap.get(mint.toString());
|
2021-06-15 12:02:50 -07:00
|
|
|
|
2021-05-12 13:10:52 -07:00
|
|
|
return (
|
2021-06-15 12:02:50 -07:00
|
|
|
<Typography
|
|
|
|
style={{
|
|
|
|
marginLeft: theme.spacing(2),
|
|
|
|
marginRight: theme.spacing(1),
|
|
|
|
...style,
|
|
|
|
}}
|
|
|
|
>
|
2021-05-26 09:47:53 -07:00
|
|
|
{tokenInfo?.symbol}
|
|
|
|
</Typography>
|
2021-05-12 13:10:52 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-06-13 10:32:34 -07:00
|
|
|
export function SwapButton() {
|
2021-05-12 13:10:52 -07:00
|
|
|
const styles = useStyles();
|
2021-06-04 12:41:20 -07:00
|
|
|
const {
|
|
|
|
fromMint,
|
|
|
|
toMint,
|
|
|
|
fromAmount,
|
|
|
|
slippage,
|
|
|
|
isClosingNewAccounts,
|
|
|
|
isStrict,
|
|
|
|
} = 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-19 18:59:29 -07:00
|
|
|
const fair = useSwapFair();
|
2021-06-24 15:32:34 -07:00
|
|
|
let fromWallet = useOwnedTokenAccount(fromMint);
|
|
|
|
let toWallet = useOwnedTokenAccount(toMint);
|
|
|
|
const quoteMint = fromMarket && fromMarket.quoteMintAddress;
|
|
|
|
const quoteMintInfo = useMint(quoteMint);
|
|
|
|
const quoteWallet = useOwnedTokenAccount(quoteMint);
|
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-19 18:59:29 -07:00
|
|
|
if (!fair) {
|
|
|
|
throw new Error("Invalid fair");
|
|
|
|
}
|
2021-06-24 15:32:34 -07:00
|
|
|
if (!quoteMint || !quoteMintInfo) {
|
2021-06-04 12:41:20 -07:00
|
|
|
throw new Error("Quote mint not found");
|
|
|
|
}
|
2021-06-24 15:32:34 -07:00
|
|
|
|
2021-06-04 12:41:20 -07:00
|
|
|
const amount = new BN(fromAmount * 10 ** fromMintInfo.decimals);
|
2021-06-24 19:01:38 -07:00
|
|
|
const isSol = fromMint.equals(SOL_MINT) || toMint.equals(SOL_MINT);
|
2021-06-24 15:32:34 -07:00
|
|
|
const wrappedSolAccount = isSol ? Keypair.generate() : undefined;
|
|
|
|
|
|
|
|
// Build the swap.
|
2021-06-28 16:25:52 -07:00
|
|
|
let txs = await (async () => {
|
|
|
|
if (!fromMarket) {
|
|
|
|
throw new Error("Market undefined");
|
|
|
|
}
|
2021-06-24 15:32:34 -07:00
|
|
|
|
2021-06-28 16:25:52 -07:00
|
|
|
const minExchangeRate = {
|
|
|
|
rate: new BN((10 ** toMintInfo.decimals * FEE_MULTIPLIER) / fair)
|
|
|
|
.muln(100 - slippage)
|
|
|
|
.divn(100),
|
|
|
|
fromDecimals: fromMintInfo.decimals,
|
|
|
|
quoteDecimals: quoteMintInfo.decimals,
|
|
|
|
strict: isStrict,
|
|
|
|
};
|
|
|
|
const fromOpenOrders = fromMarket
|
|
|
|
? openOrders.get(fromMarket?.address.toString())
|
|
|
|
: undefined;
|
|
|
|
const toOpenOrders = toMarket
|
|
|
|
? openOrders.get(toMarket?.address.toString())
|
|
|
|
: undefined;
|
|
|
|
const fromWalletAddr = fromMint.equals(SOL_MINT)
|
|
|
|
? wrappedSolAccount!.publicKey
|
|
|
|
: fromWallet
|
|
|
|
? fromWallet.publicKey
|
|
|
|
: undefined;
|
|
|
|
const toWalletAddr = toMint.equals(SOL_MINT)
|
|
|
|
? wrappedSolAccount!.publicKey
|
|
|
|
: toWallet
|
|
|
|
? toWallet.publicKey
|
|
|
|
: undefined;
|
2021-06-24 15:32:34 -07:00
|
|
|
|
2021-06-28 16:25:52 -07:00
|
|
|
return await swapClient.swapTxs({
|
|
|
|
fromMint,
|
|
|
|
toMint,
|
|
|
|
quoteMint,
|
|
|
|
amount,
|
|
|
|
minExchangeRate,
|
|
|
|
referral,
|
|
|
|
fromMarket,
|
|
|
|
toMarket,
|
|
|
|
// Automatically created if undefined.
|
|
|
|
fromOpenOrders: fromOpenOrders ? fromOpenOrders[0].address : undefined,
|
|
|
|
toOpenOrders: toOpenOrders ? toOpenOrders[0].address : undefined,
|
|
|
|
fromWallet: fromWalletAddr,
|
|
|
|
toWallet: toWalletAddr,
|
|
|
|
quoteWallet: quoteWallet ? quoteWallet.publicKey : undefined,
|
|
|
|
// Auto close newly created open orders accounts.
|
|
|
|
close: isClosingNewAccounts,
|
|
|
|
});
|
|
|
|
})();
|
2021-06-24 15:32:34 -07:00
|
|
|
|
2021-06-28 16:25:52 -07:00
|
|
|
// If swapping SOL, then insert a wrap/unwrap instruction.
|
2021-06-24 15:32:34 -07:00
|
|
|
if (isSol) {
|
2021-06-28 16:25:52 -07:00
|
|
|
if (txs.length > 1) {
|
|
|
|
throw new Error("SOL must be swapped in a single transaction");
|
|
|
|
}
|
|
|
|
const { tx: wrapTx, signers: wrapSigners } = await wrapSol(
|
|
|
|
swapClient.program.provider,
|
|
|
|
wrappedSolAccount as Keypair,
|
|
|
|
fromMint,
|
|
|
|
amount
|
|
|
|
);
|
|
|
|
const { tx: unwrapTx, signers: unwrapSigners } = unwrapSol(
|
|
|
|
swapClient.program.provider,
|
|
|
|
wrappedSolAccount as Keypair
|
2021-06-24 15:32:34 -07:00
|
|
|
);
|
2021-06-28 16:25:52 -07:00
|
|
|
const tx = new Transaction();
|
|
|
|
tx.add(wrapTx);
|
|
|
|
tx.add(txs[0].tx);
|
|
|
|
tx.add(unwrapTx);
|
|
|
|
txs[0].tx = tx;
|
|
|
|
txs[0].signers.push(...wrapSigners);
|
|
|
|
txs[0].signers.push(...unwrapSigners);
|
2021-06-24 15:32:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
await swapClient.program.provider.sendAll(txs);
|
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>
|
|
|
|
);
|
|
|
|
}
|
2021-06-24 15:32:34 -07:00
|
|
|
|
|
|
|
async function wrapSol(
|
|
|
|
provider: Provider,
|
|
|
|
wrappedSolAccount: Keypair,
|
|
|
|
fromMint: PublicKey,
|
|
|
|
amount: BN
|
|
|
|
): Promise<{ tx: Transaction; signers: Array<Signer | undefined> }> {
|
|
|
|
const tx = new Transaction();
|
|
|
|
const signers = [wrappedSolAccount];
|
|
|
|
// Create new, rent exempt account.
|
|
|
|
tx.add(
|
|
|
|
SystemProgram.createAccount({
|
|
|
|
fromPubkey: provider.wallet.publicKey,
|
|
|
|
newAccountPubkey: wrappedSolAccount.publicKey,
|
|
|
|
lamports: await Token.getMinBalanceRentForExemptAccount(
|
|
|
|
provider.connection
|
|
|
|
),
|
|
|
|
space: 165,
|
|
|
|
programId: TOKEN_PROGRAM_ID,
|
|
|
|
})
|
|
|
|
);
|
|
|
|
// Transfer lamports. These will be converted to an SPL balance by the
|
|
|
|
// token program.
|
2021-06-24 19:01:38 -07:00
|
|
|
if (fromMint.equals(SOL_MINT)) {
|
2021-06-24 15:32:34 -07:00
|
|
|
tx.add(
|
|
|
|
SystemProgram.transfer({
|
|
|
|
fromPubkey: provider.wallet.publicKey,
|
|
|
|
toPubkey: wrappedSolAccount.publicKey,
|
|
|
|
lamports: amount.toNumber(),
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
// Initialize the account.
|
|
|
|
tx.add(
|
|
|
|
Token.createInitAccountInstruction(
|
|
|
|
TOKEN_PROGRAM_ID,
|
|
|
|
WRAPPED_SOL_MINT,
|
|
|
|
wrappedSolAccount.publicKey,
|
|
|
|
provider.wallet.publicKey
|
|
|
|
)
|
|
|
|
);
|
|
|
|
return { tx, signers };
|
|
|
|
}
|
|
|
|
|
|
|
|
function unwrapSol(
|
|
|
|
provider: Provider,
|
|
|
|
wrappedSolAccount: Keypair
|
|
|
|
): { tx: Transaction; signers: Array<Signer | undefined> } {
|
|
|
|
const tx = new Transaction();
|
|
|
|
tx.add(
|
|
|
|
Token.createCloseAccountInstruction(
|
|
|
|
TOKEN_PROGRAM_ID,
|
|
|
|
wrappedSolAccount.publicKey,
|
|
|
|
provider.wallet.publicKey,
|
|
|
|
provider.wallet.publicKey,
|
|
|
|
[]
|
|
|
|
)
|
|
|
|
);
|
|
|
|
return { tx, signers: [] };
|
|
|
|
}
|