diff --git a/package.json b/package.json
index 7955b21..81ae359 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"dependencies": {
"react-async-hook": "^3.6.2",
"@project-serum/serum": "^0.13.34",
- "@project-serum/swap": "^0.1.0-alpha.9",
+ "@project-serum/swap": "^0.1.0-alpha.11",
"@solana/spl-token": "^0.1.4"
},
"peerDependencies": {
diff --git a/src/swap/components/Info.tsx b/src/swap/components/Info.tsx
index ffbbe60..01d4843 100644
--- a/src/swap/components/Info.tsx
+++ b/src/swap/components/Info.tsx
@@ -11,7 +11,7 @@ import { PublicKey } from "@solana/web3.js";
import { useTokenMap } from "../context/TokenList";
import { useSwapContext, useSwapFair } from "../context/Swap";
import { useMint } from "../context/Token";
-import { useRoute, useMarketName, useFair } from "../context/Dex";
+import { useRoute, useMarketName, useBbo } from "../context/Dex";
const useStyles = makeStyles((theme) => ({
infoLabel: {
@@ -163,7 +163,7 @@ function InfoDetails() {
function MarketRoute({ market }: { market: PublicKey }) {
const marketName = useMarketName(market);
- const fair = useFair(market);
+ const bbo = useBbo(market);
return (
{marketName}
- {fair ? fair.toFixed(6) : "-"}
+
+ {bbo && bbo.mid ? bbo.mid.toFixed(6) : "-"}
+
);
}
diff --git a/src/swap/components/Settings.tsx b/src/swap/components/Settings.tsx
index 70a7e72..eb6cdf9 100644
--- a/src/swap/components/Settings.tsx
+++ b/src/swap/components/Settings.tsx
@@ -73,6 +73,11 @@ function SettingsDetails() {
const [showSettingsDialog, setShowSettingsDialog] = useState(false);
const fair = useSwapFair();
const { swapClient } = useDexContext();
+
+ const setSlippageHandler = (value?: number) => {
+ setSlippage(!value || value < 0 ? 0 : value);
+ };
+
return (
@@ -85,7 +90,7 @@ function SettingsDetails() {
type="number"
placeholder="Error tolerance percentage"
value={slippage}
- onChange={(e) => setSlippage(parseFloat(e.target.value))}
+ onChange={(e) => setSlippageHandler(parseFloat(e.target.value))}
style={{
display: "flex",
justifyContent: "center",
diff --git a/src/swap/components/Swap.tsx b/src/swap/components/Swap.tsx
index b1fca77..87dbfcd 100644
--- a/src/swap/components/Swap.tsx
+++ b/src/swap/components/Swap.tsx
@@ -10,12 +10,13 @@ import {
TextField,
} from "@material-ui/core";
import { ExpandMore } from "@material-ui/icons";
-import { useSwapContext } from "../context/Swap";
+import { useSwapContext, useSwapFair } from "../context/Swap";
import {
useDexContext,
useOpenOrders,
useRouteVerbose,
useMarket,
+ BASE_TAKER_FEE_BPS,
} from "../context/Dex";
import { useTokenMap } from "../context/TokenList";
import { useMint, useOwnedTokenAccount } from "../context/Token";
@@ -229,14 +230,8 @@ function TokenName({ mint }: { mint: PublicKey }) {
function SwapButton() {
const styles = useStyles();
- const {
- fromMint,
- toMint,
- fromAmount,
- toAmount,
- slippage,
- isClosingNewAccounts,
- } = useSwapContext();
+ const { fromMint, toMint, fromAmount, slippage, isClosingNewAccounts } =
+ useSwapContext();
const { swapClient } = useDexContext();
const fromMintInfo = useMint(fromMint);
const toMintInfo = useMint(toMint);
@@ -250,19 +245,26 @@ function SwapButton() {
);
const canSwap = useCanSwap();
const referral = useReferral(fromMarket);
+ const fair = useSwapFair();
// Click handler.
const sendSwapTransaction = async () => {
if (!fromMintInfo || !toMintInfo) {
throw new Error("Unable to calculate mint decimals");
}
+ if (!fair) {
+ throw new Error("Invalid fair");
+ }
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)))
- .muln(100 - slippage)
- .divn(100);
+ const minExchangeRate = {
+ rate: new BN(10 ** toMintInfo.decimals * (1 - BASE_TAKER_FEE_BPS))
+ .divn(fair)
+ .muln(100 - slippage)
+ .divn(100),
+ decimals: fromMintInfo.decimals,
+ };
const fromOpenOrders = fromMarket
? openOrders.get(fromMarket?.address.toString())
: undefined;
@@ -273,7 +275,7 @@ function SwapButton() {
fromMint,
toMint,
amount,
- minExpectedSwapAmount,
+ minExchangeRate,
referral,
// Pass in the below parameters so that the client doesn't perform
// wasteful network requests when we already have the data.
diff --git a/src/swap/context/Dex.tsx b/src/swap/context/Dex.tsx
index b2a53c7..bbde5d2 100644
--- a/src/swap/context/Dex.tsx
+++ b/src/swap/context/Dex.tsx
@@ -22,6 +22,8 @@ import {
import { useTokenMap, useTokenListContext } from "./TokenList";
import { fetchSolletInfo, requestWormholeSwapMarketIfNeeded } from "./Sollet";
+export const BASE_TAKER_FEE_BPS = 0.0022;
+
type DexContext = {
// Maps market address to open orders accounts.
openOrders: Map>;
@@ -293,7 +295,7 @@ export function useMarketName(market: PublicKey): string | null {
}
// Fair price for a given market, as defined by the mid.
-export function useFair(market?: PublicKey): number | undefined {
+export function useBbo(market?: PublicKey): Bbo | undefined {
const orderbook = useOrderbook(market);
if (orderbook === undefined) {
return undefined;
@@ -301,13 +303,13 @@ export function useFair(market?: PublicKey): number | undefined {
const bestBid = orderbook.bids.items(true).next().value;
const bestOffer = orderbook.asks.items(false).next().value;
if (!bestBid) {
- return bestOffer.price;
+ return { bestOffer: bestOffer.price };
}
if (!bestOffer) {
- return bestBid.price;
+ return { bestBid: bestBid.price };
}
const mid = (bestBid.price + bestOffer.price) / 2.0;
- return mid;
+ return { bestBid: bestBid.price, bestOffer: bestOffer.price, mid };
}
// Fair price for a theoretical toMint/fromMint market. I.e., the number
@@ -318,28 +320,33 @@ export function useFairRoute(
toMint: PublicKey
): number | undefined {
const route = useRoute(fromMint, toMint);
- const fromFair = useFair(route ? route[0] : undefined);
+ const fromBbo = useBbo(route ? route[0] : undefined);
const fromMarket = useMarket(route ? route[0] : undefined);
- const toFair = useFair(route ? route[1] : undefined);
+ const toBbo = useBbo(route ? route[1] : undefined);
if (route === null) {
return undefined;
}
- if (route.length === 1 && fromFair !== undefined) {
+ if (route.length === 1 && fromBbo !== undefined) {
if (fromMarket === undefined) {
return undefined;
}
if (fromMarket?.baseMintAddress.equals(fromMint)) {
- return 1.0 / fromFair;
+ return fromBbo.bestBid && 1.0 / fromBbo.bestBid;
} else {
- return fromFair;
+ return fromBbo.bestOffer && fromBbo.bestOffer;
}
}
- if (fromFair === undefined || toFair === undefined) {
+ if (
+ fromBbo === undefined ||
+ fromBbo.bestBid === undefined ||
+ toBbo === undefined ||
+ toBbo.bestOffer === undefined
+ ) {
return undefined;
}
- return toFair / fromFair;
+ return toBbo.bestOffer / fromBbo.bestBid;
}
export function useRoute(
@@ -525,3 +532,9 @@ async function deriveWormholeMarket(
padToTwo(version);
return await PublicKey.createWithSeed(WORM_MARKET_BASE, seed, DEX_PID);
}
+
+type Bbo = {
+ bestBid?: number;
+ bestOffer?: number;
+ mid?: number;
+};
diff --git a/yarn.lock b/yarn.lock
index b3c0240..fb7f4b4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1588,10 +1588,10 @@
bs58 "^4.0.1"
eventemitter3 "^4.0.4"
-"@project-serum/swap@^0.1.0-alpha.9":
- version "0.1.0-alpha.9"
- resolved "https://registry.yarnpkg.com/@project-serum/swap/-/swap-0.1.0-alpha.9.tgz#bd3b7a48426a9dbeaea457375b9ffe9b6d0272ee"
- integrity sha512-jrKpot+WPNBPTNcDoPD09XLFkr68wDVtob3vVx1eEcgN2xHtfjg0EKh42BM2BYasPsi7VSmS4yGwXui8j84jBw==
+"@project-serum/swap@^0.1.0-alpha.11":
+ version "0.1.0-alpha.11"
+ resolved "https://registry.yarnpkg.com/@project-serum/swap/-/swap-0.1.0-alpha.11.tgz#65c6393bdc511208453c853095eba32e0748191b"
+ integrity sha512-cxjxweWrZWcxfazkxSjDXqLquKlCCOoJ7z+cfAky/l29gRet+rLbGjyd+TAcZRDrIfh+kut9svPlL/bXK9vIOw==
dependencies:
"@project-serum/anchor" "^0.5.1-beta.2"
"@project-serum/serum" "^0.13.34"