Skip to content

Commit

Permalink
fix(web): max buy failure and slippage indicator
Browse files Browse the repository at this point in the history
  • Loading branch information
broody committed Jul 17, 2023
1 parent 624d9eb commit 1fbad5c
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 31 deletions.
13 changes: 13 additions & 0 deletions web/src/components/icons/Alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Icon, IconProps } from ".";

export const Alert = (props: IconProps) => {
return (
<Icon {...props}>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M20.7389 6H16.2075V7.99983H13.9429V11.9995H11.6783V15.9992H9.41377V19.9988H7.14921V23.9985H4.88464V28.0002H7.14921V30H29.7971V28.0002H32.0617V23.9985H29.7971V19.9988H27.5325V15.9992H25.268V11.9995H23.0034V7.99983H20.7389V6ZM20.1316 12.9514V14.0584H20.6865V19.0344H20.1316V21.801H19.5807V23.4583H17.3668V21.801H16.8147V19.0344H16.2598V14.0584H16.8147V12.9514H17.3668V12.4004H19.5807V12.9514H20.1316ZM17.3668 26.7992V24.5881H19.5807V26.7992H17.3668Z"
/>
</Icon>
);
};
1 change: 1 addition & 0 deletions web/src/components/icons/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export * from "./Trophy";
export * from "./User";
export * from "./Warning";
export * from "./Cart";
export * from "./Alert";

// Template for adding new icons. When copying svg from figma, viewBox is assumed
// to be 36x36, otherwise override within individual icons.
Expand Down
77 changes: 46 additions & 31 deletions web/src/pages/[gameId]/[locationSlug]/[drugSlug].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import Button from "@/components/Button";
import Layout from "@/components/Layout";
import { useRouter } from "next/router";
import { Footer } from "@/components/Footer";
import { ArrowEnclosed, Cart } from "@/components/icons";
import { Alert, ArrowEnclosed, Cart } from "@/components/icons";
import Image from "next/image";
import { DrugProps, getDrugBySlug, getLocationBySlug } from "@/hooks/ui";

Expand Down Expand Up @@ -48,6 +48,7 @@ import {
} from "@/hooks/dojo/entities/usePlayerEntity";
import { formatQuantity, formatCash } from "@/utils/ui";
import { useRyoSystems } from "@/hooks/dojo/systems/useRyoSystems";
import { calculateMaxQuantity, calculateSlippage } from "@/utils/defi";

