Skip to content

Commit

Permalink
⛑️ Revenue share amm restriction (Joystream#6416)
Browse files Browse the repository at this point in the history
* Add new hook to unify the token lock logic

* Implement new hook throughout the app

* Correct logic to follow runtime
  • Loading branch information
ikprk committed Jul 24, 2024
1 parent 32458e5 commit 6ef604c
Show file tree
Hide file tree
Showing 6 changed files with 41 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ import { BuyMarketTokenModal } from '@/components/_crt/BuyMarketTokenModal'

type BuyFromMarketButtonProps = {
tokenId: string
hasActiveRevenueShare?: boolean
isTokenLocked?: boolean
}

export const BuyFromMarketButton = ({ tokenId, hasActiveRevenueShare }: BuyFromMarketButtonProps) => {
export const BuyFromMarketButton = ({ tokenId, isTokenLocked }: BuyFromMarketButtonProps) => {
const [showModal, setShowModal] = useState(false)
return (
<>
<BuyMarketTokenModal tokenId={tokenId} show={showModal} onClose={() => setShowModal(false)} />
<ProtectedActionWrapper title="You want to buy tokens?" description="Sign in to buy">
<Button
variant={hasActiveRevenueShare ? 'warning' : 'primary'}
variant={isTokenLocked ? 'warning' : 'primary'}
size="large"
fullWidth
onClick={() => setShowModal(true)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ import { DialogProps } from '@/components/_overlays/Dialog'
import { DialogModal } from '@/components/_overlays/DialogModal'
import { absoluteRoutes } from '@/config/routes'
import { useDismissibleAction } from '@/hooks/useDismissibleAction'
import { useIsTokenInLockedMode } from '@/hooks/useIsTokenInLockedMode'
import { useMediaMatch } from '@/hooks/useMediaMatch'
import { useSegmentAnalytics } from '@/hooks/useSegmentAnalytics'
import { hapiBnToTokenNumber, tokenNumberToHapiBn } from '@/joystream-lib/utils'
import { useFee, useJoystream, useSubscribeAccountBalance } from '@/providers/joystream'
import { useJoystreamStore } from '@/providers/joystream/joystream.store'
import { useNetworkUtils } from '@/providers/networkUtils/networkUtils.hooks'
import { useSnackbar } from '@/providers/snackbars'
import { useTransaction } from '@/providers/transactions/transactions.hooks'
Expand Down Expand Up @@ -57,7 +57,6 @@ export const BuyMarketTokenModal = ({ tokenId, onClose: _onClose, show }: BuySal
const amountRef = useRef<number | null>(null)
const { control, watch, handleSubmit, reset, formState } = useForm<{ tokenAmount: number }>()
const tokenAmount = watch('tokenAmount') || 0
const currentBlockRef = useRef(useJoystreamStore((store) => store.currentBlock))
const { fullFee } = useFee('purchaseTokenOnMarketTx', ['1', '1', String(tokenAmount ?? 0), '1000000'])
const { data, loading } = useGetFullCreatorTokenQuery({
variables: {
Expand All @@ -67,8 +66,8 @@ export const BuyMarketTokenModal = ({ tokenId, onClose: _onClose, show }: BuySal
SentryLogger.error('Error while fetching creator token', 'BuyMarketTokenModal', error)
},
})
const activeRevenueShare = data?.creatorTokenById?.revenueShares.find((rS) => !rS.finalized)
const hasActiveRevenueShare = (activeRevenueShare?.endsAt ?? 0) > currentBlockRef.current
const isTokenLocked = useIsTokenInLockedMode(data?.creatorTokenById ?? undefined)

const { data: memberTokenAccount } = useGetCreatorTokenHoldersQuery({
variables: {
where: {
Expand Down Expand Up @@ -355,10 +354,10 @@ export const BuyMarketTokenModal = ({ tokenId, onClose: _onClose, show }: BuySal
if (activeStep === BUY_MARKET_TOKEN_STEPS.form) {
setPrimaryButtonProps({
text: 'Continue',
disabled: hasActiveRevenueShare,
variant: hasActiveRevenueShare ? 'warning' : undefined,
disabled: isTokenLocked,
variant: isTokenLocked ? 'warning' : undefined,
onClick: () => {
if (hasActiveRevenueShare) {
if (isTokenLocked) {
displaySnackbar({
iconType: 'error',
title: 'You cannot trade tokens during revenue share.',
Expand All @@ -385,7 +384,7 @@ export const BuyMarketTokenModal = ({ tokenId, onClose: _onClose, show }: BuySal
displaySnackbar,
handleSubmit,
hasAcceptedConditions,
hasActiveRevenueShare,
isTokenLocked,
onTransactionSubmit,
])

Expand All @@ -410,7 +409,7 @@ export const BuyMarketTokenModal = ({ tokenId, onClose: _onClose, show }: BuySal
confetti={activeStep === BUY_MARKET_TOKEN_STEPS.success && smMatch}
noContentPadding={activeStep === BUY_MARKET_TOKEN_STEPS.conditions}
additionalActionsNode={
hasActiveRevenueShare ? (
isTokenLocked ? (
<Tooltip text="During revenue share you are unable to trade on market. Please wait until it ends to make new transactions.">
<SvgAlertsWarning24 />
</Tooltip>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import { ExpandButton } from '@/components/_buttons/ExpandButton'
import { BuyFromMarketButton } from '@/components/_crt/BuyFromMarketButton/BuyFromMarketButton'
import { SellOnMarketButton } from '@/components/_crt/SellOnMarketButton/SellOnMarketButton'
import { DetailsContent } from '@/components/_nft/NftTile'
import { useIsTokenInLockedMode } from '@/hooks/useIsTokenInLockedMode'
import { useMediaMatch } from '@/hooks/useMediaMatch'
import { hapiBnToTokenNumber } from '@/joystream-lib/utils'
import { useJoystreamStore } from '@/providers/joystream/joystream.store'
import { calcBuyMarketPricePerToken } from '@/utils/crts'
import { SentryLogger } from '@/utils/logs'
import { formatDate } from '@/utils/time'
Expand Down Expand Up @@ -110,9 +110,7 @@ const InactiveDetails = () => {
}

const MarketDetails = ({ token }: { token: FullCreatorTokenFragment }) => {
const currentBlockRef = useRef(useJoystreamStore((store) => store.currentBlock))
const activeRevenueShare = token.revenueShares.find((rS) => !rS.finalized)
const hasActiveRevenueShare = (activeRevenueShare?.endsAt ?? 0) > currentBlockRef.current
const isTokenLocked = useIsTokenInLockedMode(token)
const calculateSlippageAmount = useCallback(
(amount: number) => {
const currentAmm = token?.ammCurves.find((amm) => !amm.finalized)
Expand All @@ -136,8 +134,8 @@ const MarketDetails = ({ token }: { token: FullCreatorTokenFragment }) => {
tooltipText="Price of each incremental unit purchased or sold depends on overall quantity of tokens transacted, the actual average price per unit for the entire purchase or sale will differ from the price displayed for the first unit transacted."
/>
<FlexBox equalChildren width="100%" gap={2}>
<SellOnMarketButton hasActiveRevenueShare={hasActiveRevenueShare} tokenId={token.id} />
<BuyFromMarketButton hasActiveRevenueShare={hasActiveRevenueShare} tokenId={token.id} />
<SellOnMarketButton isTokenLocked={isTokenLocked} tokenId={token.id} />
<BuyFromMarketButton isTokenLocked={isTokenLocked} tokenId={token.id} />
</FlexBox>

<SupplyLine>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ import { SellTokenModal } from '@/components/_crt/SellTokenModal'

type SellOnMarketButtonProps = {
tokenId: string
hasActiveRevenueShare?: boolean
isTokenLocked?: boolean
}

export const SellOnMarketButton = ({ tokenId, hasActiveRevenueShare }: SellOnMarketButtonProps) => {
export const SellOnMarketButton = ({ tokenId, isTokenLocked }: SellOnMarketButtonProps) => {
const [showModal, setShowModal] = useState(false)
return (
<>
<SellTokenModal tokenId={tokenId} show={showModal} onClose={() => setShowModal(false)} />
<ProtectedActionWrapper title="You want to sell tokens?" description="Sign in to sell">
<Button
variant={hasActiveRevenueShare ? 'warning-secondary' : 'secondary'}
variant={isTokenLocked ? 'warning-secondary' : 'secondary'}
fullWidth
size="large"
onClick={() => setShowModal(true)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import BN from 'bn.js'
import { useCallback, useMemo, useRef, useState } from 'react'
import { useCallback, useMemo, useState } from 'react'
import { useForm } from 'react-hook-form'

import { useGetFullCreatorTokenQuery } from '@/api/queries/__generated__/creatorTokens.generated'
Expand All @@ -12,10 +12,10 @@ import { AmmModalSummaryTemplate } from '@/components/_crt/AmmModalTemplates/Amm
import { DialogModal } from '@/components/_overlays/DialogModal'
import { atlasConfig } from '@/config'
import { useGetTokenBalance } from '@/hooks/useGetTokenBalance'
import { useIsTokenInLockedMode } from '@/hooks/useIsTokenInLockedMode'
import { useSegmentAnalytics } from '@/hooks/useSegmentAnalytics'
import { hapiBnToTokenNumber, tokenNumberToHapiBn } from '@/joystream-lib/utils'
import { useFee, useJoystream } from '@/providers/joystream'
import { useJoystreamStore } from '@/providers/joystream/joystream.store'
import { useNetworkUtils } from '@/providers/networkUtils/networkUtils.hooks'
import { useSnackbar } from '@/providers/snackbars'
import { useTransaction } from '@/providers/transactions/transactions.hooks'
Expand All @@ -41,7 +41,6 @@ export const SellTokenModal = ({ tokenId, onClose: _onClose, show }: SellTokenMo
const { trackAMMTokensSold } = useSegmentAnalytics()
const { displaySnackbar } = useSnackbar()
const { fullFee } = useFee('sellTokenOnMarketTx', ['1', '1', '2', '10000000'])
const currentBlockRef = useRef(useJoystreamStore((store) => store.currentBlock))
const { data, loading } = useGetFullCreatorTokenQuery({
variables: {
id: tokenId,
Expand All @@ -50,8 +49,7 @@ export const SellTokenModal = ({ tokenId, onClose: _onClose, show }: SellTokenMo
SentryLogger.error('Failed to fetch token data', 'SellTokenModal', { error })
},
})
const activeRevenueShare = data?.creatorTokenById?.revenueShares.find((rS) => !rS.finalized)
const hasActiveRevenueShare = (activeRevenueShare?.endsAt ?? 0) > currentBlockRef.current
const isTokenLocked = useIsTokenInLockedMode(data?.creatorTokenById ?? undefined)

const currentAmm = data?.creatorTokenById?.currentAmmSale
const ammBalance = currentAmm ? +currentAmm.mintedByAmm - +currentAmm.burnedByAmm : 0
Expand Down Expand Up @@ -198,7 +196,7 @@ export const SellTokenModal = ({ tokenId, onClose: _onClose, show }: SellTokenMo
)

const onFormSubmit = () => {
if (hasActiveRevenueShare) {
if (isTokenLocked) {
displaySnackbar({
iconType: 'error',
title: 'You cannot trade tokens during revenue share.',
Expand Down Expand Up @@ -269,7 +267,7 @@ export const SellTokenModal = ({ tokenId, onClose: _onClose, show }: SellTokenMo
show={show}
onExitClick={onClose}
additionalActionsNode={
hasActiveRevenueShare ? (
isTokenLocked ? (
<Tooltip text="During revenue share you are unable to trade on market. Please wait until it ends to make new transactions.">
<SvgAlertsWarning24 />
</Tooltip>
Expand All @@ -282,8 +280,8 @@ export const SellTokenModal = ({ tokenId, onClose: _onClose, show }: SellTokenMo
primaryButton={{
text: isFormStep ? 'Continue' : `Sell $${title}`,
onClick: isFormStep ? onFormSubmit : onTransactionSubmit,
disabled: hasActiveRevenueShare,
variant: hasActiveRevenueShare ? 'warning' : undefined,
disabled: isTokenLocked,
variant: isTokenLocked ? 'warning' : undefined,
}}
>
{step === 'form' ? (
Expand Down
16 changes: 16 additions & 0 deletions packages/atlas/src/hooks/useIsTokenInLockedMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// import { useRef } from 'react'
import { FullCreatorTokenFragment } from '@/api/queries/__generated__/fragments.generated'

// import { useJoystreamStore } from '@/providers/joystream/joystream.store'

export const useIsTokenInLockedMode = (token?: FullCreatorTokenFragment) => {
// const currentBlockRef = useRef(useJoystreamStore((store) => store.currentBlock))
// 1. Check if there is any unfinalized revenue share
const activeRevenueShare = token?.revenueShares.find((rS) => !rS.finalized)

// 2. If ending block is not yet came for a user, consider that the token is locked
// BUG: looks like runtime doesnt allow to make tx if the revenue share not finalized
// const hasActiveRevenueShare = (activeRevenueShare?.endsAt ?? 0) > currentBlockRef.current

return !!activeRevenueShare
}

0 comments on commit 6ef604c

Please sign in to comment.