mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Pool tab refactor (#1009)
* refactor and simplify * fix user pool transaction section * split up fetching and data manipulation * restore refetch after add/remove, rename all the things * more naming and logging * new state structure unifying multiple data structures * another response failsafe * naming
This commit is contained in:
parent
42323cb8c4
commit
f55d8d9a91
@ -25,6 +25,7 @@ import {
|
|||||||
} from '../@types/subgraph/PoolShares'
|
} from '../@types/subgraph/PoolShares'
|
||||||
import { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData'
|
import { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData'
|
||||||
import { UserSalesQuery as UsersSalesList } from '../@types/subgraph/UserSalesQuery'
|
import { UserSalesQuery as UsersSalesList } from '../@types/subgraph/UserSalesQuery'
|
||||||
|
import { PoolLiquidity } from 'src/@types/subgraph/PoolLiquidity'
|
||||||
|
|
||||||
export interface UserLiquidity {
|
export interface UserLiquidity {
|
||||||
price: string
|
price: string
|
||||||
@ -281,6 +282,44 @@ const TopSalesQuery = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const poolLiquidityQuery = gql`
|
||||||
|
query PoolLiquidity($pool: ID!, $owner: String!) {
|
||||||
|
pool(id: $pool) {
|
||||||
|
id
|
||||||
|
totalShares
|
||||||
|
poolFee
|
||||||
|
opfFee
|
||||||
|
marketFee
|
||||||
|
spotPrice
|
||||||
|
baseToken {
|
||||||
|
address
|
||||||
|
symbol
|
||||||
|
}
|
||||||
|
baseTokenWeight
|
||||||
|
baseTokenLiquidity
|
||||||
|
datatoken {
|
||||||
|
address
|
||||||
|
symbol
|
||||||
|
}
|
||||||
|
datatokenWeight
|
||||||
|
datatokenLiquidity
|
||||||
|
shares(where: { user: $owner }) {
|
||||||
|
shares
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const userPoolShareQuery = gql`
|
||||||
|
query PoolShare($pool: ID!, $user: String!) {
|
||||||
|
pool(id: $pool) {
|
||||||
|
shares(where: { user: $user }) {
|
||||||
|
shares
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
export function getSubgraphUri(chainId: number): string {
|
export function getSubgraphUri(chainId: number): string {
|
||||||
const config = getOceanConfig(chainId)
|
const config = getOceanConfig(chainId)
|
||||||
return config.subgraphUri
|
return config.subgraphUri
|
||||||
@ -776,3 +815,37 @@ export async function getTopAssetsPublishers(
|
|||||||
|
|
||||||
return publisherSales.slice(0, nrItems)
|
return publisherSales.slice(0, nrItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getPoolData(
|
||||||
|
chainId: number,
|
||||||
|
pool: string,
|
||||||
|
owner: string
|
||||||
|
) {
|
||||||
|
const queryVariables = {
|
||||||
|
pool: pool.toLowerCase(),
|
||||||
|
owner: owner.toLowerCase()
|
||||||
|
}
|
||||||
|
const response: OperationResult<PoolLiquidity> = await fetchData(
|
||||||
|
poolLiquidityQuery,
|
||||||
|
queryVariables,
|
||||||
|
getQueryContext(chainId)
|
||||||
|
)
|
||||||
|
return response?.data?.pool
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserPoolShareBalance(
|
||||||
|
chainId: number,
|
||||||
|
pool: string,
|
||||||
|
accountId: string
|
||||||
|
): Promise<string> {
|
||||||
|
const queryVariables = {
|
||||||
|
pool: pool.toLowerCase(),
|
||||||
|
user: accountId.toLowerCase()
|
||||||
|
}
|
||||||
|
const response: OperationResult<PoolLiquidity> = await fetchData(
|
||||||
|
userPoolShareQuery,
|
||||||
|
queryVariables,
|
||||||
|
getQueryContext(chainId)
|
||||||
|
)
|
||||||
|
return response?.data?.pool?.shares[0]?.shares || '0'
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.actions {
|
.actions {
|
||||||
/* composes: container from './AssetActions/Pool/index.module.css'; */
|
composes: container from './Pool/index.module.css';
|
||||||
border-top: 1px solid var(--border-color);
|
border-top: 1px solid var(--border-color);
|
||||||
margin-top: calc(var(--spacer) / 1.5);
|
margin-top: calc(var(--spacer) / 1.5);
|
||||||
padding: calc(var(--spacer) / 1.5);
|
padding: calc(var(--spacer) / 1.5);
|
||||||
@ -12,7 +12,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
/* composes: title from './AssetActions/Pool/index.module.css'; */
|
composes: title from './Pool/index.module.css';
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -25,22 +25,22 @@ const initialValues: FormAddLiquidity = {
|
|||||||
|
|
||||||
export default function Add({
|
export default function Add({
|
||||||
setShowAdd,
|
setShowAdd,
|
||||||
refreshInfo,
|
|
||||||
poolAddress,
|
poolAddress,
|
||||||
totalPoolTokens,
|
totalPoolTokens,
|
||||||
totalBalance,
|
totalBalance,
|
||||||
swapFee,
|
swapFee,
|
||||||
dtSymbol,
|
dtSymbol,
|
||||||
dtAddress
|
dtAddress,
|
||||||
|
fetchAllData
|
||||||
}: {
|
}: {
|
||||||
setShowAdd: (show: boolean) => void
|
setShowAdd: (show: boolean) => void
|
||||||
refreshInfo: () => void
|
|
||||||
poolAddress: string
|
poolAddress: string
|
||||||
totalPoolTokens: string
|
totalPoolTokens: string
|
||||||
totalBalance: PoolBalance
|
totalBalance: PoolBalance
|
||||||
swapFee: string
|
swapFee: string
|
||||||
dtSymbol: string
|
dtSymbol: string
|
||||||
dtAddress: string
|
dtAddress: string
|
||||||
|
fetchAllData: () => void
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { accountId, balance, web3 } = useWeb3()
|
const { accountId, balance, web3 } = useWeb3()
|
||||||
const { isAssetNetwork } = useAsset()
|
const { isAssetNetwork } = useAsset()
|
||||||
@ -76,7 +76,7 @@ export default function Add({
|
|||||||
setDtBalance(dtBalance)
|
setDtBalance(dtBalance)
|
||||||
}
|
}
|
||||||
getDtBalance()
|
getDtBalance()
|
||||||
}, [web3, accountId, dtAddress, coin])
|
}, [web3, accountId, dtAddress, coin, isAssetNetwork])
|
||||||
|
|
||||||
// Get maximum amount for either OCEAN or datatoken
|
// Get maximum amount for either OCEAN or datatoken
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -113,7 +113,7 @@ export default function Add({
|
|||||||
// : await ocean.pool.addDTLiquidity(accountId, poolAddress, `${amount}`)
|
// : await ocean.pool.addDTLiquidity(accountId, poolAddress, `${amount}`)
|
||||||
// setTxId(result?.transactionHash)
|
// setTxId(result?.transactionHash)
|
||||||
// resetForm()
|
// resetForm()
|
||||||
// refreshInfo()
|
fetchAllData()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error.message)
|
console.error(error.message)
|
||||||
toast.error(error.message)
|
toast.error(error.message)
|
||||||
|
@ -25,18 +25,18 @@ import content from '../../../../../content/price.json'
|
|||||||
|
|
||||||
export default function Remove({
|
export default function Remove({
|
||||||
setShowRemove,
|
setShowRemove,
|
||||||
refreshInfo,
|
|
||||||
poolAddress,
|
poolAddress,
|
||||||
poolTokens,
|
poolTokens,
|
||||||
totalPoolTokens,
|
totalPoolTokens,
|
||||||
dtSymbol
|
dtSymbol,
|
||||||
|
fetchAllData
|
||||||
}: {
|
}: {
|
||||||
setShowRemove: (show: boolean) => void
|
setShowRemove: (show: boolean) => void
|
||||||
refreshInfo: () => void
|
|
||||||
poolAddress: string
|
poolAddress: string
|
||||||
poolTokens: string
|
poolTokens: string
|
||||||
totalPoolTokens: string
|
totalPoolTokens: string
|
||||||
dtSymbol: string
|
dtSymbol: string
|
||||||
|
fetchAllData: () => void
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const slippagePresets = ['5', '10', '15', '25', '50']
|
const slippagePresets = ['5', '10', '15', '25', '50']
|
||||||
const { accountId } = useWeb3()
|
const { accountId } = useWeb3()
|
||||||
@ -74,7 +74,7 @@ export default function Remove({
|
|||||||
// minOceanAmount
|
// minOceanAmount
|
||||||
// )
|
// )
|
||||||
// setTxId(result?.transactionHash)
|
// setTxId(result?.transactionHash)
|
||||||
// refreshInfo()
|
fetchAllData()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
LoggerInstance.error(error.message)
|
LoggerInstance.error(error.message)
|
||||||
toast.error(error.message)
|
toast.error(error.message)
|
||||||
|
@ -13,380 +13,369 @@ import TokenList from './TokenList'
|
|||||||
import AssetActionHistoryTable from '../AssetActionHistoryTable'
|
import AssetActionHistoryTable from '../AssetActionHistoryTable'
|
||||||
import Graph from './Graph'
|
import Graph from './Graph'
|
||||||
import { useAsset } from '@context/Asset'
|
import { useAsset } from '@context/Asset'
|
||||||
import { gql, OperationResult } from 'urql'
|
import { PoolLiquidity_pool as PoolLiquidityData } from '../../../../@types/subgraph/PoolLiquidity'
|
||||||
import { PoolLiquidity } from '../../../../@types/subgraph/PoolLiquidity'
|
|
||||||
import { useWeb3 } from '@context/Web3'
|
import { useWeb3 } from '@context/Web3'
|
||||||
import PoolTransactions from '@shared/PoolTransactions'
|
import PoolTransactions from '@shared/PoolTransactions'
|
||||||
import { fetchData, getQueryContext } from '@utils/subgraph'
|
|
||||||
import { isValidNumber } from '@utils/numbers'
|
import { isValidNumber } from '@utils/numbers'
|
||||||
import Decimal from 'decimal.js'
|
import Decimal from 'decimal.js'
|
||||||
import content from '../../../../../content/price.json'
|
import content from '../../../../../content/price.json'
|
||||||
|
import { getPoolData, getUserPoolShareBalance } from '@utils/subgraph'
|
||||||
|
|
||||||
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
|
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
|
||||||
|
|
||||||
const poolLiquidityQuery = gql`
|
function getWeight(weight: string) {
|
||||||
query PoolLiquidity($pool: ID!, $owner: String) {
|
return isValidNumber(weight) ? new Decimal(weight).mul(10).toString() : '0'
|
||||||
pool(id: $pool) {
|
}
|
||||||
id
|
|
||||||
totalShares
|
|
||||||
poolFee
|
|
||||||
opfFee
|
|
||||||
marketFee
|
|
||||||
spotPrice
|
|
||||||
baseToken {
|
|
||||||
address
|
|
||||||
symbol
|
|
||||||
}
|
|
||||||
baseTokenWeight
|
|
||||||
baseTokenLiquidity
|
|
||||||
datatoken {
|
|
||||||
address
|
|
||||||
symbol
|
|
||||||
}
|
|
||||||
datatokenWeight
|
|
||||||
datatokenLiquidity
|
|
||||||
shares(where: { user: $owner }) {
|
|
||||||
shares
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const userPoolShareQuery = gql`
|
interface PoolInfo {
|
||||||
query PoolShare($pool: ID!, $user: String) {
|
poolFee: string
|
||||||
pool(id: $pool) {
|
weightOcean: string
|
||||||
id
|
weightDt: string
|
||||||
shares(where: { user: $user }) {
|
dtSymbol: string
|
||||||
shares
|
oceanSymbol: string
|
||||||
}
|
totalPoolTokens: string
|
||||||
}
|
totalLiquidityInOcean: Decimal
|
||||||
}
|
}
|
||||||
`
|
|
||||||
|
interface PoolInfoUser {
|
||||||
|
totalLiquidityInOcean: Decimal
|
||||||
|
liquidity: PoolBalance
|
||||||
|
poolShares: string
|
||||||
|
poolShare: string // in %
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialPoolInfo: Partial<PoolInfo> = {
|
||||||
|
totalLiquidityInOcean: new Decimal(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialPoolInfoUser: Partial<PoolInfoUser> = {
|
||||||
|
totalLiquidityInOcean: new Decimal(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialPoolInfoCreator: Partial<PoolInfoUser> = initialPoolInfoUser
|
||||||
|
|
||||||
export default function Pool(): ReactElement {
|
export default function Pool(): ReactElement {
|
||||||
const { accountId } = useWeb3()
|
const { accountId } = useWeb3()
|
||||||
const [dtSymbol, setDtSymbol] = useState<string>()
|
|
||||||
const [oceanSymbol, setOceanSymbol] = useState<string>()
|
|
||||||
const { isInPurgatory, ddo, owner, price, refreshInterval, isAssetNetwork } =
|
const { isInPurgatory, ddo, owner, price, refreshInterval, isAssetNetwork } =
|
||||||
useAsset()
|
useAsset()
|
||||||
|
|
||||||
const [poolTokens, setPoolTokens] = useState<string>()
|
const [poolData, setPoolData] = useState<PoolLiquidityData>()
|
||||||
const [totalPoolTokens, setTotalPoolTokens] = useState<string>()
|
const [poolInfo, setPoolInfo] = useState<PoolInfo>(
|
||||||
const [userLiquidity, setUserLiquidity] = useState<PoolBalance>()
|
initialPoolInfo as PoolInfo
|
||||||
const [swapFee, setSwapFee] = useState<string>()
|
)
|
||||||
const [weightOcean, setWeightOcean] = useState<string>()
|
const [poolInfoOwner, setPoolInfoOwner] = useState<PoolInfoUser>(
|
||||||
const [weightDt, setWeightDt] = useState<string>()
|
initialPoolInfoCreator as PoolInfoUser
|
||||||
|
)
|
||||||
|
const [poolInfoUser, setPoolInfoUser] = useState<PoolInfoUser>(
|
||||||
|
initialPoolInfoUser as PoolInfoUser
|
||||||
|
)
|
||||||
|
|
||||||
|
const [hasUserAddedLiquidity, setUserHasAddedLiquidity] = useState(false)
|
||||||
const [showAdd, setShowAdd] = useState(false)
|
const [showAdd, setShowAdd] = useState(false)
|
||||||
const [showRemove, setShowRemove] = useState(false)
|
const [showRemove, setShowRemove] = useState(false)
|
||||||
const [isRemoveDisabled, setIsRemoveDisabled] = useState(false)
|
const [isRemoveDisabled, setIsRemoveDisabled] = useState(false)
|
||||||
|
const [fetchInterval, setFetchInterval] = useState<NodeJS.Timeout>()
|
||||||
|
|
||||||
const [hasAddedLiquidity, setHasAddedLiquidity] = useState(false)
|
const fetchPoolData = useCallback(async () => {
|
||||||
const [poolShare, setPoolShare] = useState<string>()
|
|
||||||
const [totalUserLiquidityInOcean, setTotalUserLiquidityInOcean] = useState(
|
|
||||||
new Decimal(0)
|
|
||||||
)
|
|
||||||
const [totalLiquidityInOcean, setTotalLiquidityInOcean] = useState(
|
|
||||||
new Decimal(0)
|
|
||||||
)
|
|
||||||
|
|
||||||
const [creatorTotalLiquidityInOcean, setCreatorTotalLiquidityInOcean] =
|
|
||||||
useState(new Decimal(0))
|
|
||||||
const [creatorLiquidity, setCreatorLiquidity] = useState<PoolBalance>()
|
|
||||||
const [creatorPoolTokens, setCreatorPoolTokens] = useState<string>()
|
|
||||||
const [creatorPoolShare, setCreatorPoolShare] = useState<string>()
|
|
||||||
const [dataLiquidity, setdataLiquidity] = useState<PoolLiquidity>()
|
|
||||||
const [liquidityFetchInterval, setLiquidityFetchInterval] =
|
|
||||||
useState<NodeJS.Timeout>()
|
|
||||||
|
|
||||||
// the purpose of the value is just to trigger the effect
|
|
||||||
const [refreshPool, setRefreshPool] = useState(false)
|
|
||||||
|
|
||||||
const getPoolLiquidity = useCallback(async () => {
|
|
||||||
if (!ddo?.chainId || !price?.address || !owner) return
|
if (!ddo?.chainId || !price?.address || !owner) return
|
||||||
|
|
||||||
const queryVariables = {
|
const poolData = await getPoolData(ddo.chainId, price.address, owner)
|
||||||
pool: price.address.toLowerCase(),
|
setPoolData(poolData)
|
||||||
owner: owner.toLowerCase()
|
LoggerInstance.log('[pool] Fetched pool data:', poolData)
|
||||||
}
|
|
||||||
const queryResult: OperationResult<PoolLiquidity> = await fetchData(
|
|
||||||
poolLiquidityQuery,
|
|
||||||
queryVariables,
|
|
||||||
getQueryContext(ddo.chainId)
|
|
||||||
)
|
|
||||||
setdataLiquidity(queryResult?.data)
|
|
||||||
}, [ddo?.chainId, price?.address, owner])
|
}, [ddo?.chainId, price?.address, owner])
|
||||||
|
|
||||||
async function getUserPoolShareBalance() {
|
const fetchUserShares = useCallback(async () => {
|
||||||
const queryVariables = {
|
if (!ddo?.chainId || !price?.address || !accountId) return
|
||||||
pool: price.address.toLowerCase(),
|
|
||||||
user: accountId.toLowerCase()
|
const userShares = await getUserPoolShareBalance(
|
||||||
}
|
ddo.chainId,
|
||||||
const queryResult: OperationResult<PoolLiquidity> = await fetchData(
|
price.address,
|
||||||
userPoolShareQuery,
|
accountId
|
||||||
queryVariables,
|
|
||||||
getQueryContext(ddo.chainId)
|
|
||||||
)
|
)
|
||||||
return queryResult?.data.pool.shares[0]?.shares
|
setPoolInfoUser((prevState) => ({
|
||||||
}
|
...prevState,
|
||||||
|
poolShares: userShares
|
||||||
|
}))
|
||||||
|
LoggerInstance.log(`[pool] Fetched user shares: ${userShares}`)
|
||||||
|
}, [ddo?.chainId, price?.address, accountId])
|
||||||
|
|
||||||
const refetchLiquidity = useCallback(() => {
|
// Helper: fetch everything
|
||||||
if (liquidityFetchInterval) return
|
const fetchAllData = useCallback(() => {
|
||||||
|
fetchPoolData()
|
||||||
|
fetchUserShares()
|
||||||
|
}, [fetchPoolData, fetchUserShares])
|
||||||
|
|
||||||
const newInterval = setInterval(() => getPoolLiquidity(), refreshInterval)
|
// Helper: start interval fetching
|
||||||
setLiquidityFetchInterval(newInterval)
|
const initFetchInterval = useCallback(() => {
|
||||||
}, [liquidityFetchInterval, getPoolLiquidity, refreshInterval])
|
if (fetchInterval) return
|
||||||
|
|
||||||
|
const newInterval = setInterval(() => {
|
||||||
|
fetchAllData()
|
||||||
|
LoggerInstance.log(
|
||||||
|
`[pool] Refetch interval fired after ${refreshInterval / 1000}s`
|
||||||
|
)
|
||||||
|
}, refreshInterval)
|
||||||
|
setFetchInterval(newInterval)
|
||||||
|
}, [fetchInterval, fetchAllData, refreshInterval])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
clearInterval(liquidityFetchInterval)
|
clearInterval(fetchInterval)
|
||||||
}
|
}
|
||||||
}, [liquidityFetchInterval])
|
}, [fetchInterval])
|
||||||
|
|
||||||
|
//
|
||||||
|
// 0 Fetch all the data on mount
|
||||||
|
// All further effects depend on the fetched data
|
||||||
|
// and only do further data checking and manipulation.
|
||||||
|
//
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function init() {
|
fetchAllData()
|
||||||
if (!dataLiquidity?.pool) {
|
initFetchInterval()
|
||||||
await getPoolLiquidity()
|
}, [fetchAllData, initFetchInterval])
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set symbols
|
//
|
||||||
setOceanSymbol(dataLiquidity.pool.baseToken.symbol)
|
// 1 General Pool Info
|
||||||
setDtSymbol(dataLiquidity.pool.datatoken.symbol)
|
//
|
||||||
|
useEffect(() => {
|
||||||
|
if (!poolData || !price?.ocean || !price?.datatoken) return
|
||||||
|
|
||||||
// Total pool shares
|
// Pool Fee (swap fee)
|
||||||
const totalPoolTokens = dataLiquidity.pool.totalShares
|
// poolFee is tricky: to get 0.1% you need to convert from 0.001
|
||||||
setTotalPoolTokens(totalPoolTokens)
|
const poolFee = isValidNumber(poolData.poolFee)
|
||||||
|
? new Decimal(poolData.poolFee).mul(100).toString()
|
||||||
|
: '0'
|
||||||
|
|
||||||
// Get poolFee
|
// Total Liquidity
|
||||||
// poolFee is tricky: to get 0.1% you need to convert from 0.001
|
const totalLiquidityInOcean =
|
||||||
const swapFee = isValidNumber(dataLiquidity.pool.poolFee)
|
isValidNumber(price.ocean) &&
|
||||||
? new Decimal(dataLiquidity.pool.poolFee).mul(100).toString()
|
isValidNumber(price.datatoken) &&
|
||||||
|
isValidNumber(poolData.spotPrice)
|
||||||
|
? new Decimal(price.ocean).add(
|
||||||
|
new Decimal(price.datatoken).mul(poolData.spotPrice)
|
||||||
|
)
|
||||||
|
: new Decimal(0)
|
||||||
|
|
||||||
|
const newPoolInfo = {
|
||||||
|
poolFee,
|
||||||
|
weightOcean: getWeight(poolData.baseTokenWeight),
|
||||||
|
weightDt: getWeight(poolData.datatokenWeight),
|
||||||
|
dtSymbol: poolData.datatoken.symbol,
|
||||||
|
oceanSymbol: poolData.baseToken.symbol,
|
||||||
|
totalPoolTokens: poolData.totalShares,
|
||||||
|
totalLiquidityInOcean
|
||||||
|
}
|
||||||
|
setPoolInfo(newPoolInfo)
|
||||||
|
LoggerInstance.log('[pool] Created new pool info:', newPoolInfo)
|
||||||
|
}, [poolData, price?.datatoken, price?.ocean])
|
||||||
|
|
||||||
|
//
|
||||||
|
// 2 Pool Creator Info
|
||||||
|
//
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
!poolData ||
|
||||||
|
!poolInfo?.totalPoolTokens ||
|
||||||
|
!price?.ocean ||
|
||||||
|
!price?.datatoken
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
const ownerPoolTokens = poolData.shares[0]?.shares
|
||||||
|
|
||||||
|
const ownerOceanBalance =
|
||||||
|
isValidNumber(ownerPoolTokens) &&
|
||||||
|
isValidNumber(poolInfo.totalPoolTokens) &&
|
||||||
|
isValidNumber(price.ocean)
|
||||||
|
? new Decimal(ownerPoolTokens)
|
||||||
|
.dividedBy(new Decimal(poolInfo.totalPoolTokens))
|
||||||
|
.mul(price.ocean)
|
||||||
|
.toString()
|
||||||
: '0'
|
: '0'
|
||||||
setSwapFee(swapFee)
|
|
||||||
|
|
||||||
// Get weights
|
const ownerDtBalance =
|
||||||
function getWeight(weight: string) {
|
isValidNumber(ownerPoolTokens) &&
|
||||||
return isValidNumber(weight)
|
isValidNumber(poolInfo.totalPoolTokens) &&
|
||||||
? new Decimal(weight).mul(10).toString()
|
isValidNumber(price.datatoken)
|
||||||
: '0'
|
? new Decimal(ownerPoolTokens)
|
||||||
}
|
.dividedBy(new Decimal(poolInfo.totalPoolTokens))
|
||||||
const weightDt = dataLiquidity.pool.datatokenWeight
|
.mul(price.datatoken)
|
||||||
const weightDtDecimal = getWeight(weightDt)
|
.toString()
|
||||||
setWeightDt(weightDtDecimal)
|
: '0'
|
||||||
|
|
||||||
const weightOcean = dataLiquidity.pool.baseTokenWeight
|
const liquidity = {
|
||||||
const weightOceanDecimal = getWeight(weightOcean)
|
ocean: ownerOceanBalance,
|
||||||
setWeightOcean(weightOceanDecimal)
|
datatoken: ownerDtBalance
|
||||||
|
|
||||||
//
|
|
||||||
// Get everything the creator put into the pool
|
|
||||||
//
|
|
||||||
const creatorPoolTokens = dataLiquidity.pool.shares[0]?.shares
|
|
||||||
setCreatorPoolTokens(creatorPoolTokens)
|
|
||||||
|
|
||||||
const creatorOceanBalance =
|
|
||||||
isValidNumber(creatorPoolTokens) &&
|
|
||||||
isValidNumber(totalPoolTokens) &&
|
|
||||||
isValidNumber(price.ocean)
|
|
||||||
? new Decimal(creatorPoolTokens)
|
|
||||||
.dividedBy(new Decimal(totalPoolTokens))
|
|
||||||
.mul(price.ocean)
|
|
||||||
.toString()
|
|
||||||
: '0'
|
|
||||||
|
|
||||||
const creatorDtBalance =
|
|
||||||
isValidNumber(creatorPoolTokens) &&
|
|
||||||
isValidNumber(totalPoolTokens) &&
|
|
||||||
isValidNumber(price.datatoken)
|
|
||||||
? new Decimal(creatorPoolTokens)
|
|
||||||
.dividedBy(new Decimal(totalPoolTokens))
|
|
||||||
.mul(price.datatoken)
|
|
||||||
.toString()
|
|
||||||
: '0'
|
|
||||||
|
|
||||||
const creatorLiquidity = {
|
|
||||||
ocean: creatorOceanBalance,
|
|
||||||
datatoken: creatorDtBalance
|
|
||||||
}
|
|
||||||
setCreatorLiquidity(creatorLiquidity)
|
|
||||||
|
|
||||||
const totalCreatorLiquidityInOcean =
|
|
||||||
isValidNumber(creatorLiquidity?.ocean) &&
|
|
||||||
isValidNumber(creatorLiquidity?.datatoken) &&
|
|
||||||
isValidNumber(dataLiquidity.pool.spotPrice)
|
|
||||||
? new Decimal(creatorLiquidity?.ocean).add(
|
|
||||||
new Decimal(creatorLiquidity?.datatoken).mul(
|
|
||||||
new Decimal(dataLiquidity.pool.spotPrice)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: new Decimal(0)
|
|
||||||
|
|
||||||
setCreatorTotalLiquidityInOcean(totalCreatorLiquidityInOcean)
|
|
||||||
|
|
||||||
const creatorPoolShare =
|
|
||||||
price?.ocean &&
|
|
||||||
price?.datatoken &&
|
|
||||||
creatorLiquidity &&
|
|
||||||
isValidNumber(creatorPoolTokens) &&
|
|
||||||
isValidNumber(totalPoolTokens)
|
|
||||||
? new Decimal(creatorPoolTokens)
|
|
||||||
.dividedBy(new Decimal(totalPoolTokens))
|
|
||||||
.mul(100)
|
|
||||||
.toFixed(2)
|
|
||||||
: '0'
|
|
||||||
|
|
||||||
setCreatorPoolShare(creatorPoolShare)
|
|
||||||
refetchLiquidity()
|
|
||||||
}
|
}
|
||||||
init()
|
|
||||||
}, [
|
|
||||||
dataLiquidity,
|
|
||||||
price?.datatoken,
|
|
||||||
price?.ocean,
|
|
||||||
getPoolLiquidity,
|
|
||||||
refetchLiquidity
|
|
||||||
])
|
|
||||||
|
|
||||||
useEffect(() => {
|
const totalLiquidityInOcean =
|
||||||
setIsRemoveDisabled(isInPurgatory && owner === accountId)
|
isValidNumber(liquidity.ocean) &&
|
||||||
}, [isInPurgatory, owner, accountId])
|
isValidNumber(liquidity.datatoken) &&
|
||||||
|
isValidNumber(poolData.spotPrice)
|
||||||
useEffect(() => {
|
? new Decimal(liquidity.ocean).add(
|
||||||
if (!dataLiquidity) return
|
new Decimal(liquidity.datatoken).mul(
|
||||||
|
new Decimal(poolData.spotPrice)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: new Decimal(0)
|
||||||
|
|
||||||
const poolShare =
|
const poolShare =
|
||||||
isValidNumber(poolTokens) &&
|
liquidity &&
|
||||||
isValidNumber(totalPoolTokens) &&
|
isValidNumber(ownerPoolTokens) &&
|
||||||
price?.ocean &&
|
isValidNumber(poolInfo.totalPoolTokens)
|
||||||
price?.datatoken &&
|
? new Decimal(ownerPoolTokens)
|
||||||
new Decimal(poolTokens)
|
.dividedBy(new Decimal(poolInfo.totalPoolTokens))
|
||||||
.dividedBy(new Decimal(totalPoolTokens))
|
.mul(100)
|
||||||
|
.toFixed(2)
|
||||||
|
: '0'
|
||||||
|
|
||||||
|
const newPoolOwnerInfo = {
|
||||||
|
totalLiquidityInOcean,
|
||||||
|
liquidity,
|
||||||
|
poolShares: ownerPoolTokens,
|
||||||
|
poolShare
|
||||||
|
}
|
||||||
|
setPoolInfoOwner(newPoolOwnerInfo)
|
||||||
|
LoggerInstance.log('[pool] Created new owner pool info:', newPoolOwnerInfo)
|
||||||
|
}, [poolData, price?.ocean, price?.datatoken, poolInfo?.totalPoolTokens])
|
||||||
|
|
||||||
|
//
|
||||||
|
// 3 User Pool Info
|
||||||
|
//
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
!poolData?.spotPrice ||
|
||||||
|
!poolInfo?.totalPoolTokens ||
|
||||||
|
!ddo?.chainId ||
|
||||||
|
!accountId ||
|
||||||
|
!price?.ocean ||
|
||||||
|
!price?.datatoken
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
const poolShare =
|
||||||
|
isValidNumber(poolInfoUser.poolShares) &&
|
||||||
|
isValidNumber(poolInfo.totalPoolTokens) &&
|
||||||
|
new Decimal(poolInfoUser.poolShares)
|
||||||
|
.dividedBy(new Decimal(poolInfo.totalPoolTokens))
|
||||||
.mul(100)
|
.mul(100)
|
||||||
.toFixed(5)
|
.toFixed(5)
|
||||||
|
|
||||||
setPoolShare(poolShare)
|
setUserHasAddedLiquidity(Number(poolShare) > 0)
|
||||||
setHasAddedLiquidity(Number(poolShare) > 0)
|
|
||||||
|
|
||||||
const totalUserLiquidityInOcean =
|
// calculate user's provided liquidity based on pool tokens
|
||||||
isValidNumber(userLiquidity?.ocean) &&
|
const userOceanBalance =
|
||||||
isValidNumber(userLiquidity?.datatoken) &&
|
isValidNumber(poolInfoUser.poolShares) &&
|
||||||
isValidNumber(dataLiquidity.pool.spotPrice)
|
isValidNumber(poolInfo.totalPoolTokens) &&
|
||||||
? new Decimal(userLiquidity?.ocean).add(
|
isValidNumber(price.ocean)
|
||||||
new Decimal(userLiquidity?.datatoken).mul(
|
? new Decimal(poolInfoUser.poolShares)
|
||||||
dataLiquidity.pool.spotPrice
|
.dividedBy(new Decimal(poolInfo.totalPoolTokens))
|
||||||
)
|
.mul(price.ocean)
|
||||||
)
|
.toString()
|
||||||
: new Decimal(0)
|
: '0'
|
||||||
|
|
||||||
setTotalUserLiquidityInOcean(totalUserLiquidityInOcean)
|
const userDtBalance =
|
||||||
|
isValidNumber(poolInfoUser.poolShares) &&
|
||||||
|
isValidNumber(poolInfo.totalPoolTokens) &&
|
||||||
|
isValidNumber(price.datatoken)
|
||||||
|
? new Decimal(poolInfoUser.poolShares)
|
||||||
|
.dividedBy(new Decimal(poolInfo.totalPoolTokens))
|
||||||
|
.mul(price.datatoken)
|
||||||
|
.toString()
|
||||||
|
: '0'
|
||||||
|
|
||||||
|
const liquidity = {
|
||||||
|
ocean: userOceanBalance,
|
||||||
|
datatoken: userDtBalance
|
||||||
|
}
|
||||||
|
|
||||||
const totalLiquidityInOcean =
|
const totalLiquidityInOcean =
|
||||||
isValidNumber(price?.ocean) &&
|
isValidNumber(liquidity.ocean) &&
|
||||||
isValidNumber(price?.datatoken) &&
|
isValidNumber(liquidity.datatoken) &&
|
||||||
isValidNumber(dataLiquidity.pool.spotPrice)
|
isValidNumber(poolData.spotPrice)
|
||||||
? new Decimal(price?.ocean).add(
|
? new Decimal(liquidity.ocean).add(
|
||||||
new Decimal(price?.datatoken).mul(dataLiquidity.pool.spotPrice)
|
new Decimal(liquidity.datatoken).mul(poolData.spotPrice)
|
||||||
)
|
)
|
||||||
: new Decimal(0)
|
: new Decimal(0)
|
||||||
|
|
||||||
setTotalLiquidityInOcean(totalLiquidityInOcean)
|
const newPoolInfoUser = {
|
||||||
}, [dataLiquidity, userLiquidity, price, poolTokens, totalPoolTokens])
|
totalLiquidityInOcean,
|
||||||
|
liquidity,
|
||||||
useEffect(() => {
|
poolShare
|
||||||
if (!accountId || !price) return
|
|
||||||
|
|
||||||
async function init() {
|
|
||||||
try {
|
|
||||||
//
|
|
||||||
// Get everything the user has put into the pool
|
|
||||||
//
|
|
||||||
const poolTokens = await getUserPoolShareBalance()
|
|
||||||
setPoolTokens(poolTokens)
|
|
||||||
|
|
||||||
// calculate user's provided liquidity based on pool tokens
|
|
||||||
const userOceanBalance =
|
|
||||||
isValidNumber(poolTokens) &&
|
|
||||||
isValidNumber(totalPoolTokens) &&
|
|
||||||
isValidNumber(price.ocean)
|
|
||||||
? new Decimal(poolTokens)
|
|
||||||
.dividedBy(new Decimal(totalPoolTokens))
|
|
||||||
.mul(price.ocean)
|
|
||||||
.toString()
|
|
||||||
: '0'
|
|
||||||
|
|
||||||
const userDtBalance =
|
|
||||||
isValidNumber(poolTokens) &&
|
|
||||||
isValidNumber(totalPoolTokens) &&
|
|
||||||
isValidNumber(price.datatoken)
|
|
||||||
? new Decimal(poolTokens)
|
|
||||||
.dividedBy(new Decimal(totalPoolTokens))
|
|
||||||
.mul(price.datatoken)
|
|
||||||
.toString()
|
|
||||||
: '0'
|
|
||||||
|
|
||||||
const userLiquidity = {
|
|
||||||
ocean: userOceanBalance,
|
|
||||||
datatoken: userDtBalance
|
|
||||||
}
|
|
||||||
|
|
||||||
setUserLiquidity(userLiquidity)
|
|
||||||
} catch (error) {
|
|
||||||
LoggerInstance.error(error.message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
init()
|
setPoolInfoUser((prevState: PoolInfoUser) => ({
|
||||||
}, [accountId, price, ddo, refreshPool, owner, totalPoolTokens])
|
...prevState,
|
||||||
|
...newPoolInfoUser
|
||||||
|
}))
|
||||||
|
|
||||||
const refreshInfo = async () => {
|
LoggerInstance.log('[pool] Created new user pool info:', {
|
||||||
setRefreshPool(!refreshPool)
|
poolShares: poolInfoUser?.poolShares,
|
||||||
|
...newPoolInfoUser
|
||||||
|
})
|
||||||
|
}, [
|
||||||
|
poolData?.spotPrice,
|
||||||
|
poolInfoUser?.poolShares,
|
||||||
|
accountId,
|
||||||
|
price,
|
||||||
|
ddo?.chainId,
|
||||||
|
owner,
|
||||||
|
poolInfo?.totalPoolTokens
|
||||||
|
])
|
||||||
|
|
||||||
// need some form of replacement or something.
|
//
|
||||||
// await refreshPrice()
|
// Check if removing liquidity should be disabled.
|
||||||
}
|
//
|
||||||
|
useEffect(() => {
|
||||||
|
if (!owner || !accountId) return
|
||||||
|
|
||||||
|
setIsRemoveDisabled(isInPurgatory && owner === accountId)
|
||||||
|
}, [isInPurgatory, owner, accountId])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{showAdd ? (
|
{showAdd ? (
|
||||||
<Add
|
<Add
|
||||||
setShowAdd={setShowAdd}
|
setShowAdd={setShowAdd}
|
||||||
refreshInfo={refreshInfo}
|
poolAddress={price?.address}
|
||||||
poolAddress={price.address}
|
totalPoolTokens={poolInfo.totalPoolTokens}
|
||||||
totalPoolTokens={totalPoolTokens}
|
|
||||||
totalBalance={{
|
totalBalance={{
|
||||||
ocean: new Decimal(price.ocean).toString(),
|
ocean: new Decimal(price?.ocean).toString(),
|
||||||
datatoken: new Decimal(price.datatoken).toString()
|
datatoken: new Decimal(price?.datatoken).toString()
|
||||||
}}
|
}}
|
||||||
swapFee={swapFee}
|
swapFee={poolInfo.poolFee}
|
||||||
dtSymbol={dtSymbol}
|
dtSymbol={poolInfo.dtSymbol}
|
||||||
dtAddress={ddo.services[0].datatokenAddress}
|
dtAddress={ddo?.services[0].datatokenAddress}
|
||||||
|
fetchAllData={fetchAllData}
|
||||||
/>
|
/>
|
||||||
) : showRemove ? (
|
) : showRemove ? (
|
||||||
<Remove
|
<Remove
|
||||||
setShowRemove={setShowRemove}
|
setShowRemove={setShowRemove}
|
||||||
refreshInfo={refreshInfo}
|
poolAddress={price?.address}
|
||||||
poolAddress={price.address}
|
poolTokens={poolInfoUser.poolShares}
|
||||||
poolTokens={poolTokens}
|
totalPoolTokens={poolInfo?.totalPoolTokens}
|
||||||
totalPoolTokens={totalPoolTokens}
|
dtSymbol={poolInfo?.dtSymbol}
|
||||||
dtSymbol={dtSymbol}
|
fetchAllData={fetchAllData}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className={styles.dataToken}>
|
<div className={styles.dataToken}>
|
||||||
<PriceUnit price="1" symbol={dtSymbol} /> ={' '}
|
<PriceUnit price="1" symbol={poolInfo?.dtSymbol} /> ={' '}
|
||||||
<PriceUnit price={`${price?.value}`} symbol={oceanSymbol} />
|
<PriceUnit
|
||||||
|
price={`${price?.value}`}
|
||||||
|
symbol={poolInfo?.oceanSymbol}
|
||||||
|
/>
|
||||||
<Tooltip content={content.pool.tooltips.price} />
|
<Tooltip content={content.pool.tooltips.price} />
|
||||||
<div className={styles.dataTokenLinks}>
|
<div className={styles.dataTokenLinks}>
|
||||||
<ExplorerLink
|
<ExplorerLink
|
||||||
networkId={ddo.chainId}
|
networkId={ddo?.chainId}
|
||||||
path={`address/${price?.address}`}
|
path={`address/${price?.address}`}
|
||||||
>
|
>
|
||||||
Pool
|
Pool
|
||||||
</ExplorerLink>
|
</ExplorerLink>
|
||||||
<ExplorerLink
|
<ExplorerLink
|
||||||
networkId={ddo.chainId}
|
networkId={ddo?.chainId}
|
||||||
path={
|
path={
|
||||||
ddo.chainId === 2021000 || ddo.chainId === 1287
|
ddo?.chainId === 2021000 || ddo?.chainId === 1287
|
||||||
? `tokens/${ddo.services[0].datatokenAddress}`
|
? `tokens/${ddo.services[0].datatokenAddress}`
|
||||||
: `token/${ddo.services[0].datatokenAddress}`
|
: `token/${ddo.services[0].datatokenAddress}`
|
||||||
}
|
}
|
||||||
@ -403,58 +392,66 @@ export default function Pool(): ReactElement {
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
content={content.pool.tooltips.liquidity.replace(
|
content={content.pool.tooltips.liquidity.replace(
|
||||||
'SWAPFEE',
|
'SWAPFEE',
|
||||||
swapFee
|
poolInfo?.poolFee
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
ocean={`${userLiquidity?.ocean}`}
|
ocean={`${poolInfoUser?.liquidity?.ocean}`}
|
||||||
oceanSymbol={oceanSymbol}
|
oceanSymbol={poolInfo?.oceanSymbol}
|
||||||
dt={`${userLiquidity?.datatoken}`}
|
dt={`${poolInfoUser?.liquidity?.datatoken}`}
|
||||||
dtSymbol={dtSymbol}
|
dtSymbol={poolInfo?.dtSymbol}
|
||||||
poolShares={poolTokens}
|
poolShares={poolInfoUser?.poolShares}
|
||||||
conversion={totalUserLiquidityInOcean}
|
conversion={poolInfoUser?.totalLiquidityInOcean}
|
||||||
highlight
|
highlight
|
||||||
>
|
>
|
||||||
<Token symbol="% of pool" balance={poolShare} noIcon />
|
<Token
|
||||||
|
symbol="% of pool"
|
||||||
|
balance={poolInfoUser?.poolShare}
|
||||||
|
noIcon
|
||||||
|
/>
|
||||||
</TokenList>
|
</TokenList>
|
||||||
|
|
||||||
<TokenList
|
<TokenList
|
||||||
title="Pool Creator Statistics"
|
title="Pool Creator Statistics"
|
||||||
ocean={`${creatorLiquidity?.ocean}`}
|
ocean={`${poolInfoOwner?.liquidity?.ocean}`}
|
||||||
oceanSymbol={oceanSymbol}
|
oceanSymbol={poolInfo?.oceanSymbol}
|
||||||
dt={`${creatorLiquidity?.datatoken}`}
|
dt={`${poolInfoOwner?.liquidity?.datatoken}`}
|
||||||
dtSymbol={dtSymbol}
|
dtSymbol={poolInfo?.dtSymbol}
|
||||||
poolShares={creatorPoolTokens}
|
poolShares={poolInfoOwner?.poolShares}
|
||||||
conversion={creatorTotalLiquidityInOcean}
|
conversion={poolInfoOwner?.totalLiquidityInOcean}
|
||||||
>
|
>
|
||||||
<Token symbol="% of pool" balance={creatorPoolShare} noIcon />
|
<Token
|
||||||
|
symbol="% of pool"
|
||||||
|
balance={poolInfoOwner?.poolShare}
|
||||||
|
noIcon
|
||||||
|
/>
|
||||||
</TokenList>
|
</TokenList>
|
||||||
|
|
||||||
<TokenList
|
<TokenList
|
||||||
title={
|
title={
|
||||||
<>
|
<>
|
||||||
Pool Statistics
|
Pool Statistics
|
||||||
{weightDt && (
|
{poolInfo?.weightDt && (
|
||||||
<span
|
<span
|
||||||
className={styles.titleInfo}
|
className={styles.titleInfo}
|
||||||
title={`Weight of ${weightOcean}% OCEAN & ${weightDt}% ${dtSymbol}`}
|
title={`Weight of ${poolInfo?.weightOcean}% OCEAN & ${poolInfo?.weightDt}% ${poolInfo?.dtSymbol}`}
|
||||||
>
|
>
|
||||||
{weightOcean}/{weightDt}
|
{poolInfo?.weightOcean}/{poolInfo?.weightDt}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<Graph />
|
<Graph />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
ocean={`${price?.ocean}`}
|
ocean={`${price?.ocean}`}
|
||||||
oceanSymbol={oceanSymbol}
|
oceanSymbol={poolInfo?.oceanSymbol}
|
||||||
dt={`${price?.datatoken}`}
|
dt={`${price?.datatoken}`}
|
||||||
dtSymbol={dtSymbol}
|
dtSymbol={poolInfo?.dtSymbol}
|
||||||
poolShares={totalPoolTokens}
|
poolShares={poolInfo?.totalPoolTokens}
|
||||||
conversion={totalLiquidityInOcean}
|
conversion={poolInfo?.totalLiquidityInOcean}
|
||||||
showTVLLabel
|
showTVLLabel
|
||||||
>
|
>
|
||||||
<Token symbol="% swap fee" balance={swapFee} noIcon />
|
<Token symbol="% pool fee" balance={poolInfo?.poolFee} noIcon />
|
||||||
</TokenList>
|
</TokenList>
|
||||||
|
|
||||||
<div className={styles.update}>
|
<div className={styles.update}>
|
||||||
@ -471,7 +468,7 @@ export default function Pool(): ReactElement {
|
|||||||
Add Liquidity
|
Add Liquidity
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{hasAddedLiquidity && !isRemoveDisabled && (
|
{hasUserAddedLiquidity && !isRemoveDisabled && (
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => setShowRemove(true)}
|
onClick={() => setShowRemove(true)}
|
||||||
@ -487,7 +484,7 @@ export default function Pool(): ReactElement {
|
|||||||
<PoolTransactions
|
<PoolTransactions
|
||||||
accountId={accountId}
|
accountId={accountId}
|
||||||
poolAddress={price?.address}
|
poolAddress={price?.address}
|
||||||
poolChainId={[ddo.chainId]}
|
poolChainId={[ddo?.chainId]}
|
||||||
minimal
|
minimal
|
||||||
/>
|
/>
|
||||||
</AssetActionHistoryTable>
|
</AssetActionHistoryTable>
|
||||||
|
Loading…
Reference in New Issue
Block a user