Switch min swap quantity to execution price bounds (#11)

This commit is contained in:
Armani Ferrante 2021-05-19 18:59:29 -07:00 committed by GitHub
parent 3a58375616
commit 7de26316a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 56 additions and 34 deletions

View File

@ -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": {

View File

@ -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 (
<div
style={{
@ -179,7 +179,9 @@ function MarketRoute({ market }: { market: PublicKey }) {
>
{marketName}
</Link>
<code style={{ marginLeft: "10px" }}>{fair ? fair.toFixed(6) : "-"}</code>
<code style={{ marginLeft: "10px" }}>
{bbo && bbo.mid ? bbo.mid.toFixed(6) : "-"}
</code>
</div>
);
}

View File

@ -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 (
<div style={{ padding: "15px", width: "305px" }}>
<Typography color="textSecondary" style={{ fontWeight: "bold" }}>
@ -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",

View File

@ -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.

View File

@ -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<string, Array<OpenOrders>>;
@ -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;
};

View File

@ -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"