Switch min swap quantity to execution price bounds (#11)
This commit is contained in:
parent
3a58375616
commit
7de26316a7
|
@ -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": {
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue