Skip to content

Commit fc653aa

Browse files
committed
feat: Find price impact of swap
1 parent 3f213aa commit fc653aa

File tree

2 files changed

+62
-15
lines changed

2 files changed

+62
-15
lines changed

src/components/Info.tsx

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { PublicKey } from "@solana/web3.js";
1111
import { useTokenMap } from "../context/TokenList";
1212
import { useSwapContext, useSwapFair } from "../context/Swap";
1313
import { useMint } from "../context/Token";
14-
import { useRoute, useMarketName, useBbo } from "../context/Dex";
14+
import { useRoute, useMarketName, useBbo, usePriceImpact } from "../context/Dex";
1515

1616
const useStyles = makeStyles(() => ({
1717
infoLabel: {
@@ -39,21 +39,40 @@ export function InfoLabel() {
3939
let fromTokenInfo = tokenMap.get(fromMint.toString());
4040
let toTokenInfo = tokenMap.get(toMint.toString());
4141

42+
// Use last route item to find impact
43+
const route = useRoute(fromMint, toMint);
44+
const impact = usePriceImpact(route?.at(-1))
45+
4246
return (
43-
<div className={styles.infoLabel}>
44-
<Typography color="textSecondary" style={{ fontSize: "14px" }}>
45-
{fair !== undefined && toTokenInfo && fromTokenInfo
46-
? `1 ${toTokenInfo.symbol} = ${fair.toFixed(
47-
fromMintInfo?.decimals
48-
)} ${fromTokenInfo.symbol}`
49-
: `-`}
50-
</Typography>
51-
<InfoButton />
52-
</div>
47+
<>
48+
<div className={styles.infoLabel}>
49+
<Typography color="textSecondary" style={{ fontSize: "14px" }}>
50+
{fair !== undefined && toTokenInfo && fromTokenInfo
51+
? `1 ${toTokenInfo.symbol} = ${fair.toFixed(
52+
fromMintInfo?.decimals
53+
)} ${fromTokenInfo.symbol}`
54+
: `-`}
55+
</Typography>
56+
<InfoButton route={route}/>
57+
</div>
58+
59+
<div className={styles.infoLabel}>
60+
<Typography color="textSecondary" style={{ fontSize: "14px" }}>
61+
Price impact:&nbsp;
62+
</Typography>
63+
<Typography
64+
style={{ fontSize: "14px", fontWeight: 500 }}
65+
display="inline"
66+
color={(impact ?? 0) > 10 ? "error" : "primary"}
67+
>
68+
{impact?.toFixed(2)}%
69+
</Typography>
70+
</div>
71+
</>
5372
);
5473
}
5574

56-
function InfoButton() {
75+
function InfoButton({ route }: { route: PublicKey[] | null }) {
5776
const styles = useStyles();
5877
return (
5978
<PopupState variant="popover">
@@ -80,7 +99,7 @@ function InfoButton() {
8099
PaperProps={{ style: { borderRadius: "10px" } }}
81100
disableRestoreFocus
82101
>
83-
<InfoDetails />
102+
<InfoDetails route={route}/>
84103
</Popover>
85104
</div>
86105
)
@@ -89,9 +108,8 @@ function InfoButton() {
89108
);
90109
}
91110

92-
function InfoDetails() {
111+
function InfoDetails({ route }: { route: PublicKey[] | null }) {
93112
const { fromMint, toMint } = useSwapContext();
94-
const route = useRoute(fromMint, toMint);
95113
const tokenMap = useTokenMap();
96114
const fromMintTicker = tokenMap.get(fromMint.toString())?.symbol;
97115
const toMintTicker = tokenMap.get(toMint.toString())?.symbol;

src/context/Dex.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
import { useTokenMap, useTokenListContext } from "./TokenList";
2727
import { fetchSolletInfo, requestWormholeSwapMarketIfNeeded } from "./Sollet";
2828
import { setMintCache } from "./Token";
29+
import { useSwapContext } from "./Swap";
2930

3031
const BASE_TAKER_FEE_BPS = 0.0022;
3132
export const FEE_MULTIPLIER = 1 - BASE_TAKER_FEE_BPS;
@@ -350,6 +351,34 @@ export function useMarketName(market: PublicKey): string | null {
350351
return name;
351352
}
352353

354+
// TODO handle edge case of insufficient liquidity
355+
export function usePriceImpact(market?: PublicKey): number | undefined {
356+
const { toAmount, toMint } = useSwapContext();
357+
const orderbook = useOrderbook(market);
358+
if (orderbook === undefined) {
359+
return undefined;
360+
}
361+
const orders = toMint.equals(orderbook.bids.market.baseMintAddress)
362+
? orderbook.asks.items(false)
363+
: orderbook.bids.items(true)
364+
365+
let remainingAmount = toAmount
366+
let order = orders.next()
367+
const initialPrice = order.value.price
368+
let priceAfterOrder = initialPrice
369+
370+
while (!order.done && remainingAmount > 0) {
371+
priceAfterOrder = order.value.price
372+
remainingAmount = remainingAmount > order.value.size
373+
? remainingAmount - order.value.size
374+
: 0
375+
order = orders.next()
376+
}
377+
378+
const priceChange = Math.abs(initialPrice - priceAfterOrder)
379+
const impact = priceChange * 100 / initialPrice
380+
return impact
381+
}
353382
// Fair price for a given market, as defined by the mid.
354383
export function useBbo(market?: PublicKey): Bbo | undefined {
355384
const orderbook = useOrderbook(market);

0 commit comments

Comments
 (0)