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": { "dependencies": {
"react-async-hook": "^3.6.2", "react-async-hook": "^3.6.2",
"@project-serum/serum": "^0.13.34", "@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" "@solana/spl-token": "^0.1.4"
}, },
"peerDependencies": { "peerDependencies": {

View File

@ -11,7 +11,7 @@ import { PublicKey } from "@solana/web3.js";
import { useTokenMap } from "../context/TokenList"; import { useTokenMap } from "../context/TokenList";
import { useSwapContext, useSwapFair } from "../context/Swap"; import { useSwapContext, useSwapFair } from "../context/Swap";
import { useMint } from "../context/Token"; import { useMint } from "../context/Token";
import { useRoute, useMarketName, useFair } from "../context/Dex"; import { useRoute, useMarketName, useBbo } from "../context/Dex";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
infoLabel: { infoLabel: {
@ -163,7 +163,7 @@ function InfoDetails() {
function MarketRoute({ market }: { market: PublicKey }) { function MarketRoute({ market }: { market: PublicKey }) {
const marketName = useMarketName(market); const marketName = useMarketName(market);
const fair = useFair(market); const bbo = useBbo(market);
return ( return (
<div <div
style={{ style={{
@ -179,7 +179,9 @@ function MarketRoute({ market }: { market: PublicKey }) {
> >
{marketName} {marketName}
</Link> </Link>
<code style={{ marginLeft: "10px" }}>{fair ? fair.toFixed(6) : "-"}</code> <code style={{ marginLeft: "10px" }}>
{bbo && bbo.mid ? bbo.mid.toFixed(6) : "-"}
</code>
</div> </div>
); );
} }

View File

@ -73,6 +73,11 @@ function SettingsDetails() {
const [showSettingsDialog, setShowSettingsDialog] = useState(false); const [showSettingsDialog, setShowSettingsDialog] = useState(false);
const fair = useSwapFair(); const fair = useSwapFair();
const { swapClient } = useDexContext(); const { swapClient } = useDexContext();
const setSlippageHandler = (value?: number) => {
setSlippage(!value || value < 0 ? 0 : value);
};
return ( return (
<div style={{ padding: "15px", width: "305px" }}> <div style={{ padding: "15px", width: "305px" }}>
<Typography color="textSecondary" style={{ fontWeight: "bold" }}> <Typography color="textSecondary" style={{ fontWeight: "bold" }}>
@ -85,7 +90,7 @@ function SettingsDetails() {
type="number" type="number"
placeholder="Error tolerance percentage" placeholder="Error tolerance percentage"
value={slippage} value={slippage}
onChange={(e) => setSlippage(parseFloat(e.target.value))} onChange={(e) => setSlippageHandler(parseFloat(e.target.value))}
style={{ style={{
display: "flex", display: "flex",
justifyContent: "center", justifyContent: "center",

View File

@ -10,12 +10,13 @@ import {
TextField, TextField,
} from "@material-ui/core"; } from "@material-ui/core";
import { ExpandMore } from "@material-ui/icons"; import { ExpandMore } from "@material-ui/icons";
import { useSwapContext } from "../context/Swap"; import { useSwapContext, useSwapFair } from "../context/Swap";
import { import {
useDexContext, useDexContext,
useOpenOrders, useOpenOrders,
useRouteVerbose, useRouteVerbose,
useMarket, useMarket,
BASE_TAKER_FEE_BPS,
} from "../context/Dex"; } from "../context/Dex";
import { useTokenMap } from "../context/TokenList"; import { useTokenMap } from "../context/TokenList";
import { useMint, useOwnedTokenAccount } from "../context/Token"; import { useMint, useOwnedTokenAccount } from "../context/Token";
@ -229,14 +230,8 @@ function TokenName({ mint }: { mint: PublicKey }) {
function SwapButton() { function SwapButton() {
const styles = useStyles(); const styles = useStyles();
const { const { fromMint, toMint, fromAmount, slippage, isClosingNewAccounts } =
fromMint, useSwapContext();
toMint,
fromAmount,
toAmount,
slippage,
isClosingNewAccounts,
} = useSwapContext();
const { swapClient } = useDexContext(); const { swapClient } = useDexContext();
const fromMintInfo = useMint(fromMint); const fromMintInfo = useMint(fromMint);
const toMintInfo = useMint(toMint); const toMintInfo = useMint(toMint);
@ -250,19 +245,26 @@ function SwapButton() {
); );
const canSwap = useCanSwap(); const canSwap = useCanSwap();
const referral = useReferral(fromMarket); const referral = useReferral(fromMarket);
const fair = useSwapFair();
// Click handler. // Click handler.
const sendSwapTransaction = async () => { const sendSwapTransaction = async () => {
if (!fromMintInfo || !toMintInfo) { if (!fromMintInfo || !toMintInfo) {
throw new Error("Unable to calculate mint decimals"); throw new Error("Unable to calculate mint decimals");
} }
if (!fair) {
throw new Error("Invalid fair");
}
const amount = new BN(fromAmount).mul( const amount = new BN(fromAmount).mul(
new BN(10).pow(new BN(fromMintInfo.decimals)) new BN(10).pow(new BN(fromMintInfo.decimals))
); );
const minExpectedSwapAmount = new BN(toAmount) const minExchangeRate = {
.mul(new BN(10).pow(new BN(toMintInfo.decimals))) rate: new BN(10 ** toMintInfo.decimals * (1 - BASE_TAKER_FEE_BPS))
.muln(100 - slippage) .divn(fair)
.divn(100); .muln(100 - slippage)
.divn(100),
decimals: fromMintInfo.decimals,
};
const fromOpenOrders = fromMarket const fromOpenOrders = fromMarket
? openOrders.get(fromMarket?.address.toString()) ? openOrders.get(fromMarket?.address.toString())
: undefined; : undefined;
@ -273,7 +275,7 @@ function SwapButton() {
fromMint, fromMint,
toMint, toMint,
amount, amount,
minExpectedSwapAmount, minExchangeRate,
referral, referral,
// Pass in the below parameters so that the client doesn't perform // Pass in the below parameters so that the client doesn't perform
// wasteful network requests when we already have the data. // wasteful network requests when we already have the data.

View File

@ -22,6 +22,8 @@ import {
import { useTokenMap, useTokenListContext } from "./TokenList"; import { useTokenMap, useTokenListContext } from "./TokenList";
import { fetchSolletInfo, requestWormholeSwapMarketIfNeeded } from "./Sollet"; import { fetchSolletInfo, requestWormholeSwapMarketIfNeeded } from "./Sollet";
export const BASE_TAKER_FEE_BPS = 0.0022;
type DexContext = { type DexContext = {
// Maps market address to open orders accounts. // Maps market address to open orders accounts.
openOrders: Map<string, Array<OpenOrders>>; 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. // 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); const orderbook = useOrderbook(market);
if (orderbook === undefined) { if (orderbook === undefined) {
return undefined; return undefined;
@ -301,13 +303,13 @@ export function useFair(market?: PublicKey): number | undefined {
const bestBid = orderbook.bids.items(true).next().value; const bestBid = orderbook.bids.items(true).next().value;
const bestOffer = orderbook.asks.items(false).next().value; const bestOffer = orderbook.asks.items(false).next().value;
if (!bestBid) { if (!bestBid) {
return bestOffer.price; return { bestOffer: bestOffer.price };
} }
if (!bestOffer) { if (!bestOffer) {
return bestBid.price; return { bestBid: bestBid.price };
} }
const mid = (bestBid.price + bestOffer.price) / 2.0; 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 // Fair price for a theoretical toMint/fromMint market. I.e., the number
@ -318,28 +320,33 @@ export function useFairRoute(
toMint: PublicKey toMint: PublicKey
): number | undefined { ): number | undefined {
const route = useRoute(fromMint, toMint); 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 fromMarket = useMarket(route ? route[0] : undefined);
const toFair = useFair(route ? route[1] : undefined); const toBbo = useBbo(route ? route[1] : undefined);
if (route === null) { if (route === null) {
return undefined; return undefined;
} }
if (route.length === 1 && fromFair !== undefined) { if (route.length === 1 && fromBbo !== undefined) {
if (fromMarket === undefined) { if (fromMarket === undefined) {
return undefined; return undefined;
} }
if (fromMarket?.baseMintAddress.equals(fromMint)) { if (fromMarket?.baseMintAddress.equals(fromMint)) {
return 1.0 / fromFair; return fromBbo.bestBid && 1.0 / fromBbo.bestBid;
} else { } 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 undefined;
} }
return toFair / fromFair; return toBbo.bestOffer / fromBbo.bestBid;
} }
export function useRoute( export function useRoute(
@ -525,3 +532,9 @@ async function deriveWormholeMarket(
padToTwo(version); padToTwo(version);
return await PublicKey.createWithSeed(WORM_MARKET_BASE, seed, DEX_PID); 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" bs58 "^4.0.1"
eventemitter3 "^4.0.4" eventemitter3 "^4.0.4"
"@project-serum/swap@^0.1.0-alpha.9": "@project-serum/swap@^0.1.0-alpha.11":
version "0.1.0-alpha.9" version "0.1.0-alpha.11"
resolved "https://registry.yarnpkg.com/@project-serum/swap/-/swap-0.1.0-alpha.9.tgz#bd3b7a48426a9dbeaea457375b9ffe9b6d0272ee" resolved "https://registry.yarnpkg.com/@project-serum/swap/-/swap-0.1.0-alpha.11.tgz#65c6393bdc511208453c853095eba32e0748191b"
integrity sha512-jrKpot+WPNBPTNcDoPD09XLFkr68wDVtob3vVx1eEcgN2xHtfjg0EKh42BM2BYasPsi7VSmS4yGwXui8j84jBw== integrity sha512-cxjxweWrZWcxfazkxSjDXqLquKlCCOoJ7z+cfAky/l29gRet+rLbGjyd+TAcZRDrIfh+kut9svPlL/bXK9vIOw==
dependencies: dependencies:
"@project-serum/anchor" "^0.5.1-beta.2" "@project-serum/anchor" "^0.5.1-beta.2"
"@project-serum/serum" "^0.13.34" "@project-serum/serum" "^0.13.34"