export default function Market() {
const router = useRouter();
Expand All @@ -64,8 +65,6 @@ export default function Market() {
const [canSell, setCanSell] = useState(false);
const [canBuy, setCanBuy] = useState(false);

const isBagFull = false;

const { location: locationEntity } = useLocationEntity({
gameId,
locationName: location.name,
Expand Down Expand Up @@ -173,27 +172,22 @@ export default function Market() {

<TabPanels mt={6}>
<TabPanel>
{!isBagFull && (
<QuantitySelector
drug={drug}
player={playerEntity}
marketPrice={market.price}
marketQuantity={market.marketPool.quantity}
type={TradeDirection.Buy}
onChange={setQuantityBuy}
/>
)}
<QuantitySelector
drug={drug}
player={playerEntity}
market={market}
type={TradeDirection.Buy}
onChange={setQuantityBuy}
/>

{!canBuy && <AlertMessage message="YOU ARE BROKE" />}
{isBagFull && <AlertMessage message="YOUR BAG IS FULL" />}
</TabPanel>
<TabPanel>
{canSell ? (
<QuantitySelector
drug={drug}
player={playerEntity}
marketPrice={market.price}
marketQuantity={market.marketPool.quantity}
market={market}
type={TradeDirection.Sell}
onChange={setQuantitySell}
/>
Expand Down Expand Up @@ -237,36 +231,53 @@ const QuantitySelector = ({
type,
player,
drug,
marketPrice,
marketQuantity,
market,
onChange,
}: {
type: TradeDirection;
drug: DrugProps;
player: PlayerEntity;
marketPrice: number;
marketQuantity: number;
market: DrugMarket;
onChange: (quantity: number) => void;
}) => {
const [totalPrice, setTotalPrice] = useState<number>(marketPrice);
const [totalPrice, setTotalPrice] = useState<number>(market.price);
const [priceImpact, setPriceImpact] = useState<number>(0);
const [alertColor, setAlertColor] = useState<string>("neon.500");
const [quantity, setQuantity] = useState(1);
const [max, setMax] = useState(0);

useEffect(() => {
if (type === TradeDirection.Buy) {
setMax(Math.floor(player.cash / marketPrice));
setMax(calculateMaxQuantity(market.marketPool, player.cash));
} else if (type === TradeDirection.Sell) {
const playerQuantity = player.drugs.find(
(d) => d.name === drug.name,
)?.quantity;
setMax(playerQuantity || 0);
}
}, [type, drug, marketQuantity, player, marketPrice]);
}, [type, drug, player, market]);

useEffect(() => {
setTotalPrice(quantity * marketPrice);
const slippage = calculateSlippage(
{ cash: market.marketPool.cash, quantity: market.marketPool.quantity },
quantity,
type,
);

if (slippage.priceImpact > 0.2) {
// >20%
setAlertColor("red");
} else if (slippage.priceImpact > 0.05) {
// >5%
setAlertColor("neon.200");
} else {
setAlertColor("neon.500");
}

setPriceImpact(slippage.priceImpact);
setTotalPrice(quantity * slippage.newPrice);
onChange(quantity);
}, [quantity, marketPrice, onChange]);
}, [quantity, market, onChange]);

const onDown = useCallback(() => {
if (quantity > 1) {
Expand Down Expand Up @@ -298,9 +309,15 @@ const QuantitySelector = ({
pointerEvents={max === 0 ? "none" : "all"}
>
<HStack w="100%" justifyContent="space-between">
<Text>
{quantity} for {formatCash(totalPrice)}
</Text>
<VStack align="flex-start">
<Text>
({quantity}) for {formatCash(totalPrice)}
</Text>
<Text color={alertColor}>
<Alert size="sm" /> {(priceImpact * 100).toFixed(2)}% slippage
(estimate)
</Text>
</VStack>

<HStack gap="8px">
<Text textDecoration="underline" cursor="pointer" onClick={on50}>
Expand All @@ -312,15 +329,14 @@ const QuantitySelector = ({
</HStack>
</HStack>

<HStack w="100%">
<HStack w="100%" py={2}>
<Box
cursor="pointer"
onClick={onDown}
color="neon.500"
_hover={{
color: "neon.300",
}}
p={2}
>
<ArrowEnclosed direction="down" size="lg" />
</Box>
Expand All @@ -347,7 +363,6 @@ const QuantitySelector = ({
_hover={{
color: "neon.300",
}}
p={2}
>
<ArrowEnclosed direction="up" size="lg" />
</Box>
Expand Down
30 changes: 30 additions & 0 deletions web/src/utils/defi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { TradeDirection } from "@/hooks/state";
import { SCALING_FACTOR } from "@/hooks/dojo";
import { Market } from "@/generated/graphql";

export const calculateSlippage = (
market: Market,
tradeAmount: number,
tradeDirection: TradeDirection,
) => {
const k = market.cash * market.quantity;
const currentPrice = market.cash / market.quantity;

const newQuantity =
tradeDirection === TradeDirection.Buy
? market.quantity - tradeAmount
: market.quantity + tradeAmount;
const newCash = k / newQuantity;
const newPrice = newCash / newQuantity;

const priceImpact = Math.abs((newPrice - currentPrice) / currentPrice);
return { priceImpact, newPrice: newPrice / SCALING_FACTOR };
};

export const calculateMaxQuantity = (market: Market, maxCash: number) => {
const k = market.cash * market.quantity;
const maxQuantity =
market.quantity - k / (Number(market.cash) + maxCash * SCALING_FACTOR);

return Math.floor(maxQuantity);
};

1 comment on commit 1fbad5c

@vercel
Copy link

@vercel vercel bot commented on 1fbad5c Jul 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

rollyourown – ./

rollyourown.preview.cartridge.gg
rollyourown-git-main.preview.cartridge.gg

Please sign in to comment.