From fe2ef138eb9a379f78b2f60a3f587cba30ed15a5 Mon Sep 17 00:00:00 2001 From: Alduin Date: Sat, 12 Oct 2024 16:29:04 -0300 Subject: [PATCH 1/3] Implement liquidity purchase --- frontend/src/api/mutations/BuyLiquidity.ts | 133 ++++++++++++++++++ .../components/MarketBetting/Buy/index.tsx | 3 +- .../MarketBetting/BuyLiquidity/form.ts | 64 +++++++++ .../MarketBetting/BuyLiquidity/index.tsx | 104 ++++++++++++++ .../components/MarketBetting/index.tsx | 23 ++- frontend/src/utils/analytics.ts | 28 +++- frontend/src/utils/errors.ts | 12 ++ frontend/src/utils/shares.ts | 4 +- 8 files changed, 365 insertions(+), 6 deletions(-) create mode 100644 frontend/src/api/mutations/BuyLiquidity.ts create mode 100644 frontend/src/features/MarketDetail/components/MarketBetting/BuyLiquidity/form.ts create mode 100644 frontend/src/features/MarketDetail/components/MarketBetting/BuyLiquidity/index.tsx diff --git a/frontend/src/api/mutations/BuyLiquidity.ts b/frontend/src/api/mutations/BuyLiquidity.ts new file mode 100644 index 0000000..77f556d --- /dev/null +++ b/frontend/src/api/mutations/BuyLiquidity.ts @@ -0,0 +1,133 @@ +import { useActiveWalletType, useCosmWasmSigningClient } from "graz" +import { useMutation, useQueryClient } from "@tanstack/react-query" +import type { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate" + +import { useCurrentAccount } from "@config/chain" +import { useNotifications } from "@config/notifications" +import { querierAwaitCacheAnd, querierBroadcastAndWait } from "@api/querier" +import { MARKET_KEYS, type MarketId, type OutcomeId } from "@api/queries/Market" +import { POSITIONS_KEYS } from "@api/queries/Positions" +import { BALANCES_KEYS } from "@api/queries/Balances" +import { trackBuyLiquidity } from "@utils/analytics" +import type { Coins } from "@utils/coins" +import { AppError, errorsMiddleware } from "@utils/errors" + +export const LIQUIDITY_PORTION = "1" + +interface BuyLiquidityRequest { + deposit: { + id: number + outcome: number + liquidity: string + } +} + +interface BuyLiquidityArgs { + outcomeId: OutcomeId + coinsAmount: Coins +} + +const putBuyLiquidity = ( + address: string, + signer: SigningCosmWasmClient, + marketId: MarketId, + args: BuyLiquidityArgs, +) => { + const depositMsg: BuyLiquidityRequest = { + deposit: { + id: Number(marketId), + outcome: Number(args.outcomeId), + liquidity: LIQUIDITY_PORTION, + }, + } + + return querierBroadcastAndWait(address, signer, [ + { + payload: depositMsg, + funds: [ + { + denom: args.coinsAmount.denom, + amount: args.coinsAmount.units.toFixed(0), + }, + ], + }, + ]) +} + +const BUY_LIQUIDITY_KEYS = { + all: ["buy_liquidity"] as const, + address: (address: string) => [...BUY_LIQUIDITY_KEYS.all, address] as const, + market: (address: string, marketId: MarketId) => + [...BUY_LIQUIDITY_KEYS.address(address), marketId] as const, +} + +const useBuyLiquidity = (marketId: MarketId) => { + const account = useCurrentAccount() + const walletName = useActiveWalletType().walletType + const signer = useCosmWasmSigningClient() + const queryClient = useQueryClient() + const notifications = useNotifications() + + const mutation = useMutation({ + mutationKey: BUY_LIQUIDITY_KEYS.market(account.bech32Address, marketId), + mutationFn: (args: BuyLiquidityArgs) => { + if (signer.data) { + return errorsMiddleware( + "buy", + putBuyLiquidity(account.bech32Address, signer.data, marketId, args), + ) + } else { + return Promise.reject() + } + }, + onSuccess: (_, args) => { + notifications.notifySuccess( + `Successfully bet ${args.coinsAmount.toFormat(true)}.`, + ) + + trackBuyLiquidity({ + marketId: marketId, + outcomeId: args.outcomeId, + coins: args.coinsAmount, + walletName: walletName, + }) + + return querierAwaitCacheAnd( + () => + queryClient.invalidateQueries({ + queryKey: MARKET_KEYS.market(marketId), + }), + () => + queryClient.invalidateQueries({ + queryKey: POSITIONS_KEYS.market(account.bech32Address, marketId), + }), + () => + queryClient.invalidateQueries({ + queryKey: BALANCES_KEYS.address(account.bech32Address), + }), + ) + }, + onError: (err, args) => { + notifications.notifyError( + AppError.withCause( + `Failed to buy ${args.coinsAmount.toFormat(true)} of liquidity.`, + err, + ), + ) + + trackBuyLiquidity( + { + marketId: marketId, + outcomeId: args.outcomeId, + coins: args.coinsAmount, + walletName: walletName, + }, + err, + ) + }, + }) + + return mutation +} + +export { useBuyLiquidity } diff --git a/frontend/src/features/MarketDetail/components/MarketBetting/Buy/index.tsx b/frontend/src/features/MarketDetail/components/MarketBetting/Buy/index.tsx index 333bd6c..fb91730 100644 --- a/frontend/src/features/MarketDetail/components/MarketBetting/Buy/index.tsx +++ b/frontend/src/features/MarketDetail/components/MarketBetting/Buy/index.tsx @@ -7,6 +7,7 @@ import { useCurrentAccount } from "@config/chain" import type { Market, OutcomeId } from "@api/queries/Market" import { balancesQuery } from "@api/queries/Balances" import { coinPricesQuery } from "@api/queries/Prices" +import { LIQUDITY_PORTION } from "@api/mutations/PlaceBet" import { Coins, USD } from "@utils/coins" import { useLatestFormValues } from "@utils/forms" import { getDifferencePercentage } from "@utils/number" @@ -78,7 +79,7 @@ const ShowSharesPurchased = (props: ShowSharesPurchasedProps) => { const { market, betOutcome, coinsAmount } = props const purchaseResult = betOutcome !== undefined && coinsAmount !== undefined - ? getPurchaseResult(market, betOutcome, coinsAmount) + ? getPurchaseResult(market, betOutcome, coinsAmount, LIQUDITY_PORTION) : undefined const selectedOutcome = market.possibleOutcomes.find( diff --git a/frontend/src/features/MarketDetail/components/MarketBetting/BuyLiquidity/form.ts b/frontend/src/features/MarketDetail/components/MarketBetting/BuyLiquidity/form.ts new file mode 100644 index 0000000..366a12c --- /dev/null +++ b/frontend/src/features/MarketDetail/components/MarketBetting/BuyLiquidity/form.ts @@ -0,0 +1,64 @@ +import { useForm } from "react-hook-form" +import { useQuery } from "@tanstack/react-query" + +import type { Market } from "@api/queries/Market" +import { coinPricesQuery } from "@api/queries/Prices" +import { useBuyLiquidity } from "@api/mutations/BuyLiquidity" +import { Coins, USD } from "@utils/coins" + +interface BuyLiquidityFormValues { + liquidityAmount: { + value: string + toggled: boolean + } + liquidityOutcome: string | null +} + +const useBuyLiquidityForm = (market: Market) => { + const form = useForm({ + defaultValues: { + liquidityAmount: { + value: "", + toggled: false, + }, + liquidityOutcome: null, + }, + }) + + const denom = market.denom + const buyLiquidity = useBuyLiquidity(market.id) + const prices = useQuery(coinPricesQuery) + + const onSubmit = (formValues: BuyLiquidityFormValues) => { + const isToggled = formValues.liquidityAmount.toggled + const liquidityAmount = formValues.liquidityAmount.value + const liquidityOutcome = formValues.liquidityOutcome + const price = prices.data?.get(denom) + + if (liquidityAmount && liquidityOutcome && price) { + const coinsAmount = isToggled + ? new USD(liquidityAmount).toCoins(denom, price) + : Coins.fromValue(denom, liquidityAmount) + + return buyLiquidity + .mutateAsync({ + outcomeId: liquidityOutcome, + coinsAmount: coinsAmount, + }) + .then(() => { + form.reset() + }) + } else { + return Promise.reject() + } + } + + const canSubmit = + form.formState.isValid && + !form.formState.isSubmitting && + !!prices.data?.has(market.denom) + + return { form, canSubmit, onSubmit } +} + +export { useBuyLiquidityForm } diff --git a/frontend/src/features/MarketDetail/components/MarketBetting/BuyLiquidity/index.tsx b/frontend/src/features/MarketDetail/components/MarketBetting/BuyLiquidity/index.tsx new file mode 100644 index 0000000..0d11baa --- /dev/null +++ b/frontend/src/features/MarketDetail/components/MarketBetting/BuyLiquidity/index.tsx @@ -0,0 +1,104 @@ +import { Button, Stack, Typography } from "@mui/joy" +import { FormProvider } from "react-hook-form" +import { useQuery } from "@tanstack/react-query" + +import { useCurrentAccount } from "@config/chain" +import type { Market, OutcomeId } from "@api/queries/Market" +import { balancesQuery } from "@api/queries/Balances" +import { coinPricesQuery } from "@api/queries/Prices" +import { LIQUIDITY_PORTION } from "@api/mutations/BuyLiquidity" +import { Coins, USD } from "@utils/coins" +import { useLatestFormValues } from "@utils/forms" +import { getPurchaseResult } from "@utils/shares" +import { useBuyLiquidityForm } from "./form" +import { OutcomeField } from "../OutcomeField" +import { CoinsAmountField } from "../CoinsAmountField" + +const MarketBuyLiquidityForm = (props: { market: Market }) => { + const { market } = props + const account = useCurrentAccount() + const balances = useQuery(balancesQuery(account.bech32Address)) + const price = useQuery(coinPricesQuery).data?.get(market.denom) + + const { form, canSubmit, onSubmit } = useBuyLiquidityForm(market) + const formValues = useLatestFormValues(form) + + const coinsAmount = + price && formValues.liquidityAmount.value + ? formValues.liquidityAmount.toggled + ? new USD(formValues.liquidityAmount.value).toCoins(market.denom, price) + : Coins.fromValue(market.denom, formValues.liquidityAmount.value) + : undefined + + return ( + + + + + + + + + + + + ) +} + +interface ShowLiquidityPurchasedProps { + market: Market + betOutcome: OutcomeId | undefined + coinsAmount: Coins | undefined +} + +const ShowLiquidityPurchased = (props: ShowLiquidityPurchasedProps) => { + const { market, betOutcome, coinsAmount } = props + const purchaseResult = + betOutcome !== undefined && coinsAmount !== undefined + ? getPurchaseResult(market, betOutcome, coinsAmount, LIQUIDITY_PORTION) + : undefined + + return ( + <> + + Estimated liquidity + + {purchaseResult?.liquidity.toFormat(true) ?? "-"} + + + + + Fees + + {purchaseResult?.fees.toFormat(true) ?? "-"} + + + + ) +} + +export { MarketBuyLiquidityForm } diff --git a/frontend/src/features/MarketDetail/components/MarketBetting/index.tsx b/frontend/src/features/MarketDetail/components/MarketBetting/index.tsx index 8398a51..c29b99b 100644 --- a/frontend/src/features/MarketDetail/components/MarketBetting/index.tsx +++ b/frontend/src/features/MarketDetail/components/MarketBetting/index.tsx @@ -15,6 +15,7 @@ import { import { MarketClaimForm } from "./Claim" import { MarketBuyForm } from "./Buy" import { MarketSellForm } from "./Sell" +import { MarketBuyLiquidityForm } from "./BuyLiquidity" const MarketBetting = (props: StyleProps) => { return ( @@ -46,7 +47,7 @@ const MarketBettingContent = (props: { market: Market }) => { const MarketBettingForm = (props: { market: Market; status: MarketStatus }) => { const { market, status } = props - const [action, setAction] = useState<"buy" | "sell">("buy") + const [action, setAction] = useState<"buy" | "sell" | "liquidity">("buy") return ( <> @@ -68,6 +69,7 @@ const MarketBettingForm = (props: { market: Market; status: MarketStatus }) => { > Buy + + + {match(action) .with("buy", () => ) .with("sell", () => ) + .with("liquidity", () => ) .exhaustive()} ) diff --git a/frontend/src/utils/analytics.ts b/frontend/src/utils/analytics.ts index 501c985..764f549 100644 --- a/frontend/src/utils/analytics.ts +++ b/frontend/src/utils/analytics.ts @@ -2,7 +2,7 @@ import type { MarketId, OutcomeId } from "@api/queries/Market" import type { Coins } from "./coins" import type { Shares } from "./shares" -type EventName = "place_bet" | "cancel_bet" | "claim_earnings" +type EventName = "place_bet" | "cancel_bet" | "buy_liquidity" | "claim_earnings" const trackSuccess = (eventName: EventName, params: Gtag.CustomParams) => { gtag("event", eventName, { @@ -76,6 +76,30 @@ const trackCancelBet = (params: TrackCancelBetParams, failure?: Error) => { ) } +interface TrackBuyLiquidityParams { + marketId: MarketId + outcomeId: OutcomeId + coins: Coins + walletName: string +} + +const trackBuyLiquidity = ( + params: TrackBuyLiquidityParams, + failure?: Error, +) => { + trackEvent( + "buy_liquidity", + { + market_id: params.marketId, + outcome_id: params.outcomeId, + tokens_amount: params.coins.units.toFixed(0), + denom: params.coins.denom, + wallet: params.walletName, + }, + failure, + ) +} + interface TrackClaimEarningsParams { marketId: MarketId walletName: string @@ -95,4 +119,4 @@ const trackClaimEarnings = ( ) } -export { trackPlaceBet, trackCancelBet, trackClaimEarnings } +export { trackPlaceBet, trackCancelBet, trackBuyLiquidity, trackClaimEarnings } diff --git a/frontend/src/utils/errors.ts b/frontend/src/utils/errors.ts index 4881d15..f71cc7f 100644 --- a/frontend/src/utils/errors.ts +++ b/frontend/src/utils/errors.ts @@ -100,6 +100,18 @@ const errorForAction = (err: any, actionType?: UserAction): AppError | any => { }, () => AppError.withCause("You don't have enough gas funds.", err), ) + .with( + { + message: P.string.regex( + "The transaction will use all your funds, not leaving any funds available for paying gas fees", + ), + }, + () => + AppError.withCause( + "This transaction will use all your funds, not leaving enough for gas. Try using a different token for paying gas fees.", + err, + ), + ) .otherwise(() => err) } diff --git a/frontend/src/utils/shares.ts b/frontend/src/utils/shares.ts index a990e90..b3130c4 100644 --- a/frontend/src/utils/shares.ts +++ b/frontend/src/utils/shares.ts @@ -2,7 +2,6 @@ import BigNumber from "bignumber.js" import type { Market, OutcomeId } from "@api/queries/Market" import type { Positions } from "@api/queries/Positions" -import { LIQUDITY_PORTION } from "@api/mutations/PlaceBet" import { Asset, Coins, @@ -156,6 +155,7 @@ const getPurchaseResult = ( market: Market, outcomeId: OutcomeId, coinsAmount: Coins, + liquidityPortion: BigNumber.Value, ): PurchaseResult => { // To calculate the shares properly, we need to follow the same steps as // are taken by the contract, namely: @@ -185,7 +185,7 @@ const getPurchaseResult = ( // Step 2: take off the liquidity, add to pool, prepare to use the remainder const liquidity = buyAmountWithoutFees - .times(BigNumber(LIQUDITY_PORTION)) + .times(liquidityPortion) .integerValue(BigNumber.ROUND_DOWN) const buyAmount = buyAmountWithoutFees.minus(liquidity) const { pool, returned } = addToPool(poolAfterFees, liquidity) From 714bc274408f77f31fe094542c50dffcd0ace186 Mon Sep 17 00:00:00 2001 From: Alduin Date: Mon, 14 Oct 2024 11:35:50 -0300 Subject: [PATCH 2/3] Use provide message --- .../{BuyLiquidity.ts => ProvideLiquidity.ts} | 63 +++++------ .../MarketBetting/BuyLiquidity/index.tsx | 104 ------------------ .../form.ts | 26 ++--- .../MarketBetting/ProvideLiquidity/index.tsx | 51 +++++++++ .../components/MarketBetting/index.tsx | 4 +- frontend/src/utils/analytics.ts | 19 +++- frontend/src/utils/errors.ts | 2 +- frontend/src/utils/shares.ts | 4 +- 8 files changed, 109 insertions(+), 164 deletions(-) rename frontend/src/api/mutations/{BuyLiquidity.ts => ProvideLiquidity.ts} (64%) delete mode 100644 frontend/src/features/MarketDetail/components/MarketBetting/BuyLiquidity/index.tsx rename frontend/src/features/MarketDetail/components/MarketBetting/{BuyLiquidity => ProvideLiquidity}/form.ts (62%) create mode 100644 frontend/src/features/MarketDetail/components/MarketBetting/ProvideLiquidity/index.tsx diff --git a/frontend/src/api/mutations/BuyLiquidity.ts b/frontend/src/api/mutations/ProvideLiquidity.ts similarity index 64% rename from frontend/src/api/mutations/BuyLiquidity.ts rename to frontend/src/api/mutations/ProvideLiquidity.ts index 77f556d..82e233e 100644 --- a/frontend/src/api/mutations/BuyLiquidity.ts +++ b/frontend/src/api/mutations/ProvideLiquidity.ts @@ -5,45 +5,38 @@ import type { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate" import { useCurrentAccount } from "@config/chain" import { useNotifications } from "@config/notifications" import { querierAwaitCacheAnd, querierBroadcastAndWait } from "@api/querier" -import { MARKET_KEYS, type MarketId, type OutcomeId } from "@api/queries/Market" +import { MARKET_KEYS, type MarketId } from "@api/queries/Market" import { POSITIONS_KEYS } from "@api/queries/Positions" import { BALANCES_KEYS } from "@api/queries/Balances" -import { trackBuyLiquidity } from "@utils/analytics" +import { trackProvideLiquidity } from "@utils/analytics" import type { Coins } from "@utils/coins" import { AppError, errorsMiddleware } from "@utils/errors" -export const LIQUIDITY_PORTION = "1" - -interface BuyLiquidityRequest { - deposit: { +interface ProvideLiquidityRequest { + provide: { id: number - outcome: number - liquidity: string } } -interface BuyLiquidityArgs { - outcomeId: OutcomeId +interface ProvideLiquidityArgs { coinsAmount: Coins } -const putBuyLiquidity = ( +const putProvideLiquidity = ( address: string, signer: SigningCosmWasmClient, marketId: MarketId, - args: BuyLiquidityArgs, + args: ProvideLiquidityArgs, ) => { - const depositMsg: BuyLiquidityRequest = { - deposit: { + const provideMsg: ProvideLiquidityRequest = { + provide: { id: Number(marketId), - outcome: Number(args.outcomeId), - liquidity: LIQUIDITY_PORTION, }, } return querierBroadcastAndWait(address, signer, [ { - payload: depositMsg, + payload: provideMsg, funds: [ { denom: args.coinsAmount.denom, @@ -54,14 +47,15 @@ const putBuyLiquidity = ( ]) } -const BUY_LIQUIDITY_KEYS = { - all: ["buy_liquidity"] as const, - address: (address: string) => [...BUY_LIQUIDITY_KEYS.all, address] as const, +const PROVIDE_LIQUIDITY_KEYS = { + all: ["provide_liquidity"] as const, + address: (address: string) => + [...PROVIDE_LIQUIDITY_KEYS.all, address] as const, market: (address: string, marketId: MarketId) => - [...BUY_LIQUIDITY_KEYS.address(address), marketId] as const, + [...PROVIDE_LIQUIDITY_KEYS.address(address), marketId] as const, } -const useBuyLiquidity = (marketId: MarketId) => { +const useProvideLiquidity = (marketId: MarketId) => { const account = useCurrentAccount() const walletName = useActiveWalletType().walletType const signer = useCosmWasmSigningClient() @@ -69,12 +63,17 @@ const useBuyLiquidity = (marketId: MarketId) => { const notifications = useNotifications() const mutation = useMutation({ - mutationKey: BUY_LIQUIDITY_KEYS.market(account.bech32Address, marketId), - mutationFn: (args: BuyLiquidityArgs) => { + mutationKey: PROVIDE_LIQUIDITY_KEYS.market(account.bech32Address, marketId), + mutationFn: (args: ProvideLiquidityArgs) => { if (signer.data) { return errorsMiddleware( - "buy", - putBuyLiquidity(account.bech32Address, signer.data, marketId, args), + "provide", + putProvideLiquidity( + account.bech32Address, + signer.data, + marketId, + args, + ), ) } else { return Promise.reject() @@ -82,12 +81,11 @@ const useBuyLiquidity = (marketId: MarketId) => { }, onSuccess: (_, args) => { notifications.notifySuccess( - `Successfully bet ${args.coinsAmount.toFormat(true)}.`, + `Successfully provided ${args.coinsAmount.toFormat(true)} of liquidity.`, ) - trackBuyLiquidity({ + trackProvideLiquidity({ marketId: marketId, - outcomeId: args.outcomeId, coins: args.coinsAmount, walletName: walletName, }) @@ -110,15 +108,14 @@ const useBuyLiquidity = (marketId: MarketId) => { onError: (err, args) => { notifications.notifyError( AppError.withCause( - `Failed to buy ${args.coinsAmount.toFormat(true)} of liquidity.`, + `Failed to provide ${args.coinsAmount.toFormat(true)} of liquidity.`, err, ), ) - trackBuyLiquidity( + trackProvideLiquidity( { marketId: marketId, - outcomeId: args.outcomeId, coins: args.coinsAmount, walletName: walletName, }, @@ -130,4 +127,4 @@ const useBuyLiquidity = (marketId: MarketId) => { return mutation } -export { useBuyLiquidity } +export { useProvideLiquidity } diff --git a/frontend/src/features/MarketDetail/components/MarketBetting/BuyLiquidity/index.tsx b/frontend/src/features/MarketDetail/components/MarketBetting/BuyLiquidity/index.tsx deleted file mode 100644 index 0d11baa..0000000 --- a/frontend/src/features/MarketDetail/components/MarketBetting/BuyLiquidity/index.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { Button, Stack, Typography } from "@mui/joy" -import { FormProvider } from "react-hook-form" -import { useQuery } from "@tanstack/react-query" - -import { useCurrentAccount } from "@config/chain" -import type { Market, OutcomeId } from "@api/queries/Market" -import { balancesQuery } from "@api/queries/Balances" -import { coinPricesQuery } from "@api/queries/Prices" -import { LIQUIDITY_PORTION } from "@api/mutations/BuyLiquidity" -import { Coins, USD } from "@utils/coins" -import { useLatestFormValues } from "@utils/forms" -import { getPurchaseResult } from "@utils/shares" -import { useBuyLiquidityForm } from "./form" -import { OutcomeField } from "../OutcomeField" -import { CoinsAmountField } from "../CoinsAmountField" - -const MarketBuyLiquidityForm = (props: { market: Market }) => { - const { market } = props - const account = useCurrentAccount() - const balances = useQuery(balancesQuery(account.bech32Address)) - const price = useQuery(coinPricesQuery).data?.get(market.denom) - - const { form, canSubmit, onSubmit } = useBuyLiquidityForm(market) - const formValues = useLatestFormValues(form) - - const coinsAmount = - price && formValues.liquidityAmount.value - ? formValues.liquidityAmount.toggled - ? new USD(formValues.liquidityAmount.value).toCoins(market.denom, price) - : Coins.fromValue(market.denom, formValues.liquidityAmount.value) - : undefined - - return ( - - - - - - - - - - - - ) -} - -interface ShowLiquidityPurchasedProps { - market: Market - betOutcome: OutcomeId | undefined - coinsAmount: Coins | undefined -} - -const ShowLiquidityPurchased = (props: ShowLiquidityPurchasedProps) => { - const { market, betOutcome, coinsAmount } = props - const purchaseResult = - betOutcome !== undefined && coinsAmount !== undefined - ? getPurchaseResult(market, betOutcome, coinsAmount, LIQUIDITY_PORTION) - : undefined - - return ( - <> - - Estimated liquidity - - {purchaseResult?.liquidity.toFormat(true) ?? "-"} - - - - - Fees - - {purchaseResult?.fees.toFormat(true) ?? "-"} - - - - ) -} - -export { MarketBuyLiquidityForm } diff --git a/frontend/src/features/MarketDetail/components/MarketBetting/BuyLiquidity/form.ts b/frontend/src/features/MarketDetail/components/MarketBetting/ProvideLiquidity/form.ts similarity index 62% rename from frontend/src/features/MarketDetail/components/MarketBetting/BuyLiquidity/form.ts rename to frontend/src/features/MarketDetail/components/MarketBetting/ProvideLiquidity/form.ts index 366a12c..12f104d 100644 --- a/frontend/src/features/MarketDetail/components/MarketBetting/BuyLiquidity/form.ts +++ b/frontend/src/features/MarketDetail/components/MarketBetting/ProvideLiquidity/form.ts @@ -3,48 +3,42 @@ import { useQuery } from "@tanstack/react-query" import type { Market } from "@api/queries/Market" import { coinPricesQuery } from "@api/queries/Prices" -import { useBuyLiquidity } from "@api/mutations/BuyLiquidity" +import { useProvideLiquidity } from "@api/mutations/ProvideLiquidity" import { Coins, USD } from "@utils/coins" -interface BuyLiquidityFormValues { +interface ProvideLiquidityFormValues { liquidityAmount: { value: string toggled: boolean } - liquidityOutcome: string | null } -const useBuyLiquidityForm = (market: Market) => { - const form = useForm({ +const useProvideLiquidityForm = (market: Market) => { + const form = useForm({ defaultValues: { liquidityAmount: { value: "", toggled: false, }, - liquidityOutcome: null, }, }) const denom = market.denom - const buyLiquidity = useBuyLiquidity(market.id) + const provideLiquidity = useProvideLiquidity(market.id) const prices = useQuery(coinPricesQuery) - const onSubmit = (formValues: BuyLiquidityFormValues) => { + const onSubmit = (formValues: ProvideLiquidityFormValues) => { const isToggled = formValues.liquidityAmount.toggled const liquidityAmount = formValues.liquidityAmount.value - const liquidityOutcome = formValues.liquidityOutcome const price = prices.data?.get(denom) - if (liquidityAmount && liquidityOutcome && price) { + if (liquidityAmount && price) { const coinsAmount = isToggled ? new USD(liquidityAmount).toCoins(denom, price) : Coins.fromValue(denom, liquidityAmount) - return buyLiquidity - .mutateAsync({ - outcomeId: liquidityOutcome, - coinsAmount: coinsAmount, - }) + return provideLiquidity + .mutateAsync({ coinsAmount: coinsAmount }) .then(() => { form.reset() }) @@ -61,4 +55,4 @@ const useBuyLiquidityForm = (market: Market) => { return { form, canSubmit, onSubmit } } -export { useBuyLiquidityForm } +export { useProvideLiquidityForm } diff --git a/frontend/src/features/MarketDetail/components/MarketBetting/ProvideLiquidity/index.tsx b/frontend/src/features/MarketDetail/components/MarketBetting/ProvideLiquidity/index.tsx new file mode 100644 index 0000000..2bd2cce --- /dev/null +++ b/frontend/src/features/MarketDetail/components/MarketBetting/ProvideLiquidity/index.tsx @@ -0,0 +1,51 @@ +import { Button, Stack } from "@mui/joy" +import { FormProvider } from "react-hook-form" +import { useQuery } from "@tanstack/react-query" + +import { useCurrentAccount } from "@config/chain" +import type { Market } from "@api/queries/Market" +import { balancesQuery } from "@api/queries/Balances" +import { coinPricesQuery } from "@api/queries/Prices" +import { useProvideLiquidityForm } from "./form" +import { CoinsAmountField } from "../CoinsAmountField" + +const MarketProvideLiquidityForm = (props: { market: Market }) => { + const { market } = props + const account = useCurrentAccount() + const balances = useQuery(balancesQuery(account.bech32Address)) + const price = useQuery(coinPricesQuery).data?.get(market.denom) + + const { form, canSubmit, onSubmit } = useProvideLiquidityForm(market) + + return ( + + + + + + + + ) +} + +export { MarketProvideLiquidityForm } diff --git a/frontend/src/features/MarketDetail/components/MarketBetting/index.tsx b/frontend/src/features/MarketDetail/components/MarketBetting/index.tsx index c29b99b..b19f47e 100644 --- a/frontend/src/features/MarketDetail/components/MarketBetting/index.tsx +++ b/frontend/src/features/MarketDetail/components/MarketBetting/index.tsx @@ -15,7 +15,7 @@ import { import { MarketClaimForm } from "./Claim" import { MarketBuyForm } from "./Buy" import { MarketSellForm } from "./Sell" -import { MarketBuyLiquidityForm } from "./BuyLiquidity" +import { MarketProvideLiquidityForm } from "./ProvideLiquidity" const MarketBetting = (props: StyleProps) => { return ( @@ -111,7 +111,7 @@ const MarketBettingForm = (props: { market: Market; status: MarketStatus }) => { {match(action) .with("buy", () => ) .with("sell", () => ) - .with("liquidity", () => ) + .with("liquidity", () => ) .exhaustive()} ) diff --git a/frontend/src/utils/analytics.ts b/frontend/src/utils/analytics.ts index 764f549..32cac0e 100644 --- a/frontend/src/utils/analytics.ts +++ b/frontend/src/utils/analytics.ts @@ -2,7 +2,11 @@ import type { MarketId, OutcomeId } from "@api/queries/Market" import type { Coins } from "./coins" import type { Shares } from "./shares" -type EventName = "place_bet" | "cancel_bet" | "buy_liquidity" | "claim_earnings" +type EventName = + | "place_bet" + | "cancel_bet" + | "provide_liquidity" + | "claim_earnings" const trackSuccess = (eventName: EventName, params: Gtag.CustomParams) => { gtag("event", eventName, { @@ -78,20 +82,18 @@ const trackCancelBet = (params: TrackCancelBetParams, failure?: Error) => { interface TrackBuyLiquidityParams { marketId: MarketId - outcomeId: OutcomeId coins: Coins walletName: string } -const trackBuyLiquidity = ( +const trackProvideLiquidity = ( params: TrackBuyLiquidityParams, failure?: Error, ) => { trackEvent( - "buy_liquidity", + "provide_liquidity", { market_id: params.marketId, - outcome_id: params.outcomeId, tokens_amount: params.coins.units.toFixed(0), denom: params.coins.denom, wallet: params.walletName, @@ -119,4 +121,9 @@ const trackClaimEarnings = ( ) } -export { trackPlaceBet, trackCancelBet, trackBuyLiquidity, trackClaimEarnings } +export { + trackPlaceBet, + trackCancelBet, + trackProvideLiquidity, + trackClaimEarnings, +} diff --git a/frontend/src/utils/errors.ts b/frontend/src/utils/errors.ts index f71cc7f..f088f0a 100644 --- a/frontend/src/utils/errors.ts +++ b/frontend/src/utils/errors.ts @@ -35,7 +35,7 @@ class AppError extends Error { } } -type UserAction = "connect" | "buy" | "sell" | "claim" +type UserAction = "connect" | "buy" | "sell" | "provide" | "claim" /** * @returns user-friendly errors (if possible), based on the action that is being performed. diff --git a/frontend/src/utils/shares.ts b/frontend/src/utils/shares.ts index b3130c4..d2523e9 100644 --- a/frontend/src/utils/shares.ts +++ b/frontend/src/utils/shares.ts @@ -2,6 +2,7 @@ import BigNumber from "bignumber.js" import type { Market, OutcomeId } from "@api/queries/Market" import type { Positions } from "@api/queries/Positions" +import { LIQUDITY_PORTION } from "@api/mutations/PlaceBet" import { Asset, Coins, @@ -155,7 +156,6 @@ const getPurchaseResult = ( market: Market, outcomeId: OutcomeId, coinsAmount: Coins, - liquidityPortion: BigNumber.Value, ): PurchaseResult => { // To calculate the shares properly, we need to follow the same steps as // are taken by the contract, namely: @@ -185,7 +185,7 @@ const getPurchaseResult = ( // Step 2: take off the liquidity, add to pool, prepare to use the remainder const liquidity = buyAmountWithoutFees - .times(liquidityPortion) + .times(LIQUDITY_PORTION) .integerValue(BigNumber.ROUND_DOWN) const buyAmount = buyAmountWithoutFees.minus(liquidity) const { pool, returned } = addToPool(poolAfterFees, liquidity) From 57032b7a5f931e8dc267a4fc2e2da68f20f835d3 Mon Sep 17 00:00:00 2001 From: Alduin Date: Mon, 14 Oct 2024 11:40:51 -0300 Subject: [PATCH 3/3] Cleanup --- .../MarketDetail/components/MarketBetting/Buy/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/features/MarketDetail/components/MarketBetting/Buy/index.tsx b/frontend/src/features/MarketDetail/components/MarketBetting/Buy/index.tsx index fb91730..333bd6c 100644 --- a/frontend/src/features/MarketDetail/components/MarketBetting/Buy/index.tsx +++ b/frontend/src/features/MarketDetail/components/MarketBetting/Buy/index.tsx @@ -7,7 +7,6 @@ import { useCurrentAccount } from "@config/chain" import type { Market, OutcomeId } from "@api/queries/Market" import { balancesQuery } from "@api/queries/Balances" import { coinPricesQuery } from "@api/queries/Prices" -import { LIQUDITY_PORTION } from "@api/mutations/PlaceBet" import { Coins, USD } from "@utils/coins" import { useLatestFormValues } from "@utils/forms" import { getDifferencePercentage } from "@utils/number" @@ -79,7 +78,7 @@ const ShowSharesPurchased = (props: ShowSharesPurchasedProps) => { const { market, betOutcome, coinsAmount } = props const purchaseResult = betOutcome !== undefined && coinsAmount !== undefined - ? getPurchaseResult(market, betOutcome, coinsAmount, LIQUDITY_PORTION) + ? getPurchaseResult(market, betOutcome, coinsAmount) : undefined const selectedOutcome = market.possibleOutcomes.find(