diff --git a/src/components/Swap.tsx b/src/components/Swap.tsx
index 4a3630e9..128378b7 100644
--- a/src/components/Swap.tsx
+++ b/src/components/Swap.tsx
@@ -1,12 +1,12 @@
-import { useState } from "react";
+import { useMemo, useState } from "react";
import {
PublicKey,
Keypair,
Transaction,
SystemProgram,
Signer,
- Account,
SYSVAR_RENT_PUBKEY,
+ TransactionInstruction,
} from "@solana/web3.js";
import {
u64,
@@ -25,16 +25,25 @@ import {
useTheme,
} from "@material-ui/core";
import { ExpandMore, ImportExportRounded } from "@material-ui/icons";
-import { useSwapContext, useSwapFair } from "../context/Swap";
+import {
+ useIsUnwrapSollet,
+ useCanCreateAccounts,
+ useCanWrapOrUnwrap,
+ useSwapContext,
+ useSwapFair,
+} from "../context/Swap";
+// import { useIsUnwrapSolletUsdt, useSwapContext, useSwapFair } from "../context/Swap";
import {
useDexContext,
- useOpenOrders,
useRouteVerbose,
useMarket,
FEE_MULTIPLIER,
+ _DexContext,
} from "../context/Dex";
import { useTokenMap } from "../context/TokenList";
import {
+ addTokensToCache,
+ CachedToken,
useMint,
useOwnedTokenAccount,
useTokenContext,
@@ -43,7 +52,14 @@ import { useCanSwap, useReferral, useIsWrapSol } from "../context/Swap";
import TokenDialog from "./TokenDialog";
import { SettingsButton } from "./Settings";
import { InfoLabel } from "./Info";
-import { SOL_MINT, WRAPPED_SOL_MINT, DEX_PID } from "../utils/pubkeys";
+import {
+ SOL_MINT,
+ WRAPPED_SOL_MINT,
+ DEX_PID,
+ MEMO_PROGRAM_ID,
+ SOLLET_USDT_MINT,
+} from "../utils/pubkeys";
+import { getTokenAddrressAndCreateIx } from "../utils/tokens";
const useStyles = makeStyles((theme) => ({
card: {
@@ -339,38 +355,72 @@ export function SwapButton() {
isClosingNewAccounts,
isStrict,
} = useSwapContext();
- const { swapClient, isLoaded: isDexLoaded } = useDexContext();
- const { isLoaded: isTokensLoaded } = useTokenContext();
+ const {
+ swapClient,
+ isLoaded: isDexLoaded,
+ addOpenOrderAccount,
+ openOrders,
+ } = useDexContext();
+ const { isLoaded: isTokensLoaded, refreshTokenState } = useTokenContext();
+
+ // Token to be traded away
const fromMintInfo = useMint(fromMint);
+ // End destination token
const toMintInfo = useMint(toMint);
- const openOrders = useOpenOrders();
+
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
);
- const canSwap = useCanSwap();
- const referral = useReferral(fromMarket);
- const fair = useSwapFair();
- let fromWallet = useOwnedTokenAccount(fromMint);
- let toWallet = useOwnedTokenAccount(toMint);
+
+ const toWallet = useOwnedTokenAccount(toMint);
+ const fromWallet = useOwnedTokenAccount(fromMint);
+
+ // Intermediary token for multi-market swaps, eg. USDC in a SRM -> BTC swap
const quoteMint = fromMarket && fromMarket.quoteMintAddress;
const quoteMintInfo = useMint(quoteMint);
const quoteWallet = useOwnedTokenAccount(quoteMint);
+
+ const canCreateAccounts = useCanCreateAccounts();
+ const canWrapOrUnwrap = useCanWrapOrUnwrap();
+ const canSwap = useCanSwap();
+ const referral = useReferral(fromMarket);
+ const fair = useSwapFair();
+
const { isWrapSol, isUnwrapSol } = useIsWrapSol(fromMint, toMint);
- const fromOpenOrders = fromMarket
- ? openOrders.get(fromMarket?.address.toString())
- : undefined;
- const toOpenOrders = toMarket
- ? openOrders.get(toMarket?.address.toString())
- : undefined;
+ const isUnwrapSollet = useIsUnwrapSollet(fromMint, toMint);
+
+ const fromOpenOrders = useMemo(() => {
+ return fromMarket
+ ? openOrders.get(fromMarket?.address.toString())
+ : undefined;
+ }, [fromMarket, openOrders]);
+
+ const toOpenOrders = useMemo(() => {
+ return toMarket ? openOrders.get(toMarket?.address.toString()) : undefined;
+ }, [toMarket, openOrders]);
+
const disconnected = !swapClient.program.provider.wallet.publicKey;
+
+ const insufficientBalance =
+ fromAmount == 0 ||
+ fromAmount * Math.pow(10, fromMintInfo?.decimals ?? 0) >
+ (fromWallet?.account.amount.toNumber() ?? 0);
+
const needsCreateAccounts =
- !toWallet || !fromOpenOrders || (toMarket && !toOpenOrders);
+ !toWallet ||
+ (!isUnwrapSollet && (!fromOpenOrders || (toMarket && !toOpenOrders)));
// Click handlers.
+
+ /**
+ * Find if OpenOrders or associated token accounts are required
+ * for the swap, then send a create transaction
+ */
const sendCreateAccountsTransaction = async () => {
if (!fromMintInfo || !toMintInfo) {
throw new Error("Unable to calculate mint decimals");
@@ -380,95 +430,132 @@ export function SwapButton() {
}
const tx = new Transaction();
const signers = [];
+
+ let toAssociatedPubkey!: PublicKey;
+ let quoteAssociatedPubkey!: PublicKey;
+
+ // Associated token account creation
if (!toWallet) {
- const associatedTokenPubkey = await Token.getAssociatedTokenAddress(
- ASSOCIATED_TOKEN_PROGRAM_ID,
- TOKEN_PROGRAM_ID,
- toMint,
- swapClient.program.provider.wallet.publicKey
- );
- tx.add(
- Token.createAssociatedTokenAccountInstruction(
- ASSOCIATED_TOKEN_PROGRAM_ID,
- TOKEN_PROGRAM_ID,
+ const { tokenAddress, createTokenAddrIx } =
+ await getTokenAddrressAndCreateIx(
toMint,
- associatedTokenPubkey,
- swapClient.program.provider.wallet.publicKey,
swapClient.program.provider.wallet.publicKey
- )
- );
+ );
+ toAssociatedPubkey = tokenAddress;
+ tx.add(createTokenAddrIx);
}
+
if (!quoteWallet && !quoteMint.equals(toMint)) {
- const quoteAssociatedPubkey = await Token.getAssociatedTokenAddress(
- ASSOCIATED_TOKEN_PROGRAM_ID,
- TOKEN_PROGRAM_ID,
- quoteMint,
- swapClient.program.provider.wallet.publicKey
- );
- tx.add(
- Token.createAssociatedTokenAccountInstruction(
- ASSOCIATED_TOKEN_PROGRAM_ID,
- TOKEN_PROGRAM_ID,
+ const { tokenAddress, createTokenAddrIx } =
+ await getTokenAddrressAndCreateIx(
quoteMint,
- quoteAssociatedPubkey,
- swapClient.program.provider.wallet.publicKey,
swapClient.program.provider.wallet.publicKey
- )
+ );
+ quoteAssociatedPubkey = tokenAddress;
+ tx.add(createTokenAddrIx);
+ }
+ // No point of initializing from wallet, as user won't have tokens there
+
+ // Helper functions for OpenOrders
+
+ /**
+ * Add instructions to init and create an OpenOrders account
+ * @param openOrdersKeypair
+ * @param market
+ * @param tx
+ */
+ async function getInitOpenOrdersIx(
+ openOrdersKeypair: Keypair,
+ market: PublicKey,
+ tx: Transaction
+ ) {
+ const createOoIx = await OpenOrders.makeCreateAccountTransaction(
+ swapClient.program.provider.connection,
+ market,
+ swapClient.program.provider.wallet.publicKey,
+ openOrdersKeypair.publicKey,
+ DEX_PID
+ );
+ const initAcIx = swapClient.program.instruction.initAccount({
+ accounts: {
+ openOrders: openOrdersKeypair.publicKey,
+ authority: swapClient.program.provider.wallet.publicKey,
+ market: market,
+ dexProgram: DEX_PID,
+ rent: SYSVAR_RENT_PUBKEY,
+ },
+ });
+ tx.add(createOoIx);
+ tx.add(initAcIx);
+ }
+
+ /**
+ * Save data of newly created OpenOrders account in cache
+ * TODO: generate object client side to save a network call
+ * @param openOrdersAddress
+ */
+ async function saveOpenOrders(openOrdersAddress: PublicKey) {
+ const generatedOpenOrders = await OpenOrders.load(
+ swapClient.program.provider.connection,
+ openOrdersAddress,
+ DEX_PID
);
+ addOpenOrderAccount(generatedOpenOrders.market, generatedOpenOrders);
}
+
+ // Open order accounts for to / from wallets. Generate if not already present
+ let ooFrom!: Keypair;
+ let ooTo!: Keypair;
if (fromMarket && !fromOpenOrders) {
- const ooFrom = Keypair.generate();
+ ooFrom = Keypair.generate();
+ await getInitOpenOrdersIx(ooFrom, fromMarket.address, tx);
signers.push(ooFrom);
- tx.add(
- await OpenOrders.makeCreateAccountTransaction(
- swapClient.program.provider.connection,
- fromMarket.address,
- swapClient.program.provider.wallet.publicKey,
- ooFrom.publicKey,
- DEX_PID
- )
- );
- tx.add(
- swapClient.program.instruction.initAccount({
- accounts: {
- openOrders: ooFrom.publicKey,
- authority: swapClient.program.provider.wallet.publicKey,
- market: fromMarket.address,
- dexProgram: DEX_PID,
- rent: SYSVAR_RENT_PUBKEY,
- },
- })
- );
}
if (toMarket && !toOpenOrders) {
- const ooTo = Keypair.generate();
+ ooTo = Keypair.generate();
+ await getInitOpenOrdersIx(ooTo, toMarket.address, tx);
signers.push(ooTo);
- tx.add(
- await OpenOrders.makeCreateAccountTransaction(
- swapClient.program.provider.connection,
- toMarket.address,
- swapClient.program.provider.wallet.publicKey,
- ooTo.publicKey,
- DEX_PID
- )
- );
- tx.add(
- swapClient.program.instruction.initAccount({
- accounts: {
- openOrders: ooTo.publicKey,
- authority: swapClient.program.provider.wallet.publicKey,
- market: toMarket.address,
- dexProgram: DEX_PID,
- rent: SYSVAR_RENT_PUBKEY,
- },
- })
- );
}
- await swapClient.program.provider.send(tx, signers);
- // TODO: update local data stores to add the newly created token
- // and open orders accounts.
+ try {
+ // Send transaction to create accounts
+ await swapClient.program.provider.send(tx, signers);
+
+ // Save OpenOrders to cache
+ if (ooFrom) {
+ await saveOpenOrders(ooFrom.publicKey);
+ }
+ if (ooTo) {
+ await saveOpenOrders(ooTo.publicKey);
+ }
+
+ // Save created associated token accounts to cache
+ const tokensToAdd: CachedToken[] = [];
+ if (toAssociatedPubkey) {
+ tokensToAdd.push(
+ getNewTokenAccountData(
+ toAssociatedPubkey,
+ toMint,
+ swapClient.program.provider.wallet.publicKey
+ )
+ );
+ }
+ if (quoteAssociatedPubkey && !quoteMint.equals(toMint)) {
+ tokensToAdd.push(
+ getNewTokenAccountData(
+ quoteAssociatedPubkey,
+ quoteMint,
+ swapClient.program.provider.wallet.publicKey
+ )
+ );
+ }
+ addTokensToCache(tokensToAdd);
+
+ // Refresh UI to display balance of the created token account
+ refreshTokenState();
+ } catch (error) {}
};
+
const sendWrapSolTransaction = async () => {
if (!fromMintInfo || !toMintInfo) {
throw new Error("Unable to calculate mint decimals");
@@ -528,6 +615,7 @@ export function SwapButton() {
}
await swapClient.program.provider.send(tx, signers);
};
+
const sendUnwrapSolTransaction = async () => {
if (!fromMintInfo || !toMintInfo) {
throw new Error("Unable to calculate mint decimals");
@@ -568,6 +656,65 @@ export function SwapButton() {
await swapClient.program.provider.send(tx, signers);
};
+
+ const sendUnwrapSolletTransaction = async () => {
+ interface SolletBody {
+ address: string;
+ blockchain: string;
+ coin: string;
+ size: number;
+ wusdtToUsdt?: boolean;
+ wusdcToUsdc?: boolean;
+ }
+ const solletReqBody: SolletBody = {
+ address: toWallet!.publicKey.toString(),
+ blockchain: "sol",
+ coin: toMint.toString(),
+ size: 1,
+ };
+ if (fromMint.equals(SOLLET_USDT_MINT)) {
+ solletReqBody.wusdtToUsdt = true;
+ } else {
+ solletReqBody.wusdcToUsdc = true;
+ }
+ const solletRes = await fetch("https://swap.sollet.io/api/swap_to", {
+ method: "POST",
+ headers: {
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(solletReqBody),
+ });
+
+ const { address: bridgeAddr, maxSize } = (await solletRes.json())
+ .result as {
+ address: string;
+ maxSize: number;
+ };
+
+ const tx = new Transaction();
+ const amount = new u64(fromAmount * 10 ** fromMintInfo!.decimals);
+ tx.add(
+ Token.createTransferInstruction(
+ TOKEN_PROGRAM_ID,
+ fromWallet!.publicKey,
+ new PublicKey(bridgeAddr),
+ swapClient.program.provider.wallet.publicKey,
+ [],
+ amount
+ )
+ );
+ tx.add(
+ new TransactionInstruction({
+ keys: [],
+ data: Buffer.from(toWallet!.publicKey.toString(), "utf-8"),
+ programId: MEMO_PROGRAM_ID,
+ })
+ );
+
+ await swapClient.program.provider.send(tx);
+ };
+
const sendSwapTransaction = async () => {
if (!fromMintInfo || !toMintInfo) {
throw new Error("Unable to calculate mint decimals");
@@ -608,6 +755,12 @@ export function SwapButton() {
? toWallet.publicKey
: undefined;
+ const fromOpenOrdersList = openOrders.get(fromMarket?.address.toString());
+ let fromOpenOrders: PublicKey | undefined = undefined;
+ if (fromOpenOrdersList) {
+ fromOpenOrders = fromOpenOrdersList[0].address;
+ }
+
return await swapClient.swapTxs({
fromMint,
toMint,
@@ -618,7 +771,7 @@ export function SwapButton() {
fromMarket,
toMarket,
// Automatically created if undefined.
- fromOpenOrders: fromOpenOrders ? fromOpenOrders[0].address : undefined,
+ fromOpenOrders,
toOpenOrders: toOpenOrders ? toOpenOrders[0].address : undefined,
fromWallet: fromWalletAddr,
toWallet: toWalletAddr,
@@ -675,15 +828,21 @@ export function SwapButton() {
onClick={sendSwapTransaction}
disabled={true}
>
- Swap
+ Loading
);
}
- return needsCreateAccounts ? (
+
+ return !fromWallet || insufficientBalance ? (
+
+ ) : needsCreateAccounts ? (
@@ -692,7 +851,7 @@ export function SwapButton() {
variant="contained"
className={styles.swapButton}
onClick={sendWrapSolTransaction}
- disabled={!canSwap}
+ disabled={!canWrapOrUnwrap}
>
Wrap SOL
@@ -701,10 +860,19 @@ export function SwapButton() {
variant="contained"
className={styles.swapButton}
onClick={sendUnwrapSolTransaction}
- disabled={!canSwap}
+ disabled={!canWrapOrUnwrap}
>
Unwrap SOL
+ ) : isUnwrapSollet ? (
+
) : (