From 7a207cae25dce3311d937ee326c8dfbdfca6ba5a Mon Sep 17 00:00:00 2001 From: Tom Robiquet Date: Tue, 3 Oct 2023 12:15:38 +0200 Subject: [PATCH 01/21] draft --- .../MarketContextActionOutcomeSelector.tsx | 2 +- components/trade-form/Amm2TradeForm.tsx | 232 ++++++++++++++++++ components/ui/inputs.tsx | 2 +- pages/markets/[marketid].tsx | 4 +- 4 files changed, 237 insertions(+), 3 deletions(-) create mode 100644 components/trade-form/Amm2TradeForm.tsx diff --git a/components/markets/MarketContextActionOutcomeSelector.tsx b/components/markets/MarketContextActionOutcomeSelector.tsx index d6663a05a..ba652d92c 100644 --- a/components/markets/MarketContextActionOutcomeSelector.tsx +++ b/components/markets/MarketContextActionOutcomeSelector.tsx @@ -19,7 +19,7 @@ export type MarketContextActionOutcomeSelectorProps = { const SEARCH_ITEMS_THRESHOLD = 5; -export const MarketContextActionOutcomeSelector = ({ +const MarketContextActionOutcomeSelector = ({ market, selected, options, diff --git a/components/trade-form/Amm2TradeForm.tsx b/components/trade-form/Amm2TradeForm.tsx new file mode 100644 index 000000000..7ee5d38e7 --- /dev/null +++ b/components/trade-form/Amm2TradeForm.tsx @@ -0,0 +1,232 @@ +import { Tab } from "@headlessui/react"; +import { useEffect, useState } from "react"; +import TradeTab, { TradeTabType } from "./TradeTab"; +import { useChainConstants } from "lib/hooks/queries/useChainConstants"; +import { useForm } from "react-hook-form"; +import { useSdkv2 } from "lib/hooks/useSdkv2"; +import { useNotifications } from "lib/state/notifications"; +import { useWallet } from "lib/state/wallet"; +import { useZtgBalance } from "lib/hooks/queries/useZtgBalance"; +import { + isRpcSdk, + MarketOutcomeAssetId, + parseAssetId, + ZTG, +} from "@zeitgeistpm/sdk-next"; +import FormTransactionButton from "components/ui/FormTransactionButton"; +import Decimal from "decimal.js"; +import { useExtrinsic } from "lib/hooks/useExtrinsic"; +import Input from "components/ui/Input"; +import { useMarket } from "lib/hooks/queries/useMarket"; +import { useBalance } from "lib/hooks/queries/useBalance"; +import { parseAssetIdString } from "lib/util/parse-asset-id"; +import MarketContextActionOutcomeSelector from "components/markets/MarketContextActionOutcomeSelector"; + +const BuyForm = ({ marketId }: { marketId: number }) => { + const { data: constants } = useChainConstants(); + const { + register, + handleSubmit, + getValues, + formState, + watch, + setValue, + trigger, + } = useForm({ + reValidateMode: "onChange", + mode: "onChange", + }); + const [sdk] = useSdkv2(); + const notificationStore = useNotifications(); + const { data: market } = useMarket({ + marketId, + }); + const wallet = useWallet(); + const baseAsset = parseAssetIdString(market?.baseAsset); + const { data: baseAssetBalance } = useBalance(wallet.realAddress, baseAsset); + + const outcomeAssets = market?.outcomeAssets.map( + (assetIdString) => + parseAssetId(assetIdString).unwrap() as MarketOutcomeAssetId, + ); + const [selectedAsset, setSelectedAsset] = useState< + MarketOutcomeAssetId | undefined + >(outcomeAssets?.[0]); + + // const { isLoading, send, fee } = useExtrinsic( + // () => { + // const amount = getValues("amount"); + // if (!isRpcSdk(sdk) || !amount) return; + + // return sdk.api.tx.court.delegate( + // new Decimal(amount).mul(ZTG).toFixed(0), + // [], + // ); + // }, + // { + // onSuccess: () => { + // notificationStore.pushNotification(`Successfully traded`, { + // type: "Success", + // }); + // }, + // }, + // ); + + useEffect(() => { + const subscription = watch((value, { name, type }) => { + const changedByUser = type != null; + + if (!changedByUser || !baseAssetBalance) return; + + if (name === "percentage") { + setValue( + "amount", + baseAssetBalance.mul(value.percentage).div(100).div(ZTG).toNumber(), + ); + } else if (name === "amount" && value.amount !== "") { + setValue( + "percentage", + new Decimal(value.amount) + .mul(ZTG) + .div(baseAssetBalance) + .mul(100) + .toString(), + ); + } + trigger("amount"); + }); + return () => subscription.unsubscribe(); + }, [watch, baseAssetBalance]); + + const onSubmit = () => { + // send(); + }; + return ( +
+
+
+
+ {market && selectedAsset && ( + { + setSelectedAsset(assetId); + // reset(); + // setTradeItem({ + // action: tradeItem.action, + // assetId, + // }); + }} + /> + )} +
+
{1000}
+
+
Forrr
+
+ { + if (value > (baseAssetBalance?.div(ZTG).toNumber() ?? 0)) { + return `Insufficient balance. Current balance: ${baseAssetBalance + ?.div(ZTG) + .toFixed(3)}`; + } else if (value <= 0) { + return "Value cannot be zero or less"; + } + }, + })} + /> +
+ {constants?.tokenSymbol} +
+
+ +
+ <>{formState.errors["amount"]?.message} +
+
+ {/* + Network Fee: {fee ? fee.amount.div(ZTG).toFixed(3) : 0}{" "} + {fee?.symbol} + */} +
+ + Swap + +
+
+ ); +}; +const SellForm = () => { + return
Sell
; +}; + +const Amm2TradeForm = () => { + const [tabType, setTabType] = useState(TradeTabType.Buy); + + return ( + { + setTabType(index); + + // reset(); + // setPercentageDisplay("0"); + }} + selectedIndex={tabType} + > + + + Buy + + + Sell + + + + + + + + + + + + + ); +}; + +export default Amm2TradeForm; diff --git a/components/ui/inputs.tsx b/components/ui/inputs.tsx index 21b080c58..45667bf40 100644 --- a/components/ui/inputs.tsx +++ b/components/ui/inputs.tsx @@ -24,7 +24,7 @@ const inputClasses = "bg-gray-100 dark:bg-black text-ztg-14-150 w-full rounded-lg h-ztg-40 p-ztg-8 focus:outline-none dark:border-black text-black dark:text-white"; const invalidClasses = "!border-vermilion !text-vermilion"; -export const Input: FC> = +const Input: FC> = React.forwardRef< HTMLInputElement, InputProps & InputHTMLAttributes diff --git a/pages/markets/[marketid].tsx b/pages/markets/[marketid].tsx index 62f52fc72..390bea1b2 100644 --- a/pages/markets/[marketid].tsx +++ b/pages/markets/[marketid].tsx @@ -21,6 +21,7 @@ import CategoricalDisputeBox from "components/outcomes/CategoricalDisputeBox"; import CategoricalReportBox from "components/outcomes/CategoricalReportBox"; import ScalarDisputeBox from "components/outcomes/ScalarDisputeBox"; import ScalarReportBox from "components/outcomes/ScalarReportBox"; +import Amm2TradeForm from "components/trade-form/Amm2TradeForm"; import Skeleton from "components/ui/Skeleton"; import { ChartSeries } from "components/ui/TimeSeriesChart"; import Decimal from "decimal.js"; @@ -379,7 +380,8 @@ const Market: NextPage = ({
{market?.status === MarketStatus.Active ? ( <> - + {/* */} + ) : market?.status === MarketStatus.Closed && canReport ? ( <> From a7bebc8aa3e109b2b945022c346d9bde648f9665 Mon Sep 17 00:00:00 2001 From: Tom Robiquet Date: Tue, 3 Oct 2023 12:54:51 +0200 Subject: [PATCH 02/21] sell form --- components/trade-form/Amm2TradeForm.tsx | 243 ++++++++++++++++++++---- 1 file changed, 201 insertions(+), 42 deletions(-) diff --git a/components/trade-form/Amm2TradeForm.tsx b/components/trade-form/Amm2TradeForm.tsx index 7ee5d38e7..7ed5dc97a 100644 --- a/components/trade-form/Amm2TradeForm.tsx +++ b/components/trade-form/Amm2TradeForm.tsx @@ -22,6 +22,48 @@ import { useBalance } from "lib/hooks/queries/useBalance"; import { parseAssetIdString } from "lib/util/parse-asset-id"; import MarketContextActionOutcomeSelector from "components/markets/MarketContextActionOutcomeSelector"; +const Amm2TradeForm = () => { + const [tabType, setTabType] = useState(TradeTabType.Buy); + + return ( + { + setTabType(index); + + // reset(); + // setPercentageDisplay("0"); + }} + selectedIndex={tabType} + > + + + Buy + + + Sell + + + + + + + + + + + + + ); +}; + const BuyForm = ({ marketId }: { marketId: number }) => { const { data: constants } = useChainConstants(); const { @@ -107,7 +149,8 @@ const BuyForm = ({ marketId }: { marketId: number }) => { onSubmit={handleSubmit(onSubmit)} className="w-full flex flex-col items-center gap-y-4" > -
+
+
{1000}
{market && selectedAsset && ( { /> )}
-
{1000}
-
Forrr
+
For
{
); }; -const SellForm = () => { - return
Sell
; -}; +const SellForm = ({ marketId }: { marketId: number }) => { + const { data: constants } = useChainConstants(); + const { + register, + handleSubmit, + getValues, + formState, + watch, + setValue, + trigger, + } = useForm({ + reValidateMode: "onChange", + mode: "onChange", + }); + const [sdk] = useSdkv2(); + const notificationStore = useNotifications(); + const { data: market } = useMarket({ + marketId, + }); + const wallet = useWallet(); + const baseAsset = parseAssetIdString(market?.baseAsset); + const { data: baseAssetBalance } = useBalance(wallet.realAddress, baseAsset); -const Amm2TradeForm = () => { - const [tabType, setTabType] = useState(TradeTabType.Buy); + const outcomeAssets = market?.outcomeAssets.map( + (assetIdString) => + parseAssetId(assetIdString).unwrap() as MarketOutcomeAssetId, + ); + const [selectedAsset, setSelectedAsset] = useState< + MarketOutcomeAssetId | undefined + >(outcomeAssets?.[0]); + + // const { isLoading, send, fee } = useExtrinsic( + // () => { + // const amount = getValues("amount"); + // if (!isRpcSdk(sdk) || !amount) return; + + // return sdk.api.tx.court.delegate( + // new Decimal(amount).mul(ZTG).toFixed(0), + // [], + // ); + // }, + // { + // onSuccess: () => { + // notificationStore.pushNotification(`Successfully traded`, { + // type: "Success", + // }); + // }, + // }, + // ); + + useEffect(() => { + const subscription = watch((value, { name, type }) => { + const changedByUser = type != null; + if (!changedByUser || !baseAssetBalance) return; + + if (name === "percentage") { + setValue( + "amount", + baseAssetBalance.mul(value.percentage).div(100).div(ZTG).toNumber(), + ); + } else if (name === "amount" && value.amount !== "") { + setValue( + "percentage", + new Decimal(value.amount) + .mul(ZTG) + .div(baseAssetBalance) + .mul(100) + .toString(), + ); + } + trigger("amount"); + }); + return () => subscription.unsubscribe(); + }, [watch, baseAssetBalance]); + + const onSubmit = () => { + // send(); + }; return ( - { - setTabType(index); +
+
+
+ { + if (value > (baseAssetBalance?.div(ZTG).toNumber() ?? 0)) { + return `Insufficient balance. Current balance: ${baseAssetBalance + ?.div(ZTG) + .toFixed(3)}`; + } else if (value <= 0) { + return "Value cannot be zero or less"; + } + }, + })} + /> +
+ {market && selectedAsset && ( + { + setSelectedAsset(assetId); + // reset(); + // setTradeItem({ + // action: tradeItem.action, + // assetId, + // }); + }} + /> + )} +
+
+
For
+
+
{1000}
- // reset(); - // setPercentageDisplay("0"); - }} - selectedIndex={tabType} - > - - - Buy - - + {constants?.tokenSymbol} +
+
+ +
+ <>{formState.errors["amount"]?.message} +
+
+ {/* + Network Fee: {fee ? fee.amount.div(ZTG).toFixed(3) : 0}{" "} + {fee?.symbol} + */} +
+ - Sell - - - - - - - - - - - -
+ Swap + + +
); }; From ece1b231072b9843a22d143264956e74a094bebc Mon Sep 17 00:00:00 2001 From: Tom Robiquet Date: Tue, 3 Oct 2023 14:05:29 +0200 Subject: [PATCH 03/21] make trade buttons work too --- .../AssetTradingButtons.tsx | 21 +++++++++- components/trade-form/Amm2TradeForm.tsx | 39 +++++++++++++++---- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/components/assets/AssetActionButtons/AssetTradingButtons.tsx b/components/assets/AssetActionButtons/AssetTradingButtons.tsx index ec10bf8cf..cfb6ca26f 100644 --- a/components/assets/AssetActionButtons/AssetTradingButtons.tsx +++ b/components/assets/AssetActionButtons/AssetTradingButtons.tsx @@ -1,6 +1,12 @@ import { Dialog } from "@headlessui/react"; -import { ScalarAssetId, CategoricalAssetId } from "@zeitgeistpm/sdk-next"; +import { + ScalarAssetId, + CategoricalAssetId, + getMarketIdOf, +} from "@zeitgeistpm/sdk-next"; import TradeForm from "components/trade-form"; +import Amm2TradeForm from "components/trade-form/Amm2TradeForm"; +import { TradeTabType } from "components/trade-form/TradeTab"; import Modal from "components/ui/Modal"; import SecondaryButton from "components/ui/SecondaryButton"; import { useTradeItem } from "lib/hooks/trade"; @@ -14,6 +20,7 @@ const AssetTradingButtons = ({ const [isOpen, setIsOpen] = useState(false); const { data: tradeItem, set: setTradeItem } = useTradeItem(); + const marketId = getMarketIdOf(assetId); return ( <>
@@ -43,7 +50,17 @@ const AssetTradingButtons = ({ {tradeItem && ( setIsOpen(false)}> - + {/* todo: add condition */} + {/* */} + )} diff --git a/components/trade-form/Amm2TradeForm.tsx b/components/trade-form/Amm2TradeForm.tsx index 7ed5dc97a..d5e59b1f0 100644 --- a/components/trade-form/Amm2TradeForm.tsx +++ b/components/trade-form/Amm2TradeForm.tsx @@ -8,6 +8,7 @@ import { useNotifications } from "lib/state/notifications"; import { useWallet } from "lib/state/wallet"; import { useZtgBalance } from "lib/hooks/queries/useZtgBalance"; import { + AssetId, isRpcSdk, MarketOutcomeAssetId, parseAssetId, @@ -22,8 +23,18 @@ import { useBalance } from "lib/hooks/queries/useBalance"; import { parseAssetIdString } from "lib/util/parse-asset-id"; import MarketContextActionOutcomeSelector from "components/markets/MarketContextActionOutcomeSelector"; -const Amm2TradeForm = () => { - const [tabType, setTabType] = useState(TradeTabType.Buy); +const Amm2TradeForm = ({ + marketId, + initialTab, + initialAsset, +}: { + marketId: number; + initialTab?: TradeTabType; + initialAsset?: MarketOutcomeAssetId; +}) => { + const [tabType, setTabType] = useState( + initialTab ?? TradeTabType.Buy, + ); return ( { - + - + ); }; -const BuyForm = ({ marketId }: { marketId: number }) => { +const BuyForm = ({ + marketId, + initialAsset, +}: { + marketId: number; + initialAsset?: MarketOutcomeAssetId; +}) => { const { data: constants } = useChainConstants(); const { register, @@ -93,7 +110,7 @@ const BuyForm = ({ marketId }: { marketId: number }) => { ); const [selectedAsset, setSelectedAsset] = useState< MarketOutcomeAssetId | undefined - >(outcomeAssets?.[0]); + >(initialAsset ?? outcomeAssets?.[0]); // const { isLoading, send, fee } = useExtrinsic( // () => { @@ -225,7 +242,13 @@ const BuyForm = ({ marketId }: { marketId: number }) => {
); }; -const SellForm = ({ marketId }: { marketId: number }) => { +const SellForm = ({ + marketId, + initialAsset, +}: { + marketId: number; + initialAsset?: MarketOutcomeAssetId; +}) => { const { data: constants } = useChainConstants(); const { register, @@ -254,7 +277,7 @@ const SellForm = ({ marketId }: { marketId: number }) => { ); const [selectedAsset, setSelectedAsset] = useState< MarketOutcomeAssetId | undefined - >(outcomeAssets?.[0]); + >(initialAsset ?? outcomeAssets?.[0]); // const { isLoading, send, fee } = useExtrinsic( // () => { From 60a88b28b0bdeb695ee2a368dc03242c9c9e321b Mon Sep 17 00:00:00 2001 From: Tom Robiquet Date: Tue, 3 Oct 2023 14:48:39 +0200 Subject: [PATCH 04/21] refactor --- components/trade-form/Amm2TradeForm.tsx | 369 +----------------------- components/trade-form/BuyForm.tsx | 184 ++++++++++++ components/trade-form/SellForm.tsx | 185 ++++++++++++ pages/markets/[marketid].tsx | 2 +- 4 files changed, 376 insertions(+), 364 deletions(-) create mode 100644 components/trade-form/BuyForm.tsx create mode 100644 components/trade-form/SellForm.tsx diff --git a/components/trade-form/Amm2TradeForm.tsx b/components/trade-form/Amm2TradeForm.tsx index d5e59b1f0..4b9412b30 100644 --- a/components/trade-form/Amm2TradeForm.tsx +++ b/components/trade-form/Amm2TradeForm.tsx @@ -1,27 +1,9 @@ import { Tab } from "@headlessui/react"; -import { useEffect, useState } from "react"; +import { MarketOutcomeAssetId } from "@zeitgeistpm/sdk-next"; +import { useState } from "react"; +import BuyForm from "./BuyForm"; +import SellForm from "./SellForm"; import TradeTab, { TradeTabType } from "./TradeTab"; -import { useChainConstants } from "lib/hooks/queries/useChainConstants"; -import { useForm } from "react-hook-form"; -import { useSdkv2 } from "lib/hooks/useSdkv2"; -import { useNotifications } from "lib/state/notifications"; -import { useWallet } from "lib/state/wallet"; -import { useZtgBalance } from "lib/hooks/queries/useZtgBalance"; -import { - AssetId, - isRpcSdk, - MarketOutcomeAssetId, - parseAssetId, - ZTG, -} from "@zeitgeistpm/sdk-next"; -import FormTransactionButton from "components/ui/FormTransactionButton"; -import Decimal from "decimal.js"; -import { useExtrinsic } from "lib/hooks/useExtrinsic"; -import Input from "components/ui/Input"; -import { useMarket } from "lib/hooks/queries/useMarket"; -import { useBalance } from "lib/hooks/queries/useBalance"; -import { parseAssetIdString } from "lib/util/parse-asset-id"; -import MarketContextActionOutcomeSelector from "components/markets/MarketContextActionOutcomeSelector"; const Amm2TradeForm = ({ marketId, @@ -40,9 +22,6 @@ const Amm2TradeForm = ({ { setTabType(index); - - // reset(); - // setPercentageDisplay("0"); }} selectedIndex={tabType} > @@ -65,350 +44,14 @@ const Amm2TradeForm = ({ - + - + ); }; -const BuyForm = ({ - marketId, - initialAsset, -}: { - marketId: number; - initialAsset?: MarketOutcomeAssetId; -}) => { - const { data: constants } = useChainConstants(); - const { - register, - handleSubmit, - getValues, - formState, - watch, - setValue, - trigger, - } = useForm({ - reValidateMode: "onChange", - mode: "onChange", - }); - const [sdk] = useSdkv2(); - const notificationStore = useNotifications(); - const { data: market } = useMarket({ - marketId, - }); - const wallet = useWallet(); - const baseAsset = parseAssetIdString(market?.baseAsset); - const { data: baseAssetBalance } = useBalance(wallet.realAddress, baseAsset); - - const outcomeAssets = market?.outcomeAssets.map( - (assetIdString) => - parseAssetId(assetIdString).unwrap() as MarketOutcomeAssetId, - ); - const [selectedAsset, setSelectedAsset] = useState< - MarketOutcomeAssetId | undefined - >(initialAsset ?? outcomeAssets?.[0]); - - // const { isLoading, send, fee } = useExtrinsic( - // () => { - // const amount = getValues("amount"); - // if (!isRpcSdk(sdk) || !amount) return; - - // return sdk.api.tx.court.delegate( - // new Decimal(amount).mul(ZTG).toFixed(0), - // [], - // ); - // }, - // { - // onSuccess: () => { - // notificationStore.pushNotification(`Successfully traded`, { - // type: "Success", - // }); - // }, - // }, - // ); - - useEffect(() => { - const subscription = watch((value, { name, type }) => { - const changedByUser = type != null; - - if (!changedByUser || !baseAssetBalance) return; - - if (name === "percentage") { - setValue( - "amount", - baseAssetBalance.mul(value.percentage).div(100).div(ZTG).toNumber(), - ); - } else if (name === "amount" && value.amount !== "") { - setValue( - "percentage", - new Decimal(value.amount) - .mul(ZTG) - .div(baseAssetBalance) - .mul(100) - .toString(), - ); - } - trigger("amount"); - }); - return () => subscription.unsubscribe(); - }, [watch, baseAssetBalance]); - - const onSubmit = () => { - // send(); - }; - return ( -
-
-
-
{1000}
-
- {market && selectedAsset && ( - { - setSelectedAsset(assetId); - // reset(); - // setTradeItem({ - // action: tradeItem.action, - // assetId, - // }); - }} - /> - )} -
-
-
For
-
- { - if (value > (baseAssetBalance?.div(ZTG).toNumber() ?? 0)) { - return `Insufficient balance. Current balance: ${baseAssetBalance - ?.div(ZTG) - .toFixed(3)}`; - } else if (value <= 0) { - return "Value cannot be zero or less"; - } - }, - })} - /> -
- {constants?.tokenSymbol} -
-
- -
- <>{formState.errors["amount"]?.message} -
-
- {/* - Network Fee: {fee ? fee.amount.div(ZTG).toFixed(3) : 0}{" "} - {fee?.symbol} - */} -
- - Swap - -
-
- ); -}; -const SellForm = ({ - marketId, - initialAsset, -}: { - marketId: number; - initialAsset?: MarketOutcomeAssetId; -}) => { - const { data: constants } = useChainConstants(); - const { - register, - handleSubmit, - getValues, - formState, - watch, - setValue, - trigger, - } = useForm({ - reValidateMode: "onChange", - mode: "onChange", - }); - const [sdk] = useSdkv2(); - const notificationStore = useNotifications(); - const { data: market } = useMarket({ - marketId, - }); - const wallet = useWallet(); - const baseAsset = parseAssetIdString(market?.baseAsset); - const { data: baseAssetBalance } = useBalance(wallet.realAddress, baseAsset); - - const outcomeAssets = market?.outcomeAssets.map( - (assetIdString) => - parseAssetId(assetIdString).unwrap() as MarketOutcomeAssetId, - ); - const [selectedAsset, setSelectedAsset] = useState< - MarketOutcomeAssetId | undefined - >(initialAsset ?? outcomeAssets?.[0]); - - // const { isLoading, send, fee } = useExtrinsic( - // () => { - // const amount = getValues("amount"); - // if (!isRpcSdk(sdk) || !amount) return; - - // return sdk.api.tx.court.delegate( - // new Decimal(amount).mul(ZTG).toFixed(0), - // [], - // ); - // }, - // { - // onSuccess: () => { - // notificationStore.pushNotification(`Successfully traded`, { - // type: "Success", - // }); - // }, - // }, - // ); - - useEffect(() => { - const subscription = watch((value, { name, type }) => { - const changedByUser = type != null; - - if (!changedByUser || !baseAssetBalance) return; - - if (name === "percentage") { - setValue( - "amount", - baseAssetBalance.mul(value.percentage).div(100).div(ZTG).toNumber(), - ); - } else if (name === "amount" && value.amount !== "") { - setValue( - "percentage", - new Decimal(value.amount) - .mul(ZTG) - .div(baseAssetBalance) - .mul(100) - .toString(), - ); - } - trigger("amount"); - }); - return () => subscription.unsubscribe(); - }, [watch, baseAssetBalance]); - - const onSubmit = () => { - // send(); - }; - return ( -
-
-
- { - if (value > (baseAssetBalance?.div(ZTG).toNumber() ?? 0)) { - return `Insufficient balance. Current balance: ${baseAssetBalance - ?.div(ZTG) - .toFixed(3)}`; - } else if (value <= 0) { - return "Value cannot be zero or less"; - } - }, - })} - /> -
- {market && selectedAsset && ( - { - setSelectedAsset(assetId); - // reset(); - // setTradeItem({ - // action: tradeItem.action, - // assetId, - // }); - }} - /> - )} -
-
-
For
-
-
{1000}
- -
- {constants?.tokenSymbol} -
-
- -
- <>{formState.errors["amount"]?.message} -
-
- {/* - Network Fee: {fee ? fee.amount.div(ZTG).toFixed(3) : 0}{" "} - {fee?.symbol} - */} -
- - Swap - -
-
- ); -}; - export default Amm2TradeForm; diff --git a/components/trade-form/BuyForm.tsx b/components/trade-form/BuyForm.tsx new file mode 100644 index 000000000..7f815ee37 --- /dev/null +++ b/components/trade-form/BuyForm.tsx @@ -0,0 +1,184 @@ +import { MarketOutcomeAssetId, parseAssetId, ZTG } from "@zeitgeistpm/sdk-next"; +import MarketContextActionOutcomeSelector from "components/markets/MarketContextActionOutcomeSelector"; +import FormTransactionButton from "components/ui/FormTransactionButton"; +import Input from "components/ui/Input"; +import Decimal from "decimal.js"; +import { useBalance } from "lib/hooks/queries/useBalance"; +import { useChainConstants } from "lib/hooks/queries/useChainConstants"; +import { useMarket } from "lib/hooks/queries/useMarket"; +import { useSdkv2 } from "lib/hooks/useSdkv2"; +import { useNotifications } from "lib/state/notifications"; +import { useWallet } from "lib/state/wallet"; +import { parseAssetIdString } from "lib/util/parse-asset-id"; +import { useState, useEffect } from "react"; +import { useForm } from "react-hook-form"; + +const BuyForm = ({ + marketId, + initialAsset, +}: { + marketId: number; + initialAsset?: MarketOutcomeAssetId; +}) => { + const { data: constants } = useChainConstants(); + const { + register, + handleSubmit, + getValues, + formState, + watch, + setValue, + trigger, + } = useForm({ + reValidateMode: "onChange", + mode: "onChange", + }); + const [sdk] = useSdkv2(); + const notificationStore = useNotifications(); + const { data: market } = useMarket({ + marketId, + }); + const wallet = useWallet(); + const baseAsset = parseAssetIdString(market?.baseAsset); + const { data: baseAssetBalance } = useBalance(wallet.realAddress, baseAsset); + + const outcomeAssets = market?.outcomeAssets.map( + (assetIdString) => + parseAssetId(assetIdString).unwrap() as MarketOutcomeAssetId, + ); + const [selectedAsset, setSelectedAsset] = useState< + MarketOutcomeAssetId | undefined + >(initialAsset ?? outcomeAssets?.[0]); + + // const { isLoading, send, fee } = useExtrinsic( + // () => { + // const amount = getValues("amount"); + // if (!isRpcSdk(sdk) || !amount) return; + + // return sdk.api.tx.court.delegate( + // new Decimal(amount).mul(ZTG).toFixed(0), + // [], + // ); + // }, + // { + // onSuccess: () => { + // notificationStore.pushNotification(`Successfully traded`, { + // type: "Success", + // }); + // }, + // }, + // ); + + useEffect(() => { + const subscription = watch((value, { name, type }) => { + const changedByUser = type != null; + + if (!changedByUser || !baseAssetBalance) return; + + if (name === "percentage") { + setValue( + "amount", + baseAssetBalance.mul(value.percentage).div(100).div(ZTG).toNumber(), + ); + } else if (name === "amount" && value.amount !== "") { + setValue( + "percentage", + new Decimal(value.amount) + .mul(ZTG) + .div(baseAssetBalance) + .mul(100) + .toString(), + ); + } + trigger("amount"); + }); + return () => subscription.unsubscribe(); + }, [watch, baseAssetBalance]); + + const onSubmit = () => { + // send(); + }; + return ( +
+
+
+
{1000}
+
+ {market && selectedAsset && ( + { + setSelectedAsset(assetId); + // reset(); + // setTradeItem({ + // action: tradeItem.action, + // assetId, + // }); + }} + /> + )} +
+
+
For
+
+ { + if (value > (baseAssetBalance?.div(ZTG).toNumber() ?? 0)) { + return `Insufficient balance. Current balance: ${baseAssetBalance + ?.div(ZTG) + .toFixed(3)}`; + } else if (value <= 0) { + return "Value cannot be zero or less"; + } + }, + })} + /> +
+ {constants?.tokenSymbol} +
+
+ +
+ <>{formState.errors["amount"]?.message} +
+
+ {/* + Network Fee: {fee ? fee.amount.div(ZTG).toFixed(3) : 0}{" "} + {fee?.symbol} + */} +
+ + Swap + +
+
+ ); +}; + +export default BuyForm; diff --git a/components/trade-form/SellForm.tsx b/components/trade-form/SellForm.tsx new file mode 100644 index 000000000..8ef76e958 --- /dev/null +++ b/components/trade-form/SellForm.tsx @@ -0,0 +1,185 @@ +import { MarketOutcomeAssetId, parseAssetId, ZTG } from "@zeitgeistpm/sdk-next"; +import MarketContextActionOutcomeSelector from "components/markets/MarketContextActionOutcomeSelector"; +import FormTransactionButton from "components/ui/FormTransactionButton"; +import Input from "components/ui/Input"; +import Decimal from "decimal.js"; +import { useBalance } from "lib/hooks/queries/useBalance"; +import { useChainConstants } from "lib/hooks/queries/useChainConstants"; +import { useMarket } from "lib/hooks/queries/useMarket"; +import { useSdkv2 } from "lib/hooks/useSdkv2"; +import { useNotifications } from "lib/state/notifications"; +import { useWallet } from "lib/state/wallet"; +import { parseAssetIdString } from "lib/util/parse-asset-id"; +import { useState, useEffect } from "react"; +import { useForm } from "react-hook-form"; + +const SellForm = ({ + marketId, + initialAsset, +}: { + marketId: number; + initialAsset?: MarketOutcomeAssetId; +}) => { + const { data: constants } = useChainConstants(); + const { + register, + handleSubmit, + getValues, + formState, + watch, + setValue, + trigger, + } = useForm({ + reValidateMode: "onChange", + mode: "onChange", + }); + const [sdk] = useSdkv2(); + const notificationStore = useNotifications(); + const { data: market } = useMarket({ + marketId, + }); + const wallet = useWallet(); + const baseAsset = parseAssetIdString(market?.baseAsset); + const { data: baseAssetBalance } = useBalance(wallet.realAddress, baseAsset); + + const outcomeAssets = market?.outcomeAssets.map( + (assetIdString) => + parseAssetId(assetIdString).unwrap() as MarketOutcomeAssetId, + ); + const [selectedAsset, setSelectedAsset] = useState< + MarketOutcomeAssetId | undefined + >(initialAsset ?? outcomeAssets?.[0]); + + // const { isLoading, send, fee } = useExtrinsic( + // () => { + // const amount = getValues("amount"); + // if (!isRpcSdk(sdk) || !amount) return; + + // return sdk.api.tx.court.delegate( + // new Decimal(amount).mul(ZTG).toFixed(0), + // [], + // ); + // }, + // { + // onSuccess: () => { + // notificationStore.pushNotification(`Successfully traded`, { + // type: "Success", + // }); + // }, + // }, + // ); + + useEffect(() => { + const subscription = watch((value, { name, type }) => { + const changedByUser = type != null; + + if (!changedByUser || !baseAssetBalance) return; + + if (name === "percentage") { + setValue( + "amount", + baseAssetBalance.mul(value.percentage).div(100).div(ZTG).toNumber(), + ); + } else if (name === "amount" && value.amount !== "") { + setValue( + "percentage", + new Decimal(value.amount) + .mul(ZTG) + .div(baseAssetBalance) + .mul(100) + .toString(), + ); + } + trigger("amount"); + }); + return () => subscription.unsubscribe(); + }, [watch, baseAssetBalance]); + + const onSubmit = () => { + // send(); + }; + return ( +
+
+
+ { + if (value > (baseAssetBalance?.div(ZTG).toNumber() ?? 0)) { + return `Insufficient balance. Current balance: ${baseAssetBalance + ?.div(ZTG) + .toFixed(3)}`; + } else if (value <= 0) { + return "Value cannot be zero or less"; + } + }, + })} + /> +
+ {market && selectedAsset && ( + { + setSelectedAsset(assetId); + // reset(); + // setTradeItem({ + // action: tradeItem.action, + // assetId, + // }); + }} + /> + )} +
+
+
For
+
+
{1000}
+ +
+ {constants?.tokenSymbol} +
+
+ +
+ <>{formState.errors["amount"]?.message} +
+
+ {/* + Network Fee: {fee ? fee.amount.div(ZTG).toFixed(3) : 0}{" "} + {fee?.symbol} + */} +
+ + Swap + +
+
+ ); +}; + +export default SellForm; diff --git a/pages/markets/[marketid].tsx b/pages/markets/[marketid].tsx index 8d23e2a6d..95ce29239 100644 --- a/pages/markets/[marketid].tsx +++ b/pages/markets/[marketid].tsx @@ -387,7 +387,7 @@ const Market: NextPage = ({ {market?.status === MarketStatus.Active ? ( <> {/* */} - + ) : market?.status === MarketStatus.Closed && canReport ? ( <> From 08d7d147514c2a6394876944e202b1513f389767 Mon Sep 17 00:00:00 2001 From: Tom Robiquet Date: Tue, 3 Oct 2023 14:55:49 +0200 Subject: [PATCH 05/21] add math --- components/trade-form/BuyForm.tsx | 16 +++++++++++++++- components/trade-form/SellForm.tsx | 13 ++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/components/trade-form/BuyForm.tsx b/components/trade-form/BuyForm.tsx index 7f815ee37..339ef8aa1 100644 --- a/components/trade-form/BuyForm.tsx +++ b/components/trade-form/BuyForm.tsx @@ -9,6 +9,7 @@ import { useMarket } from "lib/hooks/queries/useMarket"; import { useSdkv2 } from "lib/hooks/useSdkv2"; import { useNotifications } from "lib/state/notifications"; import { useWallet } from "lib/state/wallet"; +import { calculateSwapAmountOutForBuy } from "lib/util/amm2"; import { parseAssetIdString } from "lib/util/parse-asset-id"; import { useState, useEffect } from "react"; import { useForm } from "react-hook-form"; @@ -50,6 +51,19 @@ const BuyForm = ({ MarketOutcomeAssetId | undefined >(initialAsset ?? outcomeAssets?.[0]); + const amountIn = new Decimal(getValues("amount") ?? 0); + + const amountOut = calculateSwapAmountOutForBuy( + new Decimal(10000), //todo: fetch params + amountIn, + new Decimal(100000), + new Decimal(0.01), + new Decimal(0.001), + ); + + console.log(amountIn.toString()); + console.log(amountOut.toString()); + // const { isLoading, send, fee } = useExtrinsic( // () => { // const amount = getValues("amount"); @@ -105,7 +119,7 @@ const BuyForm = ({ className="w-full flex flex-col items-center gap-y-4" >
-
{1000}
+
{amountOut.toString()}
{market && selectedAsset && ( (initialAsset ?? outcomeAssets?.[0]); + const amountIn = new Decimal(getValues("amount") ?? 0); + + const amountOut = calculateSwapAmountOutForSell( + new Decimal(10000), //todo: fetch params + amountIn, + new Decimal(100000), + new Decimal(0.01), + new Decimal(0.001), + ); + // const { isLoading, send, fee } = useExtrinsic( // () => { // const amount = getValues("amount"); @@ -146,7 +157,7 @@ const SellForm = ({
For
-
{1000}
+
{amountOut.toString()}
{constants?.tokenSymbol} From 57a07ab3e1baab562a0fff5bab42f91c2e4b52a9 Mon Sep 17 00:00:00 2001 From: Tom Robiquet Date: Wed, 18 Oct 2023 11:36:53 +0200 Subject: [PATCH 06/21] fix build --- components/trade-form/Amm2TradeForm.tsx | 2 +- components/trade-form/BuyForm.tsx | 2 +- components/trade-form/SellForm.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/trade-form/Amm2TradeForm.tsx b/components/trade-form/Amm2TradeForm.tsx index 4b9412b30..11fabe45b 100644 --- a/components/trade-form/Amm2TradeForm.tsx +++ b/components/trade-form/Amm2TradeForm.tsx @@ -1,5 +1,5 @@ import { Tab } from "@headlessui/react"; -import { MarketOutcomeAssetId } from "@zeitgeistpm/sdk-next"; +import { MarketOutcomeAssetId } from "@zeitgeistpm/sdk"; import { useState } from "react"; import BuyForm from "./BuyForm"; import SellForm from "./SellForm"; diff --git a/components/trade-form/BuyForm.tsx b/components/trade-form/BuyForm.tsx index 339ef8aa1..568451781 100644 --- a/components/trade-form/BuyForm.tsx +++ b/components/trade-form/BuyForm.tsx @@ -1,4 +1,4 @@ -import { MarketOutcomeAssetId, parseAssetId, ZTG } from "@zeitgeistpm/sdk-next"; +import { MarketOutcomeAssetId, parseAssetId, ZTG } from "@zeitgeistpm/sdk"; import MarketContextActionOutcomeSelector from "components/markets/MarketContextActionOutcomeSelector"; import FormTransactionButton from "components/ui/FormTransactionButton"; import Input from "components/ui/Input"; diff --git a/components/trade-form/SellForm.tsx b/components/trade-form/SellForm.tsx index 0fce4f17e..97caaf551 100644 --- a/components/trade-form/SellForm.tsx +++ b/components/trade-form/SellForm.tsx @@ -1,4 +1,4 @@ -import { MarketOutcomeAssetId, parseAssetId, ZTG } from "@zeitgeistpm/sdk-next"; +import { MarketOutcomeAssetId, parseAssetId, ZTG } from "@zeitgeistpm/sdk"; import MarketContextActionOutcomeSelector from "components/markets/MarketContextActionOutcomeSelector"; import FormTransactionButton from "components/ui/FormTransactionButton"; import Input from "components/ui/Input"; From 3c8d7c468ffa6b35e9cdfec9a61c02da148e70b5 Mon Sep 17 00:00:00 2001 From: Tom Robiquet Date: Wed, 18 Oct 2023 12:06:43 +0200 Subject: [PATCH 07/21] add buy and sell extrinsics --- components/trade-form/BuyForm.tsx | 71 +++++++++++++++++----------- components/trade-form/SellForm.tsx | 76 +++++++++++++++++------------- package.json | 2 +- yarn.lock | 30 ++++++------ 4 files changed, 103 insertions(+), 76 deletions(-) diff --git a/components/trade-form/BuyForm.tsx b/components/trade-form/BuyForm.tsx index 568451781..6ad699aec 100644 --- a/components/trade-form/BuyForm.tsx +++ b/components/trade-form/BuyForm.tsx @@ -1,11 +1,18 @@ -import { MarketOutcomeAssetId, parseAssetId, ZTG } from "@zeitgeistpm/sdk"; +import { + isRpcSdk, + MarketOutcomeAssetId, + parseAssetId, + ZTG, +} from "@zeitgeistpm/sdk"; import MarketContextActionOutcomeSelector from "components/markets/MarketContextActionOutcomeSelector"; import FormTransactionButton from "components/ui/FormTransactionButton"; import Input from "components/ui/Input"; import Decimal from "decimal.js"; +import { DEFAULT_SLIPPAGE_PERCENTAGE } from "lib/constants"; import { useBalance } from "lib/hooks/queries/useBalance"; import { useChainConstants } from "lib/hooks/queries/useChainConstants"; import { useMarket } from "lib/hooks/queries/useMarket"; +import { useExtrinsic } from "lib/hooks/useExtrinsic"; import { useSdkv2 } from "lib/hooks/useSdkv2"; import { useNotifications } from "lib/state/notifications"; import { useWallet } from "lib/state/wallet"; @@ -61,27 +68,40 @@ const BuyForm = ({ new Decimal(0.001), ); + const slippageMultiplier = (100 - DEFAULT_SLIPPAGE_PERCENTAGE) / 100; + console.log(amountIn.toString()); console.log(amountOut.toString()); - // const { isLoading, send, fee } = useExtrinsic( - // () => { - // const amount = getValues("amount"); - // if (!isRpcSdk(sdk) || !amount) return; + const { isLoading, send, fee } = useExtrinsic( + () => { + const amount = getValues("amount"); + if ( + !isRpcSdk(sdk) || + !amount || + amount === "" || + market?.categories?.length == null || + !selectedAsset + ) { + return; + } - // return sdk.api.tx.court.delegate( - // new Decimal(amount).mul(ZTG).toFixed(0), - // [], - // ); - // }, - // { - // onSuccess: () => { - // notificationStore.pushNotification(`Successfully traded`, { - // type: "Success", - // }); - // }, - // }, - // ); + return sdk.api.tx.neoSwaps.buy( + marketId, + market?.categories?.length, + selectedAsset, + new Decimal(amount).mul(ZTG).toFixed(0), + amountOut.mul(slippageMultiplier).div(ZTG).toFixed(0), + ); + }, + { + onSuccess: () => { + notificationStore.pushNotification(`Successfully traded`, { + type: "Success", + }); + }, + }, + ); useEffect(() => { const subscription = watch((value, { name, type }) => { @@ -110,7 +130,7 @@ const BuyForm = ({ }, [watch, baseAssetBalance]); const onSubmit = () => { - // send(); + send(); }; return (
@@ -175,17 +195,14 @@ const BuyForm = ({ <>{formState.errors["amount"]?.message}
- {/* - Network Fee: {fee ? fee.amount.div(ZTG).toFixed(3) : 0}{" "} - {fee?.symbol} - */} + + Network Fee: {fee ? fee.amount.div(ZTG).toFixed(3) : 0}{" "} + {fee?.symbol} +
Swap diff --git a/components/trade-form/SellForm.tsx b/components/trade-form/SellForm.tsx index 97caaf551..ef00bca59 100644 --- a/components/trade-form/SellForm.tsx +++ b/components/trade-form/SellForm.tsx @@ -1,11 +1,18 @@ -import { MarketOutcomeAssetId, parseAssetId, ZTG } from "@zeitgeistpm/sdk"; +import { + isRpcSdk, + MarketOutcomeAssetId, + parseAssetId, + ZTG, +} from "@zeitgeistpm/sdk"; import MarketContextActionOutcomeSelector from "components/markets/MarketContextActionOutcomeSelector"; import FormTransactionButton from "components/ui/FormTransactionButton"; import Input from "components/ui/Input"; import Decimal from "decimal.js"; +import { DEFAULT_SLIPPAGE_PERCENTAGE } from "lib/constants"; import { useBalance } from "lib/hooks/queries/useBalance"; import { useChainConstants } from "lib/hooks/queries/useChainConstants"; import { useMarket } from "lib/hooks/queries/useMarket"; +import { useExtrinsic } from "lib/hooks/useExtrinsic"; import { useSdkv2 } from "lib/hooks/useSdkv2"; import { useNotifications } from "lib/state/notifications"; import { useWallet } from "lib/state/wallet"; @@ -60,25 +67,36 @@ const SellForm = ({ new Decimal(0.01), new Decimal(0.001), ); + const slippageMultiplier = (100 - DEFAULT_SLIPPAGE_PERCENTAGE) / 100; + const { isLoading, send, fee } = useExtrinsic( + () => { + const amount = getValues("amount"); + if ( + !isRpcSdk(sdk) || + !amount || + amount === "" || + market?.categories?.length == null || + !selectedAsset + ) { + return; + } - // const { isLoading, send, fee } = useExtrinsic( - // () => { - // const amount = getValues("amount"); - // if (!isRpcSdk(sdk) || !amount) return; - - // return sdk.api.tx.court.delegate( - // new Decimal(amount).mul(ZTG).toFixed(0), - // [], - // ); - // }, - // { - // onSuccess: () => { - // notificationStore.pushNotification(`Successfully traded`, { - // type: "Success", - // }); - // }, - // }, - // ); + return sdk.api.tx.neoSwaps.buy( + marketId, + market?.categories?.length, + selectedAsset, + new Decimal(amount).mul(ZTG).toFixed(0), + amountOut.mul(slippageMultiplier).div(ZTG).toFixed(0), + ); + }, + { + onSuccess: () => { + notificationStore.pushNotification(`Successfully traded`, { + type: "Success", + }); + }, + }, + ); useEffect(() => { const subscription = watch((value, { name, type }) => { @@ -107,7 +125,7 @@ const SellForm = ({ }, [watch, baseAssetBalance]); const onSubmit = () => { - // send(); + send(); }; return (
@@ -145,11 +163,6 @@ const SellForm = ({ options={outcomeAssets} onChange={(assetId) => { setSelectedAsset(assetId); - // reset(); - // setTradeItem({ - // action: tradeItem.action, - // assetId, - // }); }} /> )} @@ -173,17 +186,14 @@ const SellForm = ({ <>{formState.errors["amount"]?.message}
- {/* - Network Fee: {fee ? fee.amount.div(ZTG).toFixed(3) : 0}{" "} - {fee?.symbol} - */} + + Network Fee: {fee ? fee.amount.div(ZTG).toFixed(3) : 0}{" "} + {fee?.symbol} +
Swap diff --git a/package.json b/package.json index 359c8000a..29414d72a 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "@zeitgeistpm/avatara-nft-sdk": "^1.3.1", "@zeitgeistpm/avatara-react": "^1.3.2", "@zeitgeistpm/avatara-util": "^1.2.0", - "@zeitgeistpm/sdk": "2.39.0", + "@zeitgeistpm/sdk": "2.42.0", "@zeitgeistpm/utility": "^2.20.0", "axios": "^0.21.4", "boring-avatars": "^1.6.1", diff --git a/yarn.lock b/yarn.lock index 1a43b1412..ca3e5fae2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3319,14 +3319,14 @@ __metadata: languageName: node linkType: hard -"@zeitgeistpm/augment-api@npm:^2.17.0": - version: 2.17.0 - resolution: "@zeitgeistpm/augment-api@npm:2.17.0" +"@zeitgeistpm/augment-api@npm:^2.18.0": + version: 2.19.0 + resolution: "@zeitgeistpm/augment-api@npm:2.19.0" peerDependencies: "@polkadot/api-base": "*" "@polkadot/rpc-core": "*" "@polkadot/types": "*" - checksum: 9b6440888a5c0098b1777b971673b6ca3eea0f46c9e52a69cee86f4c3d5f5ae48605afdf3ab75f8d7ade1479f107995b88afbbc5d0bd1101b75e3275bb801cf6 + checksum: ea143cc03fd70414c6d1b702dc76c8522e07a5f2e4e210f6f58f9a448146d75dcaf78b8ae42da3d40eaeda0d542130cbfcb6144daa3800c9aebe4495c3ba8875 languageName: node linkType: hard @@ -3433,14 +3433,14 @@ __metadata: languageName: node linkType: hard -"@zeitgeistpm/indexer@npm:^3.10.0": - version: 3.10.0 - resolution: "@zeitgeistpm/indexer@npm:3.10.0" +"@zeitgeistpm/indexer@npm:^3.13.0": + version: 3.14.0 + resolution: "@zeitgeistpm/indexer@npm:3.14.0" dependencies: graphql: ^16.6.0 graphql-request: ^5.0.0 graphql-tag: ^2.12.6 - checksum: 1f6c8a35f1397f0f7ceacbf6c2c2e5f63bcfe4b7b3829eea1e5d9d3fe7eb8c09df980270001ba479117b90c15d458be57d6db8c9e31a7c24be331dbe41521074 + checksum: 2e4ed77d3f0c46b5596c36a99e84433a3214b375e6814cee8bc23338d21b8b24f298d31e9bfd0084e80884f67bf91f7cf4ebdfc2a76ffbeee86c0545d5384142 languageName: node linkType: hard @@ -3458,12 +3458,12 @@ __metadata: languageName: node linkType: hard -"@zeitgeistpm/sdk@npm:2.39.0": - version: 2.39.0 - resolution: "@zeitgeistpm/sdk@npm:2.39.0" +"@zeitgeistpm/sdk@npm:2.42.0": + version: 2.42.0 + resolution: "@zeitgeistpm/sdk@npm:2.42.0" dependencies: - "@zeitgeistpm/augment-api": ^2.17.0 - "@zeitgeistpm/indexer": ^3.10.0 + "@zeitgeistpm/augment-api": ^2.18.0 + "@zeitgeistpm/indexer": ^3.13.0 "@zeitgeistpm/rpc": ^2.11.0 "@zeitgeistpm/utility": ^2.21.0 "@zeitgeistpm/web3.storage": ^2.12.0 @@ -3480,7 +3480,7 @@ __metadata: "@polkadot/api": "*" "@polkadot/types": "*" "@polkadot/util": "*" - checksum: e575ee6e92f6d43f8ae22251885c3fd5fccedd3be864f9413c9154883e6bfe7d2ed62c6ee6781aaed6ca5a242edceddc738241e716a99ce8fbadcc5ff697362f + checksum: c099dc473185a6927b40a15a91a7d73e7f19cfed66a9f3f3bad5087808d54b4574099d4d72ae3b56a316170e37b2861e483942501f893a22012447e680df7b5f languageName: node linkType: hard @@ -3540,7 +3540,7 @@ __metadata: "@zeitgeistpm/avatara-nft-sdk": ^1.3.1 "@zeitgeistpm/avatara-react": ^1.3.2 "@zeitgeistpm/avatara-util": ^1.2.0 - "@zeitgeistpm/sdk": 2.39.0 + "@zeitgeistpm/sdk": 2.42.0 "@zeitgeistpm/utility": ^2.20.0 autoprefixer: 10.2.5 axios: ^0.21.4 From ac5911f89a28f5e5f211fa4ca6fd10a9e5362c10 Mon Sep 17 00:00:00 2001 From: Tom Robiquet Date: Tue, 24 Oct 2023 15:46:52 +0200 Subject: [PATCH 08/21] add lsmr conditionals and data fetching --- .../AssetTradingButtons.tsx | 29 ++++--- components/trade-form/BuyForm.tsx | 43 ++++++++--- lib/hooks/queries/amm2/useAmm2Pool.ts | 75 +++++++++++++++++++ lib/util/parse-asset-id.ts | 4 +- pages/markets/[marketid].tsx | 7 +- 5 files changed, 132 insertions(+), 26 deletions(-) create mode 100644 lib/hooks/queries/amm2/useAmm2Pool.ts diff --git a/components/assets/AssetActionButtons/AssetTradingButtons.tsx b/components/assets/AssetActionButtons/AssetTradingButtons.tsx index 60b57ad8f..306e22a70 100644 --- a/components/assets/AssetActionButtons/AssetTradingButtons.tsx +++ b/components/assets/AssetActionButtons/AssetTradingButtons.tsx @@ -4,10 +4,12 @@ import { ScalarAssetId, getMarketIdOf, } from "@zeitgeistpm/sdk"; +import TradeForm from "components/trade-form"; import Amm2TradeForm from "components/trade-form/Amm2TradeForm"; import { TradeTabType } from "components/trade-form/TradeTab"; import Modal from "components/ui/Modal"; import SecondaryButton from "components/ui/SecondaryButton"; +import { useMarket } from "lib/hooks/queries/useMarket"; import { useTradeItem } from "lib/hooks/trade"; import { useState } from "react"; @@ -18,8 +20,9 @@ const AssetTradingButtons = ({ }) => { const [isOpen, setIsOpen] = useState(false); const { data: tradeItem, set: setTradeItem } = useTradeItem(); - const marketId = getMarketIdOf(assetId); + const { data: market } = useMarket({ marketId }); + return ( <>
@@ -49,17 +52,19 @@ const AssetTradingButtons = ({ {tradeItem && ( setIsOpen(false)}> - {/* todo: add condition */} - {/* */} - + {market?.scoringRule === "Lsmr" ? ( + + ) : ( + + )} )} diff --git a/components/trade-form/BuyForm.tsx b/components/trade-form/BuyForm.tsx index 6ad699aec..8f46fdc15 100644 --- a/components/trade-form/BuyForm.tsx +++ b/components/trade-form/BuyForm.tsx @@ -9,6 +9,10 @@ import FormTransactionButton from "components/ui/FormTransactionButton"; import Input from "components/ui/Input"; import Decimal from "decimal.js"; import { DEFAULT_SLIPPAGE_PERCENTAGE } from "lib/constants"; +import { + lookupAssetReserve, + useAmm2Pool, +} from "lib/hooks/queries/amm2/useAmm2Pool"; import { useBalance } from "lib/hooks/queries/useBalance"; import { useChainConstants } from "lib/hooks/queries/useChainConstants"; import { useMarket } from "lib/hooks/queries/useMarket"; @@ -49,6 +53,8 @@ const BuyForm = ({ const wallet = useWallet(); const baseAsset = parseAssetIdString(market?.baseAsset); const { data: baseAssetBalance } = useBalance(wallet.realAddress, baseAsset); + const { data: pool } = useAmm2Pool(marketId); + console.log(pool); const outcomeAssets = market?.outcomeAssets.map( (assetIdString) => @@ -58,20 +64,35 @@ const BuyForm = ({ MarketOutcomeAssetId | undefined >(initialAsset ?? outcomeAssets?.[0]); - const amountIn = new Decimal(getValues("amount") ?? 0); + const formAmount = getValues("amount"); + + const amountIn = new Decimal( + formAmount && formAmount !== "" ? formAmount : 0, + ).mul(ZTG); + const assetReserve = + pool?.reserves && lookupAssetReserve(pool?.reserves, selectedAsset); - const amountOut = calculateSwapAmountOutForBuy( - new Decimal(10000), //todo: fetch params - amountIn, - new Decimal(100000), - new Decimal(0.01), - new Decimal(0.001), + console.log( + assetReserve?.div(ZTG).toString(), + amountIn.div(ZTG).toString(), + pool?.liquidity.div(ZTG).toString(), ); + const amountOut = + assetReserve && pool.liquidity + ? calculateSwapAmountOutForBuy( + assetReserve, + amountIn, + pool.liquidity, + new Decimal(0.01), + new Decimal(0.001), + ) + : new Decimal(0); + const slippageMultiplier = (100 - DEFAULT_SLIPPAGE_PERCENTAGE) / 100; - console.log(amountIn.toString()); - console.log(amountOut.toString()); + console.log(amountIn.div(ZTG).toString()); + console.log(amountOut.div(ZTG).toString()); const { isLoading, send, fee } = useExtrinsic( () => { @@ -91,7 +112,7 @@ const BuyForm = ({ market?.categories?.length, selectedAsset, new Decimal(amount).mul(ZTG).toFixed(0), - amountOut.mul(slippageMultiplier).div(ZTG).toFixed(0), + amountOut.mul(slippageMultiplier).toFixed(0), ); }, { @@ -139,7 +160,7 @@ const BuyForm = ({ className="w-full flex flex-col items-center gap-y-4" >
-
{amountOut.toString()}
+
{amountOut.div(ZTG).toString()}
{market && selectedAsset && ( ; + +export const useAmm2Pool = (marketId?: number) => { + const [sdk, id] = useSdkv2(); + + const enabled = !!sdk && marketId != null && isRpcSdk(sdk); + const query = useQuery( + [id, amm2PoolKey, marketId], + async () => { + if (!enabled) return; + const res = await sdk.api.query.neoSwaps.pools(marketId); + const unwrappedRes = res.unwrapOr(null); + + if (unwrappedRes) { + const reserves: ReserveMap = new Map(); + + unwrappedRes.reserves.forEach((reserve, asset) => { + const assetId = parseAssetIdString(asset.toString()); + if (IOMarketOutcomeAssetId.is(assetId)) { + reserves.set( + IOCategoricalAssetId.is(assetId) + ? assetId.CategoricalOutcome[1] + : assetId.ScalarOutcome[1], + new Decimal(reserve.toString()), + ); + } + }); + + return { + accountId: unwrappedRes.accountId.toString(), + baseAsset: parseAssetIdString(unwrappedRes.collateral.toString()), + liquidity: new Decimal(unwrappedRes.liquidityParameter.toString()), + swapFee: new Decimal(unwrappedRes.swapFee.toString()), + reserves, + }; + } + }, + { + enabled: enabled, + }, + ); + + return query; +}; + +// todo: change to hook that takes assetId +export const lookupAssetReserve = ( + map: ReserveMap, + asset?: string | AssetId, +) => { + const assetId = parseAssetIdString(asset); + if (IOMarketOutcomeAssetId.is(assetId)) { + return map.get( + IOCategoricalAssetId.is(assetId) + ? assetId.CategoricalOutcome[1] + : assetId.ScalarOutcome[1], + ); + } +}; diff --git a/lib/util/parse-asset-id.ts b/lib/util/parse-asset-id.ts index e3a4dfc0a..4897f9a91 100644 --- a/lib/util/parse-asset-id.ts +++ b/lib/util/parse-asset-id.ts @@ -1,5 +1,7 @@ import { AssetId, parseAssetId } from "@zeitgeistpm/sdk"; -export const parseAssetIdString = (assetId?: string): AssetId | undefined => { +export const parseAssetIdString = ( + assetId?: string | AssetId, +): AssetId | undefined => { return assetId ? parseAssetId(assetId).unrightOr(undefined) : undefined; }; diff --git a/pages/markets/[marketid].tsx b/pages/markets/[marketid].tsx index 78c058927..f8925a980 100644 --- a/pages/markets/[marketid].tsx +++ b/pages/markets/[marketid].tsx @@ -380,8 +380,11 @@ const Market: NextPage = ({
{market?.status === MarketStatus.Active ? ( <> - {/* */} - + {market?.scoringRule === "Lsmr" ? ( + + ) : ( + + )} ) : market?.status === MarketStatus.Closed && canReport ? ( <> From 7e1e5497d51b16a239796aea9c340fa4fd3f2dbb Mon Sep 17 00:00:00 2001 From: Tom Robiquet Date: Wed, 25 Oct 2023 16:51:01 +0200 Subject: [PATCH 09/21] add the amount from buying a full set --- lib/util/amm2.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/util/amm2.ts b/lib/util/amm2.ts index a3df4acbb..03e4818f6 100644 --- a/lib/util/amm2.ts +++ b/lib/util/amm2.ts @@ -22,7 +22,8 @@ export const calculateSwapAmountOutForBuy = ( .ln() .mul(liquidity) .plus(reserve) - .minus(amountIn); + .minus(amountIn) + .plus(amountIn); }; // sell outcome token for the base asset From d2bc9c6a5b8412ff456632a3df3fef4398eecd82 Mon Sep 17 00:00:00 2001 From: Tom Robiquet Date: Fri, 27 Oct 2023 15:57:51 +0200 Subject: [PATCH 10/21] adjust test --- lib/util/amm2.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/util/amm2.spec.ts b/lib/util/amm2.spec.ts index e17eac921..c6e7d1094 100644 --- a/lib/util/amm2.spec.ts +++ b/lib/util/amm2.spec.ts @@ -17,7 +17,7 @@ describe("amm2", () => { new Decimal(0), ); - expect(amountOut.toFixed(0)).toEqual("58496250072"); + expect(amountOut.toFixed(0)).toEqual("158496250072"); }); }); From 41e2742f71d630a6255c49c30ff80dded349e45f Mon Sep 17 00:00:00 2001 From: Tom Robiquet Date: Mon, 30 Oct 2023 12:17:57 +0100 Subject: [PATCH 11/21] calculate various metrics. add validation --- components/trade-form/BuyForm.tsx | 113 +++++++++++++++++++++++++----- 1 file changed, 94 insertions(+), 19 deletions(-) diff --git a/components/trade-form/BuyForm.tsx b/components/trade-form/BuyForm.tsx index 8f46fdc15..0ef69257f 100644 --- a/components/trade-form/BuyForm.tsx +++ b/components/trade-form/BuyForm.tsx @@ -13,6 +13,7 @@ import { lookupAssetReserve, useAmm2Pool, } from "lib/hooks/queries/amm2/useAmm2Pool"; +import { useAssetMetadata } from "lib/hooks/queries/useAssetMetadata"; import { useBalance } from "lib/hooks/queries/useBalance"; import { useChainConstants } from "lib/hooks/queries/useChainConstants"; import { useMarket } from "lib/hooks/queries/useMarket"; @@ -20,11 +21,16 @@ import { useExtrinsic } from "lib/hooks/useExtrinsic"; import { useSdkv2 } from "lib/hooks/useSdkv2"; import { useNotifications } from "lib/state/notifications"; import { useWallet } from "lib/state/wallet"; -import { calculateSwapAmountOutForBuy } from "lib/util/amm2"; +import { + calculateSpotPrice, + calculateSwapAmountOutForBuy, +} from "lib/util/amm2"; import { parseAssetIdString } from "lib/util/parse-asset-id"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useMemo } from "react"; import { useForm } from "react-hook-form"; +const slippageMultiplier = (100 - DEFAULT_SLIPPAGE_PERCENTAGE) / 100; + const BuyForm = ({ marketId, initialAsset, @@ -52,6 +58,8 @@ const BuyForm = ({ }); const wallet = useWallet(); const baseAsset = parseAssetIdString(market?.baseAsset); + const { data: assetMetadata } = useAssetMetadata(baseAsset); + const baseSymbol = assetMetadata?.symbol; const { data: baseAssetBalance } = useBalance(wallet.realAddress, baseAsset); const { data: pool } = useAmm2Pool(marketId); console.log(pool); @@ -78,21 +86,50 @@ const BuyForm = ({ pool?.liquidity.div(ZTG).toString(), ); - const amountOut = - assetReserve && pool.liquidity - ? calculateSwapAmountOutForBuy( - assetReserve, - amountIn, - pool.liquidity, - new Decimal(0.01), - new Decimal(0.001), - ) + const { + amountOut, + spotPrice, + newSpotPrice, + priceImpact, + maxProfit, + minAmountOut, + } = useMemo(() => { + const amountOut = + assetReserve && pool.liquidity + ? calculateSwapAmountOutForBuy( + assetReserve, + amountIn, + pool.liquidity, + new Decimal(0.01), + new Decimal(0.001), + ) + : new Decimal(0); + + const spotPrice = + assetReserve && calculateSpotPrice(assetReserve, pool?.liquidity); + + const newSpotPrice = + pool?.liquidity && + assetReserve && + calculateSpotPrice(assetReserve?.minus(amountOut), pool?.liquidity); + + const priceImpact = spotPrice + ? newSpotPrice?.div(spotPrice).minus(1).mul(100) : new Decimal(0); - const slippageMultiplier = (100 - DEFAULT_SLIPPAGE_PERCENTAGE) / 100; + const maxProfit = amountOut.minus(amountIn); - console.log(amountIn.div(ZTG).toString()); - console.log(amountOut.div(ZTG).toString()); + const minAmountOut = amountOut.mul(slippageMultiplier); + + return { + amountOut, + spotPrice, + newSpotPrice, + priceImpact, + maxProfit, + minAmountOut, + }; + }, [amountIn, pool?.liquidity, assetReserve]); const { isLoading, send, fee } = useExtrinsic( () => { @@ -112,7 +149,7 @@ const BuyForm = ({ market?.categories?.length, selectedAsset, new Decimal(amount).mul(ZTG).toFixed(0), - amountOut.mul(slippageMultiplier).toFixed(0), + minAmountOut.toFixed(0), ); }, { @@ -192,12 +229,37 @@ const BuyForm = ({ message: "Value is required", }, validate: (value) => { + const amountIn = new Decimal( + value && value !== "" ? value : 0, + ).mul(ZTG); + const amountOut = + assetReserve && pool.liquidity + ? calculateSwapAmountOutForBuy( + assetReserve, + amountIn, + pool.liquidity, + new Decimal(0.01), + new Decimal(0.001), + ) + : new Decimal(0); + + const newSpotPrice = + pool?.liquidity && + assetReserve && + calculateSpotPrice( + assetReserve?.minus(amountOut), + pool?.liquidity, + ); if (value > (baseAssetBalance?.div(ZTG).toNumber() ?? 0)) { return `Insufficient balance. Current balance: ${baseAssetBalance ?.div(ZTG) .toFixed(3)}`; } else if (value <= 0) { return "Value cannot be zero or less"; + } else if (newSpotPrice?.greaterThan(0.99)) { + return "New spot price cannot be greater than 0.99, please reduce trade size"; + } else if (newSpotPrice?.lessThan(0.01)) { + return "New spot price cannot be less than 0.01, please reduce trade size"; } }, })} @@ -212,14 +274,27 @@ const BuyForm = ({ disabled={!baseAssetBalance || baseAssetBalance.lessThanOrEqualTo(0)} {...register("percentage", { value: "0" })} /> -
+
<>{formState.errors["amount"]?.message}
-
- +
+
Network Fee: {fee ? fee.amount.div(ZTG).toFixed(3) : 0}{" "} {fee?.symbol} - +
+
+ Max profit: {maxProfit.toFixed(2)} + {baseSymbol} +
+
Min amount out:
+
+ Price after trade: + {newSpotPrice?.toFixed(2)} +
+
+ Price impact: + {priceImpact?.toFixed(2)}% +
Date: Mon, 30 Oct 2023 13:50:17 +0100 Subject: [PATCH 12/21] amm2 market spot prices --- lib/hooks/queries/amm2/useAmm2Pool.ts | 23 ++++++++++++------ lib/hooks/queries/useMarketSpotPrices.ts | 31 +++++++++++++++++++++++- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/lib/hooks/queries/amm2/useAmm2Pool.ts b/lib/hooks/queries/amm2/useAmm2Pool.ts index 062e7db1e..285c858e7 100644 --- a/lib/hooks/queries/amm2/useAmm2Pool.ts +++ b/lib/hooks/queries/amm2/useAmm2Pool.ts @@ -1,12 +1,9 @@ -import type { ZeitgeistPrimitivesAsset } from "@polkadot/types/lookup"; import { useQuery } from "@tanstack/react-query"; import { - isRpcSdk, - MarketOutcomeAssetId, - IOMarketOutcomeAssetId, - IOCategoricalAssetId, - parseAssetId, AssetId, + IOCategoricalAssetId, + IOMarketOutcomeAssetId, + isRpcSdk, } from "@zeitgeistpm/sdk"; import Decimal from "decimal.js"; import { useSdkv2 } from "lib/hooks/useSdkv2"; @@ -16,6 +13,14 @@ export const amm2PoolKey = "amm2-pool"; type ReserveMap = Map; +export type Amm2Pool = { + accountId: string; + baseAsset: AssetId; + liquidity: Decimal; + swapFee: Decimal; + reserves: ReserveMap; +}; + export const useAmm2Pool = (marketId?: number) => { const [sdk, id] = useSdkv2(); @@ -42,13 +47,15 @@ export const useAmm2Pool = (marketId?: number) => { } }); - return { + const pool: Amm2Pool = { accountId: unwrappedRes.accountId.toString(), - baseAsset: parseAssetIdString(unwrappedRes.collateral.toString()), + baseAsset: parseAssetIdString(unwrappedRes.collateral.toString())!, liquidity: new Decimal(unwrappedRes.liquidityParameter.toString()), swapFee: new Decimal(unwrappedRes.swapFee.toString()), reserves, }; + + return pool; } }, { diff --git a/lib/hooks/queries/useMarketSpotPrices.ts b/lib/hooks/queries/useMarketSpotPrices.ts index 7e3fb617f..59462931a 100644 --- a/lib/hooks/queries/useMarketSpotPrices.ts +++ b/lib/hooks/queries/useMarketSpotPrices.ts @@ -9,6 +9,8 @@ import { FullMarketFragment } from "@zeitgeistpm/indexer"; import { OrmlTokensAccountData } from "@polkadot/types/lookup"; import { calcResolvedMarketPrices } from "lib/util/calc-resolved-market-prices"; import { usePoolBaseBalance } from "./usePoolBaseBalance"; +import { Amm2Pool, useAmm2Pool } from "./amm2/useAmm2Pool"; +import { calculateSpotPrice } from "lib/util/amm2"; export const marketSpotPricesKey = "market-spot-prices"; @@ -34,6 +36,8 @@ export const useMarketSpotPrices = ( blockNumber, ); + const { data: amm2Pool } = useAmm2Pool(marketId); + const enabled = isRpcSdk(sdk) && marketId != null && @@ -41,15 +45,26 @@ export const useMarketSpotPrices = ( !!market && !!basePoolBalance && !!balances && + !!amm2Pool && balances.length !== 0; const query = useQuery( - [id, marketSpotPricesKey, pool, blockNumber, balances, basePoolBalance], + [ + id, + marketSpotPricesKey, + pool, + blockNumber, + balances, + basePoolBalance, + amm2Pool, + ], async () => { if (!enabled) return; const spotPrices: MarketPrices = market?.status !== "Resolved" ? calcMarketPrices(market, basePoolBalance, balances) + : market.scoringRule === "Lsmr" + ? calcMarketPricesAmm2(amm2Pool) : calcResolvedMarketPrices(market); return spotPrices; @@ -62,6 +77,20 @@ export const useMarketSpotPrices = ( return query; }; +const calcMarketPricesAmm2 = (pool: Amm2Pool) => { + const spotPrices: MarketPrices = new Map(); + + Array.from(pool.reserves.values()).forEach((reserve, index) => { + const spotPrice = calculateSpotPrice(reserve, pool.liquidity); + + if (!spotPrice.isNaN()) { + spotPrices.set(index, spotPrice); + } + }); + + return spotPrices; +}; + const calcMarketPrices = ( market: FullMarketFragment, basePoolBalance: Decimal, From 6c43aafdcbbd4350131ad09e861f4aec1ce56903 Mon Sep 17 00:00:00 2001 From: Tom Robiquet Date: Tue, 31 Oct 2023 15:31:05 +0100 Subject: [PATCH 13/21] fix build --- .../AssetActionButtons/AssetTradingButtons.tsx | 3 ++- lib/hooks/queries/useMarketSpotPrices.ts | 12 ++++++------ pages/markets/[marketid].tsx | 3 ++- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/components/assets/AssetActionButtons/AssetTradingButtons.tsx b/components/assets/AssetActionButtons/AssetTradingButtons.tsx index 306e22a70..4e201e610 100644 --- a/components/assets/AssetActionButtons/AssetTradingButtons.tsx +++ b/components/assets/AssetActionButtons/AssetTradingButtons.tsx @@ -12,6 +12,7 @@ import SecondaryButton from "components/ui/SecondaryButton"; import { useMarket } from "lib/hooks/queries/useMarket"; import { useTradeItem } from "lib/hooks/trade"; import { useState } from "react"; +import { ScoringRule } from "@zeitgeistpm/indexer"; const AssetTradingButtons = ({ assetId, @@ -52,7 +53,7 @@ const AssetTradingButtons = ({ {tradeItem && ( setIsOpen(false)}> - {market?.scoringRule === "Lsmr" ? ( + {market?.scoringRule === ScoringRule.Lmsr ? ( import("../../components/trade-form"), { ssr: false, @@ -381,7 +382,7 @@ const Market: NextPage = ({
{market?.status === MarketStatus.Active ? ( <> - {market?.scoringRule === "Lsmr" ? ( + {market?.scoringRule === ScoringRule.Cpmm ? ( ) : ( From ebec40fa64e98a0ba35c0c0c5682f2ee48833532 Mon Sep 17 00:00:00 2001 From: Tom Robiquet Date: Tue, 31 Oct 2023 16:33:29 +0100 Subject: [PATCH 14/21] update sell form --- components/trade-form/BuyForm.tsx | 13 ++--- components/trade-form/SellForm.tsx | 89 ++++++++++++++++++++++++------ 2 files changed, 76 insertions(+), 26 deletions(-) diff --git a/components/trade-form/BuyForm.tsx b/components/trade-form/BuyForm.tsx index 0ef69257f..ea0e6af24 100644 --- a/components/trade-form/BuyForm.tsx +++ b/components/trade-form/BuyForm.tsx @@ -62,7 +62,6 @@ const BuyForm = ({ const baseSymbol = assetMetadata?.symbol; const { data: baseAssetBalance } = useBalance(wallet.realAddress, baseAsset); const { data: pool } = useAmm2Pool(marketId); - console.log(pool); const outcomeAssets = market?.outcomeAssets.map( (assetIdString) => @@ -80,12 +79,6 @@ const BuyForm = ({ const assetReserve = pool?.reserves && lookupAssetReserve(pool?.reserves, selectedAsset); - console.log( - assetReserve?.div(ZTG).toString(), - amountIn.div(ZTG).toString(), - pool?.liquidity.div(ZTG).toString(), - ); - const { amountOut, spotPrice, @@ -283,10 +276,12 @@ const BuyForm = ({ {fee?.symbol}
- Max profit: {maxProfit.toFixed(2)} + Max profit: {maxProfit.div(ZTG).toFixed(2)} {baseSymbol}
-
Min amount out:
+
+ Min amount out: {minAmountOut.div(ZTG).toFixed(2)} +
Price after trade: {newSpotPrice?.toFixed(2)} diff --git a/components/trade-form/SellForm.tsx b/components/trade-form/SellForm.tsx index ef00bca59..6dfaeff70 100644 --- a/components/trade-form/SellForm.tsx +++ b/components/trade-form/SellForm.tsx @@ -9,6 +9,10 @@ import FormTransactionButton from "components/ui/FormTransactionButton"; import Input from "components/ui/Input"; import Decimal from "decimal.js"; import { DEFAULT_SLIPPAGE_PERCENTAGE } from "lib/constants"; +import { + lookupAssetReserve, + useAmm2Pool, +} from "lib/hooks/queries/amm2/useAmm2Pool"; import { useBalance } from "lib/hooks/queries/useBalance"; import { useChainConstants } from "lib/hooks/queries/useChainConstants"; import { useMarket } from "lib/hooks/queries/useMarket"; @@ -16,11 +20,16 @@ import { useExtrinsic } from "lib/hooks/useExtrinsic"; import { useSdkv2 } from "lib/hooks/useSdkv2"; import { useNotifications } from "lib/state/notifications"; import { useWallet } from "lib/state/wallet"; -import { calculateSwapAmountOutForSell } from "lib/util/amm2"; +import { + calculateSpotPrice, + calculateSwapAmountOutForSell, +} from "lib/util/amm2"; import { parseAssetIdString } from "lib/util/parse-asset-id"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useMemo } from "react"; import { useForm } from "react-hook-form"; +const slippageMultiplier = (100 - DEFAULT_SLIPPAGE_PERCENTAGE) / 100; + const SellForm = ({ marketId, initialAsset, @@ -49,6 +58,7 @@ const SellForm = ({ const wallet = useWallet(); const baseAsset = parseAssetIdString(market?.baseAsset); const { data: baseAssetBalance } = useBalance(wallet.realAddress, baseAsset); + const { data: pool } = useAmm2Pool(marketId); const outcomeAssets = market?.outcomeAssets.map( (assetIdString) => @@ -58,16 +68,50 @@ const SellForm = ({ MarketOutcomeAssetId | undefined >(initialAsset ?? outcomeAssets?.[0]); - const amountIn = new Decimal(getValues("amount") ?? 0); + const formAmount = getValues("amount"); + + const amountIn = new Decimal( + formAmount && formAmount !== "" ? formAmount : 0, + ).mul(ZTG); + const assetReserve = + pool?.reserves && lookupAssetReserve(pool?.reserves, selectedAsset); + + const { amountOut, spotPrice, newSpotPrice, priceImpact, minAmountOut } = + useMemo(() => { + const amountOut = + assetReserve && pool.liquidity + ? calculateSwapAmountOutForSell( + assetReserve, + amountIn, + pool.liquidity, + new Decimal(0), + new Decimal(0), + ) + : new Decimal(0); + + const spotPrice = + assetReserve && calculateSpotPrice(assetReserve, pool?.liquidity); + + const newSpotPrice = + pool?.liquidity && + assetReserve && + calculateSpotPrice(assetReserve?.minus(amountOut), pool?.liquidity); + + const priceImpact = spotPrice + ? newSpotPrice?.div(spotPrice).minus(1).mul(100) + : new Decimal(0); + + const minAmountOut = amountOut.mul(slippageMultiplier); + + return { + amountOut, + spotPrice, + newSpotPrice, + priceImpact, + minAmountOut, + }; + }, [amountIn, pool?.liquidity, assetReserve]); - const amountOut = calculateSwapAmountOutForSell( - new Decimal(10000), //todo: fetch params - amountIn, - new Decimal(100000), - new Decimal(0.01), - new Decimal(0.001), - ); - const slippageMultiplier = (100 - DEFAULT_SLIPPAGE_PERCENTAGE) / 100; const { isLoading, send, fee } = useExtrinsic( () => { const amount = getValues("amount"); @@ -81,12 +125,12 @@ const SellForm = ({ return; } - return sdk.api.tx.neoSwaps.buy( + return sdk.api.tx.neoSwaps.sell( marketId, market?.categories?.length, selectedAsset, new Decimal(amount).mul(ZTG).toFixed(0), - amountOut.mul(slippageMultiplier).div(ZTG).toFixed(0), + minAmountOut.toFixed(0), ); }, { @@ -170,7 +214,7 @@ const SellForm = ({
For
-
{amountOut.toString()}
+
{amountOut.div(ZTG).toFixed(5)}
{constants?.tokenSymbol} @@ -185,11 +229,22 @@ const SellForm = ({
<>{formState.errors["amount"]?.message}
-
- +
+
Network Fee: {fee ? fee.amount.div(ZTG).toFixed(3) : 0}{" "} {fee?.symbol} - +
+
+ Min amount out: {minAmountOut.div(ZTG).toFixed(2)} +
+
+ Price after trade: + {newSpotPrice?.toFixed(2)} +
+
+ Price impact: + {priceImpact?.toFixed(2)}% +
Date: Wed, 1 Nov 2023 11:42:15 +0100 Subject: [PATCH 15/21] invalidate amm2 pool cache --- lib/hooks/useSubscribeBlockEvents.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/hooks/useSubscribeBlockEvents.ts b/lib/hooks/useSubscribeBlockEvents.ts index b8252afae..7cefca0fe 100644 --- a/lib/hooks/useSubscribeBlockEvents.ts +++ b/lib/hooks/useSubscribeBlockEvents.ts @@ -7,6 +7,7 @@ import { balanceRootKey } from "./queries/useBalance"; import { currencyBalanceRootKey } from "./queries/useCurrencyBalances"; import { tradeItemStateRootQueryKey } from "./queries/useTradeItemState"; import { useSdkv2 } from "./useSdkv2"; +import { amm2PoolKey } from "./queries/amm2/useAmm2Pool"; export const useSubscribeBlockEvents = () => { const [sdk, id] = useSdkv2(); @@ -16,6 +17,7 @@ export const useSubscribeBlockEvents = () => { if (sdk && isRpcSdk(sdk)) { sdk.api.query.system.events((events) => { const accounts = new Set(); + const amm2MarketIds = new Set(); events.forEach((record) => { const { event } = record; @@ -27,6 +29,10 @@ export const useSubscribeBlockEvents = () => { types[index].type === "AccountId32" ) { accounts.add(data.toString()); + } else if (event.section === "neoSwaps") { + if (event.data.names?.includes("marketId")) { + amm2MarketIds.add(event.data["marketId"].toString()); + } } }); }); @@ -50,6 +56,10 @@ export const useSubscribeBlockEvents = () => { ]); queryClient.invalidateQueries([id, currencyBalanceRootKey, account]); }); + + amm2MarketIds.forEach((marketId) => { + queryClient.invalidateQueries([id, amm2PoolKey, Number(marketId)]); + }); }); } }, [sdk]); From c477407c58a84818027f6f0bdc5951db245808d5 Mon Sep 17 00:00:00 2001 From: Tom Robiquet Date: Thu, 2 Nov 2023 16:21:15 +0100 Subject: [PATCH 16/21] add new approximation functions --- lib/util/amm2.ts | 52 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/lib/util/amm2.ts b/lib/util/amm2.ts index 03e4818f6..fbaf85049 100644 --- a/lib/util/amm2.ts +++ b/lib/util/amm2.ts @@ -4,7 +4,7 @@ import Decimal from "decimal.js"; export const calculateSwapAmountOutForBuy = ( reserve: Decimal, // amount of asset you want to buy in the pool amountIn: Decimal, // amount you want to spend - liquidity: Decimal, + liquidity: Decimal, // liqudity parameter of the pool poolFee: Decimal, // 1% is 0.01 creatorFee: Decimal, // 1% is 0.01 ) => { @@ -29,8 +29,8 @@ export const calculateSwapAmountOutForBuy = ( // sell outcome token for the base asset export const calculateSwapAmountOutForSell = ( reserve: Decimal, // amount of asset you want to sell in the pool - amountIn: Decimal, //amount of asset to sell - liquidity: Decimal, + amountIn: Decimal, // amount of asset to sell + liquidity: Decimal, // liqudity parameter of the pool poolFee: Decimal, // 1% is 0.01 creatorFee: Decimal, // 1% is 0.01 ) => { @@ -54,7 +54,51 @@ export const calculateSwapAmountOutForSell = ( export const calculateSpotPrice = ( reserve: Decimal, // amount of asset in the pool - liquidity: Decimal, + liquidity: Decimal, // liqudity parameter of the pool ) => { return new Decimal(0).minus(reserve).div(liquidity).exp(); }; + +export const approximateMaxAmountInForBuy = ( + reserve: Decimal, // amount of asset in the pool + liquidity: Decimal, // liqudity parameter of the pool +) => { + const price = calculateSpotPrice(reserve, liquidity).toNumber(); + console.log(price); + + return liquidity.mul( + 0.99 * + (-10015.14417168339605268557 * price ** 10 + + 46175.29901770254946313798 * price ** 9 - + 90642.29890720185358077288 * price ** 8 + + 98754.41788689797976985574 * price ** 7 - + 65270.04041910833620931953 * price ** 6 + + 26866.82939015745796496049 * price ** 5 - + 6805.08835771731082786573 * price ** 4 + + 1008.86080878299947016785 * price ** 3 - + 79.48474558820969093631 * price ** 2 + + 1.45265602009115950999 * price ** 1 + + 4.5837281790982524754), + ); +}; + +export const approximateMaxAmountInForSell = ( + reserve: Decimal, // amount of asset in the pool + liquidity: Decimal, // liqudity parameter of the pool +) => { + const price = calculateSpotPrice(reserve, liquidity).toNumber(); + return ( + 0.99 * + (6027.48739001329704478849 * price ** 10 - + 23943.83771737971983384341 * price ** 9 + + 36748.94249659497290849686 * price ** 8 - + 24233.23433796403696760535 * price ** 7 + + 453.48665119856707406143 * price ** 6 + + 10032.97899602322468126658 * price ** 5 - + 7080.22041420203277084511 * price ** 4 + + 2410.0255212617116740148 * price ** 3 - + 459.95159954049660200326 * price ** 2 + + 54.14308593643040978804 * price ** 1 - + 0.538594836861739279) + ); +}; From 51226f75f4b019be018da3060c9d86f962ee2bf6 Mon Sep 17 00:00:00 2001 From: Tom Robiquet Date: Fri, 3 Nov 2023 12:15:26 +0100 Subject: [PATCH 17/21] update form validation and styling --- components/trade-form/BuyForm.tsx | 111 ++++++++++------------ components/trade-form/SellForm.tsx | 144 ++++++++++++++++------------- lib/util/amm2.ts | 26 +++--- package.json | 4 +- yarn.lock | 37 +++++--- 5 files changed, 169 insertions(+), 153 deletions(-) diff --git a/components/trade-form/BuyForm.tsx b/components/trade-form/BuyForm.tsx index ea0e6af24..6d7bf35d1 100644 --- a/components/trade-form/BuyForm.tsx +++ b/components/trade-form/BuyForm.tsx @@ -22,9 +22,11 @@ import { useSdkv2 } from "lib/hooks/useSdkv2"; import { useNotifications } from "lib/state/notifications"; import { useWallet } from "lib/state/wallet"; import { + approximateMaxAmountInForBuy, calculateSpotPrice, calculateSwapAmountOutForBuy, } from "lib/util/amm2"; +import { formatNumberCompact } from "lib/util/format-compact"; import { parseAssetIdString } from "lib/util/parse-asset-id"; import { useState, useEffect, useMemo } from "react"; import { useForm } from "react-hook-form"; @@ -79,6 +81,14 @@ const BuyForm = ({ const assetReserve = pool?.reserves && lookupAssetReserve(pool?.reserves, selectedAsset); + const maxAmountIn = useMemo(() => { + return ( + assetReserve && + pool && + approximateMaxAmountInForBuy(assetReserve, pool.liquidity) + ); + }, [assetReserve, pool?.liquidity]); + const { amountOut, spotPrice, @@ -101,10 +111,11 @@ const BuyForm = ({ const spotPrice = assetReserve && calculateSpotPrice(assetReserve, pool?.liquidity); + const poolAmountOut = amountOut.minus(amountIn); const newSpotPrice = pool?.liquidity && assetReserve && - calculateSpotPrice(assetReserve?.minus(amountOut), pool?.liquidity); + calculateSpotPrice(assetReserve?.minus(poolAmountOut), pool?.liquidity); const priceImpact = spotPrice ? newSpotPrice?.div(spotPrice).minus(1).mul(100) @@ -158,12 +169,15 @@ const BuyForm = ({ const subscription = watch((value, { name, type }) => { const changedByUser = type != null; - if (!changedByUser || !baseAssetBalance) return; + if (!changedByUser || !baseAssetBalance || !maxAmountIn) return; if (name === "percentage") { + const max = baseAssetBalance.greaterThan(maxAmountIn) + ? maxAmountIn + : baseAssetBalance; setValue( "amount", - baseAssetBalance.mul(value.percentage).div(100).div(ZTG).toNumber(), + max.mul(value.percentage).div(100).div(ZTG).toNumber(), ); } else if (name === "amount" && value.amount !== "") { setValue( @@ -178,19 +192,19 @@ const BuyForm = ({ trigger("amount"); }); return () => subscription.unsubscribe(); - }, [watch, baseAssetBalance]); + }, [watch, baseAssetBalance, maxAmountIn]); const onSubmit = () => { send(); }; return ( -
+
-
-
{amountOut.div(ZTG).toString()}
+
+
{amountOut.div(ZTG).toFixed(3)}
{market && selectedAsset && ( { setSelectedAsset(assetId); - // reset(); - // setTradeItem({ - // action: tradeItem.action, - // assetId, - // }); + trigger(); }} /> )}
-
For
-
+
For
+
{ - const amountIn = new Decimal( - value && value !== "" ? value : 0, - ).mul(ZTG); - const amountOut = - assetReserve && pool.liquidity - ? calculateSwapAmountOutForBuy( - assetReserve, - amountIn, - pool.liquidity, - new Decimal(0.01), - new Decimal(0.001), - ) - : new Decimal(0); - - const newSpotPrice = - pool?.liquidity && - assetReserve && - calculateSpotPrice( - assetReserve?.minus(amountOut), - pool?.liquidity, - ); if (value > (baseAssetBalance?.div(ZTG).toNumber() ?? 0)) { return `Insufficient balance. Current balance: ${baseAssetBalance ?.div(ZTG) .toFixed(3)}`; } else if (value <= 0) { return "Value cannot be zero or less"; - } else if (newSpotPrice?.greaterThan(0.99)) { - return "New spot price cannot be greater than 0.99, please reduce trade size"; - } else if (newSpotPrice?.lessThan(0.01)) { - return "New spot price cannot be less than 0.01, please reduce trade size"; + } else if (maxAmountIn?.div(ZTG)?.lessThanOrEqualTo(value)) { + return `Maximum amount of ${baseSymbol} that can be traded is ${maxAmountIn + .div(ZTG) + .toFixed(3)}`; } }, })} @@ -267,28 +256,21 @@ const BuyForm = ({ disabled={!baseAssetBalance || baseAssetBalance.lessThanOrEqualTo(0)} {...register("percentage", { value: "0" })} /> -
- <>{formState.errors["amount"]?.message} -
-
-
- Network Fee: {fee ? fee.amount.div(ZTG).toFixed(3) : 0}{" "} - {fee?.symbol} +
+
+ <>{formState.errors["amount"]?.message}
-
- Max profit: {maxProfit.div(ZTG).toFixed(2)} - {baseSymbol} +
+
Max profit:
+
+ {maxProfit.div(ZTG).toFixed(2)} {baseSymbol} +
-
- Min amount out: {minAmountOut.div(ZTG).toFixed(2)} -
-
- Price after trade: - {newSpotPrice?.toFixed(2)} -
-
- Price impact: - {priceImpact?.toFixed(2)}% +
+
Price after trade:
+
+ {newSpotPrice?.toFixed(2)} ({priceImpact?.toFixed(2)}%) +
- Swap +
+
Buy
+
+ Network fee:{" "} + {formatNumberCompact(fee?.amount.div(ZTG).toNumber() ?? 0)}{" "} + {fee?.symbol} +
+
diff --git a/components/trade-form/SellForm.tsx b/components/trade-form/SellForm.tsx index 6dfaeff70..87aaf0c40 100644 --- a/components/trade-form/SellForm.tsx +++ b/components/trade-form/SellForm.tsx @@ -21,9 +21,11 @@ import { useSdkv2 } from "lib/hooks/useSdkv2"; import { useNotifications } from "lib/state/notifications"; import { useWallet } from "lib/state/wallet"; import { + approximateMaxAmountInForSell, calculateSpotPrice, calculateSwapAmountOutForSell, } from "lib/util/amm2"; +import { formatNumberCompact } from "lib/util/format-compact"; import { parseAssetIdString } from "lib/util/parse-asset-id"; import { useState, useEffect, useMemo } from "react"; import { useForm } from "react-hook-form"; @@ -56,8 +58,6 @@ const SellForm = ({ marketId, }); const wallet = useWallet(); - const baseAsset = parseAssetIdString(market?.baseAsset); - const { data: baseAssetBalance } = useBalance(wallet.realAddress, baseAsset); const { data: pool } = useAmm2Pool(marketId); const outcomeAssets = market?.outcomeAssets.map( @@ -68,6 +68,10 @@ const SellForm = ({ MarketOutcomeAssetId | undefined >(initialAsset ?? outcomeAssets?.[0]); + const { data: selectedAssetBalance } = useBalance( + wallet.realAddress, + selectedAsset, + ); const formAmount = getValues("amount"); const amountIn = new Decimal( @@ -76,41 +80,49 @@ const SellForm = ({ const assetReserve = pool?.reserves && lookupAssetReserve(pool?.reserves, selectedAsset); - const { amountOut, spotPrice, newSpotPrice, priceImpact, minAmountOut } = - useMemo(() => { - const amountOut = - assetReserve && pool.liquidity - ? calculateSwapAmountOutForSell( - assetReserve, - amountIn, - pool.liquidity, - new Decimal(0), - new Decimal(0), - ) - : new Decimal(0); + const maxAmountIn = useMemo(() => { + return ( + assetReserve && + pool && + approximateMaxAmountInForSell(assetReserve, pool.liquidity) + ); + }, [assetReserve, pool?.liquidity]); - const spotPrice = - assetReserve && calculateSpotPrice(assetReserve, pool?.liquidity); + const { amountOut, newSpotPrice, priceImpact, minAmountOut } = useMemo(() => { + const amountOut = + assetReserve && pool.liquidity + ? calculateSwapAmountOutForSell( + assetReserve, + amountIn, + pool.liquidity, + new Decimal(0), + new Decimal(0), + ) + : new Decimal(0); - const newSpotPrice = - pool?.liquidity && - assetReserve && - calculateSpotPrice(assetReserve?.minus(amountOut), pool?.liquidity); + const spotPrice = + assetReserve && calculateSpotPrice(assetReserve, pool?.liquidity); - const priceImpact = spotPrice - ? newSpotPrice?.div(spotPrice).minus(1).mul(100) - : new Decimal(0); + const poolAmountIn = amountIn.minus(amountOut); + const newSpotPrice = + pool?.liquidity && + assetReserve && + calculateSpotPrice(assetReserve?.plus(poolAmountIn), pool?.liquidity); - const minAmountOut = amountOut.mul(slippageMultiplier); + const priceImpact = spotPrice + ? newSpotPrice?.div(spotPrice).minus(1).mul(100) + : new Decimal(0); - return { - amountOut, - spotPrice, - newSpotPrice, - priceImpact, - minAmountOut, - }; - }, [amountIn, pool?.liquidity, assetReserve]); + const minAmountOut = amountOut.mul(slippageMultiplier); + + return { + amountOut, + spotPrice, + newSpotPrice, + priceImpact, + minAmountOut, + }; + }, [amountIn, pool?.liquidity, assetReserve]); const { isLoading, send, fee } = useExtrinsic( () => { @@ -146,19 +158,22 @@ const SellForm = ({ const subscription = watch((value, { name, type }) => { const changedByUser = type != null; - if (!changedByUser || !baseAssetBalance) return; + if (!changedByUser || !selectedAssetBalance || !maxAmountIn) return; if (name === "percentage") { + const max = selectedAssetBalance.greaterThan(maxAmountIn) + ? maxAmountIn + : selectedAssetBalance; setValue( "amount", - baseAssetBalance.mul(value.percentage).div(100).div(ZTG).toNumber(), + max.mul(value.percentage).div(100).div(ZTG).toNumber(), ); } else if (name === "amount" && value.amount !== "") { setValue( "percentage", new Decimal(value.amount) .mul(ZTG) - .div(baseAssetBalance) + .div(selectedAssetBalance) .mul(100) .toString(), ); @@ -166,7 +181,7 @@ const SellForm = ({ trigger("amount"); }); return () => subscription.unsubscribe(); - }, [watch, baseAssetBalance]); + }, [watch, selectedAssetBalance, maxAmountIn]); const onSubmit = () => { send(); @@ -177,7 +192,7 @@ const SellForm = ({ onSubmit={handleSubmit(onSubmit)} className="w-full flex flex-col items-center gap-y-4" > -
+
{ - if (value > (baseAssetBalance?.div(ZTG).toNumber() ?? 0)) { - return `Insufficient balance. Current balance: ${baseAssetBalance + if (value > (selectedAssetBalance?.div(ZTG).toNumber() ?? 0)) { + return `Insufficient balance. Current balance: ${selectedAssetBalance ?.div(ZTG) .toFixed(3)}`; } else if (value <= 0) { return "Value cannot be zero or less"; + } else if (maxAmountIn?.div(ZTG)?.lessThanOrEqualTo(value)) { + return `Maximum amount that can be traded is ${maxAmountIn + .div(ZTG) + .toFixed(3)}`; } }, })} @@ -212,38 +231,28 @@ const SellForm = ({ )}
-
For
-
-
{amountOut.div(ZTG).toFixed(5)}
- -
- {constants?.tokenSymbol} -
+
For
+
+
{amountOut.div(ZTG).toFixed(5)}
+
{constants?.tokenSymbol}
-
- <>{formState.errors["amount"]?.message} -
-
- Network Fee: {fee ? fee.amount.div(ZTG).toFixed(3) : 0}{" "} - {fee?.symbol} -
-
- Min amount out: {minAmountOut.div(ZTG).toFixed(2)} +
+ <>{formState.errors["amount"]?.message}
-
- Price after trade: - {newSpotPrice?.toFixed(2)} -
-
- Price impact: - {priceImpact?.toFixed(2)}% +
+
Price after trade:
+
+ {newSpotPrice?.toFixed(2)} ({priceImpact?.toFixed(2)}%) +
- Swap +
+
Sell
+
+ Network fee:{" "} + {formatNumberCompact(fee?.amount.div(ZTG).toNumber() ?? 0)}{" "} + {fee?.symbol} +
+
diff --git a/lib/util/amm2.ts b/lib/util/amm2.ts index fbaf85049..f0acf5a12 100644 --- a/lib/util/amm2.ts +++ b/lib/util/amm2.ts @@ -64,7 +64,6 @@ export const approximateMaxAmountInForBuy = ( liquidity: Decimal, // liqudity parameter of the pool ) => { const price = calculateSpotPrice(reserve, liquidity).toNumber(); - console.log(price); return liquidity.mul( 0.99 * @@ -87,18 +86,19 @@ export const approximateMaxAmountInForSell = ( liquidity: Decimal, // liqudity parameter of the pool ) => { const price = calculateSpotPrice(reserve, liquidity).toNumber(); - return ( + + return liquidity.mul( 0.99 * - (6027.48739001329704478849 * price ** 10 - - 23943.83771737971983384341 * price ** 9 + - 36748.94249659497290849686 * price ** 8 - - 24233.23433796403696760535 * price ** 7 + - 453.48665119856707406143 * price ** 6 + - 10032.97899602322468126658 * price ** 5 - - 7080.22041420203277084511 * price ** 4 + - 2410.0255212617116740148 * price ** 3 - - 459.95159954049660200326 * price ** 2 + - 54.14308593643040978804 * price ** 1 - - 0.538594836861739279) + (6027.48739001329704478849 * price ** 10 - + 23943.83771737971983384341 * price ** 9 + + 36748.94249659497290849686 * price ** 8 - + 24233.23433796403696760535 * price ** 7 + + 453.48665119856707406143 * price ** 6 + + 10032.97899602322468126658 * price ** 5 - + 7080.22041420203277084511 * price ** 4 + + 2410.0255212617116740148 * price ** 3 - + 459.95159954049660200326 * price ** 2 + + 54.14308593643040978804 * price ** 1 - + 0.538594836861739279), ); }; diff --git a/package.json b/package.json index 6ce54dd56..9af7aa1eb 100644 --- a/package.json +++ b/package.json @@ -37,11 +37,11 @@ "@vercel/og": "^0.5.19", "@yornaath/batshit": "^0.7.1", "@yornaath/batshit-devtools-react": "^0.5.4", - "@zeitgeistpm/augment-api": "2.22.0", + "@zeitgeistpm/augment-api": "2.23.0", "@zeitgeistpm/avatara-nft-sdk": "^1.3.1", "@zeitgeistpm/avatara-react": "^1.3.2", "@zeitgeistpm/avatara-util": "^1.2.0", - "@zeitgeistpm/sdk": "2.46.0", + "@zeitgeistpm/sdk": "2.47.0", "@zeitgeistpm/utility": "^2.20.0", "axios": "^0.21.4", "boring-avatars": "^1.6.1", diff --git a/yarn.lock b/yarn.lock index 707c557db..627186c75 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3308,7 +3308,18 @@ __metadata: languageName: node linkType: hard -"@zeitgeistpm/augment-api@npm:2.22.0, @zeitgeistpm/augment-api@npm:^2.21.0, @zeitgeistpm/augment-api@npm:^2.22.0": +"@zeitgeistpm/augment-api@npm:2.23.0, @zeitgeistpm/augment-api@npm:^2.23.0": + version: 2.23.0 + resolution: "@zeitgeistpm/augment-api@npm:2.23.0" + peerDependencies: + "@polkadot/api-base": "*" + "@polkadot/rpc-core": "*" + "@polkadot/types": "*" + checksum: 482ea1c15b8c2e42325b730015979d16b369348307497d0a3c9451dee20aaa507e1dd1f6c0b241ca05c8d466bac075c0146ec917d96222e67c4df41abf213c79 + languageName: node + linkType: hard + +"@zeitgeistpm/augment-api@npm:^2.21.0": version: 2.22.0 resolution: "@zeitgeistpm/augment-api@npm:2.22.0" peerDependencies: @@ -3411,14 +3422,14 @@ __metadata: languageName: node linkType: hard -"@zeitgeistpm/indexer@npm:^3.17.0": - version: 3.17.0 - resolution: "@zeitgeistpm/indexer@npm:3.17.0" +"@zeitgeistpm/indexer@npm:^3.18.0": + version: 3.18.0 + resolution: "@zeitgeistpm/indexer@npm:3.18.0" dependencies: graphql: ^16.6.0 graphql-request: ^5.0.0 graphql-tag: ^2.12.6 - checksum: 5ac0556457dc50bb3eaea51eb77d727ad88bb34a775b4ca88ecc396a078ecd6705dfd6d022647b3fae592acea48f6f3ab6760c730fa8d3198c8700c25f773597 + checksum: befbc749a39fa3777fd7c7036056010322ee5270bef661bc72c0e69d52ca64a86cfea747a481d97b5cd7f43961863d12a658eb9eed853d140f9570d4c1bfd4da languageName: node linkType: hard @@ -3436,12 +3447,12 @@ __metadata: languageName: node linkType: hard -"@zeitgeistpm/sdk@npm:2.46.0": - version: 2.46.0 - resolution: "@zeitgeistpm/sdk@npm:2.46.0" +"@zeitgeistpm/sdk@npm:2.47.0": + version: 2.47.0 + resolution: "@zeitgeistpm/sdk@npm:2.47.0" dependencies: - "@zeitgeistpm/augment-api": ^2.22.0 - "@zeitgeistpm/indexer": ^3.17.0 + "@zeitgeistpm/augment-api": ^2.23.0 + "@zeitgeistpm/indexer": ^3.18.0 "@zeitgeistpm/rpc": ^2.14.0 "@zeitgeistpm/utility": ^2.24.0 "@zeitgeistpm/web3.storage": ^2.15.0 @@ -3458,7 +3469,7 @@ __metadata: "@polkadot/api": "*" "@polkadot/types": "*" "@polkadot/util": "*" - checksum: ea611d8b0b71cefb0e7cb893e0e93b18924639160ea1fff43a2a67b23e236bf1d6be55b42634ea0d98e549c6ec6dc38c2ac124d95e4c39949901b9294c0b06ef + checksum: ffcb1a82229a5f5f225262513f554d89ca642555a2311dacb38d17b44164a8c273e60080f0ee238ff43de869626d7c129981d093ab015ab23d9ca2bddd7ca3e4 languageName: node linkType: hard @@ -3514,11 +3525,11 @@ __metadata: "@vercel/og": ^0.5.19 "@yornaath/batshit": ^0.7.1 "@yornaath/batshit-devtools-react": ^0.5.4 - "@zeitgeistpm/augment-api": 2.22.0 + "@zeitgeistpm/augment-api": 2.23.0 "@zeitgeistpm/avatara-nft-sdk": ^1.3.1 "@zeitgeistpm/avatara-react": ^1.3.2 "@zeitgeistpm/avatara-util": ^1.2.0 - "@zeitgeistpm/sdk": 2.46.0 + "@zeitgeistpm/sdk": 2.47.0 "@zeitgeistpm/utility": ^2.20.0 autoprefixer: 10.2.5 axios: ^0.21.4 From 768a765b0e9604bfd69ea5056dcf4f6b2e20eeb9 Mon Sep 17 00:00:00 2001 From: Tom Robiquet Date: Mon, 6 Nov 2023 13:27:43 +0100 Subject: [PATCH 18/21] handle success --- components/trade-form/Amm2TradeForm.tsx | 138 ++++++++++++++++++------ components/trade-form/BuyForm.tsx | 10 +- components/trade-form/SellForm.tsx | 10 +- 3 files changed, 125 insertions(+), 33 deletions(-) diff --git a/components/trade-form/Amm2TradeForm.tsx b/components/trade-form/Amm2TradeForm.tsx index 11fabe45b..4cbdf8087 100644 --- a/components/trade-form/Amm2TradeForm.tsx +++ b/components/trade-form/Amm2TradeForm.tsx @@ -1,9 +1,15 @@ import { Tab } from "@headlessui/react"; -import { MarketOutcomeAssetId } from "@zeitgeistpm/sdk"; +import { MarketOutcomeAssetId, getIndexOf, ZTG } from "@zeitgeistpm/sdk"; import { useState } from "react"; import BuyForm from "./BuyForm"; import SellForm from "./SellForm"; import TradeTab, { TradeTabType } from "./TradeTab"; +import { ISubmittableResult } from "@polkadot/types/types"; +import TradeResult from "components/markets/TradeResult"; +import Decimal from "decimal.js"; +import { useMarket } from "lib/hooks/queries/useMarket"; +import { useAssetMetadata } from "lib/hooks/queries/useAssetMetadata"; +import { parseAssetIdString } from "lib/util/parse-asset-id"; const Amm2TradeForm = ({ marketId, @@ -17,40 +23,110 @@ const Amm2TradeForm = ({ const [tabType, setTabType] = useState( initialTab ?? TradeTabType.Buy, ); + const [showSuccessBox, setShowSuccessBox] = useState(false); + const [amountReceived, setAmountReceived] = useState(); + const [amountIn, setAmountIn] = useState(); + const [outcomeAsset, setOutcomeAsset] = useState(); + const { data: market } = useMarket({ marketId }); + const baseAsset = parseAssetIdString(market?.baseAsset); + const { data: assetMetadata } = useAssetMetadata(baseAsset); + const baseSymbol = assetMetadata?.symbol; + + const handleSuccess = (data: ISubmittableResult) => { + const { events } = data; + for (const eventData of events) { + const { event } = eventData; + const { data } = event; + if ( + event.section.toString() === "neoSwaps" && + (event.method.toString() === "SellExecuted" || + event.method.toString() === "BuyExecuted") + ) { + const amountOut: number = data["amountOut"].toNumber(); + setAmountReceived(new Decimal(amountOut ?? 0)); + setShowSuccessBox(true); + } + } + }; return ( - { - setTabType(index); - }} - selectedIndex={tabType} - > - - - Buy - - + {showSuccessBox === true ? ( + { + setShowSuccessBox(false); + }} + /> + ) : ( + { + setTabType(index); + }} + selectedIndex={tabType} > - Sell - - + + + Buy + + + Sell + + - - - - - - - - - + + + { + handleSuccess(data); + setOutcomeAsset(asset); + setAmountIn(amount); + }} + /> + + + { + handleSuccess(data); + setOutcomeAsset(asset); + setAmountIn(amount); + }} + /> + + + + )} + ); }; diff --git a/components/trade-form/BuyForm.tsx b/components/trade-form/BuyForm.tsx index 6d7bf35d1..cb802f071 100644 --- a/components/trade-form/BuyForm.tsx +++ b/components/trade-form/BuyForm.tsx @@ -30,15 +30,22 @@ import { formatNumberCompact } from "lib/util/format-compact"; import { parseAssetIdString } from "lib/util/parse-asset-id"; import { useState, useEffect, useMemo } from "react"; import { useForm } from "react-hook-form"; +import { ISubmittableResult } from "@polkadot/types/types"; const slippageMultiplier = (100 - DEFAULT_SLIPPAGE_PERCENTAGE) / 100; const BuyForm = ({ marketId, initialAsset, + onSuccess, }: { marketId: number; initialAsset?: MarketOutcomeAssetId; + onSuccess: ( + data: ISubmittableResult, + outcomeAsset: MarketOutcomeAssetId, + amountIn: Decimal, + ) => void; }) => { const { data: constants } = useChainConstants(); const { @@ -157,10 +164,11 @@ const BuyForm = ({ ); }, { - onSuccess: () => { + onSuccess: (data) => { notificationStore.pushNotification(`Successfully traded`, { type: "Success", }); + onSuccess(data, selectedAsset!, amountIn); }, }, ); diff --git a/components/trade-form/SellForm.tsx b/components/trade-form/SellForm.tsx index 87aaf0c40..b156667a9 100644 --- a/components/trade-form/SellForm.tsx +++ b/components/trade-form/SellForm.tsx @@ -29,15 +29,22 @@ import { formatNumberCompact } from "lib/util/format-compact"; import { parseAssetIdString } from "lib/util/parse-asset-id"; import { useState, useEffect, useMemo } from "react"; import { useForm } from "react-hook-form"; +import { ISubmittableResult } from "@polkadot/types/types"; const slippageMultiplier = (100 - DEFAULT_SLIPPAGE_PERCENTAGE) / 100; const SellForm = ({ marketId, initialAsset, + onSuccess, }: { marketId: number; initialAsset?: MarketOutcomeAssetId; + onSuccess: ( + data: ISubmittableResult, + outcomeAsset: MarketOutcomeAssetId, + amountIn: Decimal, + ) => void; }) => { const { data: constants } = useChainConstants(); const { @@ -146,10 +153,11 @@ const SellForm = ({ ); }, { - onSuccess: () => { + onSuccess: (data) => { notificationStore.pushNotification(`Successfully traded`, { type: "Success", }); + onSuccess(data, selectedAsset!, amountIn); }, }, ); From b13fccc36096503d347cc8cddcd8ecbfe8fb5dac Mon Sep 17 00:00:00 2001 From: Tom Robiquet Date: Mon, 6 Nov 2023 14:31:47 +0100 Subject: [PATCH 19/21] market page integration --- .../liquidity/MarketLiquiditySection.tsx | 17 +++++++------ components/liquidity/PoolTable.tsx | 24 +++++++++---------- .../MarketContextActionOutcomeSelector.tsx | 4 ++-- components/markets/TradeResult.tsx | 12 +++++----- lib/hooks/queries/useMarketSpotPrices.ts | 12 ++++------ pages/markets/[marketid].tsx | 14 +++++++---- 6 files changed, 41 insertions(+), 42 deletions(-) diff --git a/components/liquidity/MarketLiquiditySection.tsx b/components/liquidity/MarketLiquiditySection.tsx index 0c6539b1b..c228b6f7b 100644 --- a/components/liquidity/MarketLiquiditySection.tsx +++ b/components/liquidity/MarketLiquiditySection.tsx @@ -19,6 +19,7 @@ import { formatScalarOutcome } from "lib/util/format-scalar-outcome"; import { perbillToNumber } from "lib/util/perbill-to-number"; import { FC, PropsWithChildren, useState } from "react"; import { AiOutlineInfoCircle } from "react-icons/ai"; +import { ScoringRule } from "@zeitgeistpm/indexer"; export const MarketLiquiditySection = ({ market, @@ -27,9 +28,13 @@ export const MarketLiquiditySection = ({ market: FullMarketFragment; poll?: boolean; }) => { + const marketHasPool = + (market?.scoringRule === ScoringRule.Cpmm && market.pool != null) || + (market?.scoringRule === ScoringRule.Lmsr && market.neoPool != null); + return ( <> - {poll && !market?.pool?.poolId && ( + {poll && !marketHasPool && ( <>
@@ -39,13 +44,13 @@ export const MarketLiquiditySection = ({
)} - {market?.pool?.poolId && ( + {marketHasPool && ( <>
@@ -151,12 +156,6 @@ const LiquidityHeader = ({ market }: { market: FullMarketFragment }) => {
- - {predictionDisplay} -
{!wallet.connected ? ( diff --git a/components/liquidity/PoolTable.tsx b/components/liquidity/PoolTable.tsx index 0fc2961f7..b85d75ac2 100644 --- a/components/liquidity/PoolTable.tsx +++ b/components/liquidity/PoolTable.tsx @@ -1,6 +1,7 @@ import { IOBaseAssetId, parseAssetId, ZTG } from "@zeitgeistpm/sdk"; import Table, { TableColumn, TableData } from "components/ui/Table"; import Decimal from "decimal.js"; +import { useAmm2Pool } from "lib/hooks/queries/amm2/useAmm2Pool"; import { useAccountPoolAssetBalances } from "lib/hooks/queries/useAccountPoolAssetBalances"; import { useAssetMetadata } from "lib/hooks/queries/useAssetMetadata"; import { useAssetUsdPrice } from "lib/hooks/queries/useAssetUsdPrice"; @@ -10,6 +11,7 @@ import { usePool } from "lib/hooks/queries/usePool"; import { usePoolBaseBalance } from "lib/hooks/queries/usePoolBaseBalance"; import { calcMarketColors } from "lib/util/color-calc"; import { parseAssetIdString } from "lib/util/parse-asset-id"; +import { ScoringRule } from "@zeitgeistpm/indexer"; const poolTableColums: TableColumn[] = [ { @@ -17,11 +19,6 @@ const poolTableColums: TableColumn[] = [ accessor: "token", type: "token", }, - { - header: "Weights", - accessor: "weights", - type: "percentage", - }, { header: "Pool Balance", accessor: "poolBalance", @@ -33,10 +30,10 @@ const PoolTable = ({ poolId, marketId, }: { - poolId: number; + poolId?: number; marketId: number; }) => { - const { data: pool } = usePool({ poolId }); + const { data: pool } = usePool(poolId != null ? { poolId } : undefined); const { data: market } = useMarket({ marketId }); const baseAssetId = pool?.baseAsset ? parseAssetId(pool.baseAsset).unrightOr(undefined) @@ -50,20 +47,25 @@ const PoolTable = ({ const { data: basePoolBalance } = usePoolBaseBalance(poolId); const { data: baseAssetUsdPrice } = useAssetUsdPrice(baseAssetId); const { data: spotPrices } = useMarketSpotPrices(marketId); + const { data: amm2Pool } = useAmm2Pool(marketId); const colors = market?.categories ? calcMarketColors(marketId, market.categories.length) : []; + const assetIds = + market?.scoringRule === ScoringRule.Cpmm + ? pool?.weights?.map((weight) => parseAssetIdString(weight?.assetId)) + : amm2Pool?.assetIds; + const tableData: TableData[] = - pool?.weights?.map((asset, index) => { + assetIds?.map((assetId, index) => { let amount: Decimal | undefined; let usdValue: Decimal | undefined; let category: | { color?: string | null; name?: string | null } | undefined | null; - const assetId = parseAssetIdString(asset?.assetId); if (IOBaseAssetId.is(assetId)) { amount = basePoolBalance ?? undefined; @@ -83,10 +85,6 @@ const PoolTable = ({ color: colors[index] || "#ffffff", label: category?.name ?? "", }, - weights: new Decimal(asset!.weight) - .div(pool.totalWeight) - .mul(100) - .toNumber(), poolBalance: { value: amount?.div(ZTG).toDecimalPlaces(2).toNumber() ?? 0, usdValue: usdValue?.div(ZTG).toDecimalPlaces(2).toNumber(), diff --git a/components/markets/MarketContextActionOutcomeSelector.tsx b/components/markets/MarketContextActionOutcomeSelector.tsx index 1da2ed513..41c930ea1 100644 --- a/components/markets/MarketContextActionOutcomeSelector.tsx +++ b/components/markets/MarketContextActionOutcomeSelector.tsx @@ -88,14 +88,14 @@ const MarketContextActionOutcomeSelector = ({ }} > setOpen(!open)}> -
+
{(text) => <>{text}} - +
{ interface TradeResultProps { type: "buy" | "sell"; - amount: Decimal; + amount?: Decimal; tokenName?: string; - baseTokenAmount: Decimal; + baseTokenAmount?: Decimal; baseToken?: string; marketId: number; marketQuestion?: string; @@ -49,12 +49,12 @@ const TradeResult = ({ onContinueClick, }: TradeResultProps) => { const marketUrl = `https://app.zeitgeist.pm/markets/${marketId}`; - const potentialGain = amount.div(baseTokenAmount); + const potentialGain = amount?.div(baseTokenAmount ?? 0); const twitterBaseUrl = "https://twitter.com/intent/tweet?text="; const tweetUrl = type === "buy" ? `${twitterBaseUrl}I'm using %40ZeitgeistPM to bet on "${marketQuestion}" %0A%0AIf I'm right, I'll gain ${potentialGain - .minus(1) + ?.minus(1) .times(100) .toFixed( 0, @@ -64,12 +64,12 @@ const TradeResult = ({ return (
You've just {type === "buy" ? "bought" : "sold"}
-
{amount.toFixed(2)}
+
{amount?.toFixed(2)}
{tokenName} Predictions For
- {baseTokenAmount.toFixed(2)} {baseToken} + {baseTokenAmount?.toFixed(2)} {baseToken}
= ({ return ; } + const marketHasPool = + (market?.scoringRule === ScoringRule.Cpmm && + poolId != null && + poolIdLoading === false) || + (market?.scoringRule === ScoringRule.Lmsr && market.neoPool != null); + return (
@@ -285,7 +291,7 @@ const Market: NextPage = ({ ) : ( <> )} - {poolId == null && poolIdLoading === false && ( + {marketHasPool && market.neoPool == null && (
@@ -336,7 +342,7 @@ const Market: NextPage = ({ )} - {market && !market.pool && ( + {market && !marketHasPool && ( = ({ - {market && (market?.pool || poolDeployed) && ( + {market && (marketHasPool || poolDeployed) && (
= ({ leave="transition ease-in duration-75" leaveFrom="transform opacity-100 " leaveTo="transform opacity-0 " - show={showLiquidity && Boolean(market?.pool || poolDeployed)} + show={showLiquidity && Boolean(marketHasPool || poolDeployed)} > From fdec5bdbe45618467edd186d7231615ae358f255 Mon Sep 17 00:00:00 2001 From: Tom Robiquet Date: Tue, 7 Nov 2023 13:45:58 +0100 Subject: [PATCH 20/21] misc --- .env.development | 2 +- lib/hooks/queries/amm2/useAmm2Pool.ts | 6 ++++- lib/util/amm2.spec.ts | 39 +++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/.env.development b/.env.development index 9178fe5c0..c8feca14a 100644 --- a/.env.development +++ b/.env.development @@ -23,7 +23,7 @@ NEXT_PUBLIC_AVATAR_COLLECTION_ID="2e55d4bf2e85715b63-ZEITASTAGE" NEXT_PUBLIC_SINGULAR_URL="https://singular-rmrk2-dev.vercel.app" NEXT_PUBLIC_RMRK_INDEXER_API="https://gql2.rmrk.dev/v1/graphql" NEXT_PUBLIC_IPFS_NODE="http://ipfs.zeitgeist.pm:5001" -NEXT_PUBLIC_RMRK_CHAIN_RPC_NODE="wss://staging.node.rmrk.app" +NEXT_PUBLIC_RMRK_CHAIN_RPC_NODE="wss://kusama-node-staging.rmrk.link/" NEXT_PUBLIC_AVATAR_API_HOST="https://avatar-bsr.zeitgeist.pm/" #enable in dev/staging to inspect react-query cache and query handling. diff --git a/lib/hooks/queries/amm2/useAmm2Pool.ts b/lib/hooks/queries/amm2/useAmm2Pool.ts index 285c858e7..746459cda 100644 --- a/lib/hooks/queries/amm2/useAmm2Pool.ts +++ b/lib/hooks/queries/amm2/useAmm2Pool.ts @@ -3,6 +3,7 @@ import { AssetId, IOCategoricalAssetId, IOMarketOutcomeAssetId, + MarketOutcomeAssetId, isRpcSdk, } from "@zeitgeistpm/sdk"; import Decimal from "decimal.js"; @@ -19,6 +20,7 @@ export type Amm2Pool = { liquidity: Decimal; swapFee: Decimal; reserves: ReserveMap; + assetIds: MarketOutcomeAssetId[]; }; export const useAmm2Pool = (marketId?: number) => { @@ -34,6 +36,7 @@ export const useAmm2Pool = (marketId?: number) => { if (unwrappedRes) { const reserves: ReserveMap = new Map(); + const assetIds: MarketOutcomeAssetId[] = []; unwrappedRes.reserves.forEach((reserve, asset) => { const assetId = parseAssetIdString(asset.toString()); @@ -44,6 +47,7 @@ export const useAmm2Pool = (marketId?: number) => { : assetId.ScalarOutcome[1], new Decimal(reserve.toString()), ); + assetIds.push(assetId); } }); @@ -53,6 +57,7 @@ export const useAmm2Pool = (marketId?: number) => { liquidity: new Decimal(unwrappedRes.liquidityParameter.toString()), swapFee: new Decimal(unwrappedRes.swapFee.toString()), reserves, + assetIds, }; return pool; @@ -66,7 +71,6 @@ export const useAmm2Pool = (marketId?: number) => { return query; }; -// todo: change to hook that takes assetId export const lookupAssetReserve = ( map: ReserveMap, asset?: string | AssetId, diff --git a/lib/util/amm2.spec.ts b/lib/util/amm2.spec.ts index c6e7d1094..6f22322ff 100644 --- a/lib/util/amm2.spec.ts +++ b/lib/util/amm2.spec.ts @@ -1,6 +1,7 @@ import Decimal from "decimal.js"; import { calculateSpotPrice, + approximateMaxAmountInForBuy, calculateSwapAmountOutForBuy, calculateSwapAmountOutForSell, } from "./amm2"; @@ -63,4 +64,42 @@ describe("amm2", () => { expect(amountOut.toFixed(5)).toEqual("0.25000"); }); }); + + describe("approximateMaxAmountInForBuy", () => { + //seems like correct number would be 41 + test("should work", () => { + const amountOut = approximateMaxAmountInForBuy( + new Decimal(59_9567744280), + new Decimal(144_2695040889), + ); + + expect(amountOut.toFixed(0)).toEqual("4867389110738"); + }); + }); + + test("all functions", () => { + const liquidity = new Decimal(144.00003701590745); + const reserves = [ + new Decimal(59.00001516623987), + new Decimal(156.98193508578956), + ]; + const spotPrices = [0.6638346230341853, 0.33616537696581467]; + const amountIn = new Decimal(486); + + const amountOut = calculateSwapAmountOutForBuy( + reserves[0], + amountIn, + liquidity, + new Decimal(0), + new Decimal(0), + ); + const poolAmountOut = amountOut.minus(amountIn); + const newReserve = reserves[0].minus(poolAmountOut); + const newSpotPrice = calculateSpotPrice(newReserve, liquidity); + + expect(amountOut.toFixed(5)).toEqual("543.33399"); + expect(poolAmountOut.toFixed(5)).toEqual("57.33399"); + expect(newReserve.toFixed(5)).toEqual("1.66603"); + expect(newSpotPrice.toFixed(5)).toEqual("0.98850"); + }); }); From 566297c32a5218efe741d20b8b539e383267c301 Mon Sep 17 00:00:00 2001 From: Tom Robiquet Date: Tue, 7 Nov 2023 15:12:53 +0100 Subject: [PATCH 21/21] mobile trading support --- .../AssetTradingButtons.tsx | 2 +- components/trade-form/Amm2TradeForm.tsx | 23 ++++++++++++------- pages/markets/[marketid].tsx | 17 +++++++++++++- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/components/assets/AssetActionButtons/AssetTradingButtons.tsx b/components/assets/AssetActionButtons/AssetTradingButtons.tsx index 4e201e610..c95c742fc 100644 --- a/components/assets/AssetActionButtons/AssetTradingButtons.tsx +++ b/components/assets/AssetActionButtons/AssetTradingButtons.tsx @@ -57,7 +57,7 @@ const AssetTradingButtons = ({ { - const [tabType, setTabType] = useState( - initialTab ?? TradeTabType.Buy, - ); + const [tabType, setTabType] = useState(); const [showSuccessBox, setShowSuccessBox] = useState(false); const [amountReceived, setAmountReceived] = useState(); const [amountIn, setAmountIn] = useState(); @@ -32,6 +32,10 @@ const Amm2TradeForm = ({ const { data: assetMetadata } = useAssetMetadata(baseAsset); const baseSymbol = assetMetadata?.symbol; + useEffect(() => { + setTabType(selectedTab ?? TradeTabType.Buy); + }, [selectedTab]); + const handleSuccess = (data: ISubmittableResult) => { const { events } = data; for (const eventData of events) { @@ -83,7 +87,11 @@ const Amm2TradeForm = ({ }} selectedIndex={tabType} > - + - import("../../components/trade-form"), { ssr: false, @@ -462,7 +463,21 @@ const MobileContextButtons = ({ market }: { market: FullMarketFragment }) => { > {market?.status === MarketStatus.Active ? ( <> - + {market?.scoringRule === ScoringRule.Cpmm ? ( +
+ +
+ ) : ( + + )} ) : market?.status === MarketStatus.Closed && canReport ? ( <>