mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Merge branch 'v4' into feature/v4-c2d
This commit is contained in:
commit
6e2e9875f1
8397
package-lock.json
generated
8397
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -21,7 +21,7 @@
|
|||||||
"@coingecko/cryptoformat": "^0.4.4",
|
"@coingecko/cryptoformat": "^0.4.4",
|
||||||
"@loadable/component": "^5.15.2",
|
"@loadable/component": "^5.15.2",
|
||||||
"@oceanprotocol/art": "^3.2.0",
|
"@oceanprotocol/art": "^3.2.0",
|
||||||
"@oceanprotocol/lib": "^1.0.0-next.32",
|
"@oceanprotocol/lib": "^1.0.0-next.33",
|
||||||
"@oceanprotocol/typographies": "^0.1.0",
|
"@oceanprotocol/typographies": "^0.1.0",
|
||||||
"@portis/web3": "^4.0.7",
|
"@portis/web3": "^4.0.7",
|
||||||
"@tippyjs/react": "^4.2.6",
|
"@tippyjs/react": "^4.2.6",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
import { LoggerInstance, Pool } from '@oceanprotocol/lib'
|
||||||
import { isValidNumber } from '@utils/numbers'
|
import { isValidNumber } from '@utils/numbers'
|
||||||
import Decimal from 'decimal.js'
|
import Decimal from 'decimal.js'
|
||||||
import React, {
|
import React, {
|
||||||
@ -16,6 +16,7 @@ import {
|
|||||||
} from 'src/@types/subgraph/PoolData'
|
} from 'src/@types/subgraph/PoolData'
|
||||||
import { useAsset } from '../Asset'
|
import { useAsset } from '../Asset'
|
||||||
import { useWeb3 } from '../Web3'
|
import { useWeb3 } from '../Web3'
|
||||||
|
import { calculateSharesVL } from '@utils/pool'
|
||||||
import { PoolProviderValue, PoolInfo, PoolInfoUser } from './_types'
|
import { PoolProviderValue, PoolInfo, PoolInfoUser } from './_types'
|
||||||
import { getFee, getPoolData, getWeight } from './_utils'
|
import { getFee, getPoolData, getWeight } from './_utils'
|
||||||
|
|
||||||
@ -37,7 +38,7 @@ const initialPoolInfoUser: Partial<PoolInfoUser> = {
|
|||||||
const initialPoolInfoCreator: Partial<PoolInfoUser> = initialPoolInfoUser
|
const initialPoolInfoCreator: Partial<PoolInfoUser> = initialPoolInfoUser
|
||||||
|
|
||||||
function PoolProvider({ children }: { children: ReactNode }): ReactElement {
|
function PoolProvider({ children }: { children: ReactNode }): ReactElement {
|
||||||
const { accountId } = useWeb3()
|
const { accountId, web3, chainId } = useWeb3()
|
||||||
const { isInPurgatory, asset, owner } = useAsset()
|
const { isInPurgatory, asset, owner } = useAsset()
|
||||||
|
|
||||||
const [poolData, setPoolData] = useState<PoolDataPoolData>()
|
const [poolData, setPoolData] = useState<PoolDataPoolData>()
|
||||||
@ -54,6 +55,8 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
|
|||||||
const [hasUserAddedLiquidity, setUserHasAddedLiquidity] = useState(false)
|
const [hasUserAddedLiquidity, setUserHasAddedLiquidity] = useState(false)
|
||||||
const [isRemoveDisabled, setIsRemoveDisabled] = useState(false)
|
const [isRemoveDisabled, setIsRemoveDisabled] = useState(false)
|
||||||
// const [fetchInterval, setFetchInterval] = useState<NodeJS.Timeout>()
|
// const [fetchInterval, setFetchInterval] = useState<NodeJS.Timeout>()
|
||||||
|
const [ownerPoolShares, setOwnerPoolShares] = useState('0')
|
||||||
|
const [userPoolShares, setUserPoolShares] = useState('0')
|
||||||
|
|
||||||
const fetchAllData = useCallback(async () => {
|
const fetchAllData = useCallback(async () => {
|
||||||
if (
|
if (
|
||||||
@ -84,30 +87,6 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
|
|||||||
LoggerInstance.log('[pool] Fetched pool snapshots:', response.poolSnapshots)
|
LoggerInstance.log('[pool] Fetched pool snapshots:', response.poolSnapshots)
|
||||||
}, [asset?.chainId, asset?.accessDetails?.addressOrId, owner, accountId])
|
}, [asset?.chainId, asset?.accessDetails?.addressOrId, owner, accountId])
|
||||||
|
|
||||||
// Helper: start interval fetching
|
|
||||||
// const initFetchInterval = useCallback(() => {
|
|
||||||
// if (fetchInterval) return
|
|
||||||
|
|
||||||
// const newInterval = setInterval(() => {
|
|
||||||
// fetchAllData()
|
|
||||||
// LoggerInstance.log(
|
|
||||||
// `[pool] Refetch interval fired after ${refreshInterval / 1000}s`
|
|
||||||
// )
|
|
||||||
// }, refreshInterval)
|
|
||||||
// setFetchInterval(newInterval)
|
|
||||||
|
|
||||||
// // Having `accountId` as dependency is important for interval to
|
|
||||||
// // change after user account switch.
|
|
||||||
// // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
// }, [fetchInterval, fetchAllData, accountId])
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// return () => {
|
|
||||||
// clearInterval(fetchInterval)
|
|
||||||
// }
|
|
||||||
// }, [fetchInterval])
|
|
||||||
|
|
||||||
//
|
|
||||||
// 0 Fetch all the data on mount if we are on a pool.
|
// 0 Fetch all the data on mount if we are on a pool.
|
||||||
// All further effects depend on the fetched data
|
// All further effects depend on the fetched data
|
||||||
// and only do further data checking and manipulation.
|
// and only do further data checking and manipulation.
|
||||||
@ -116,7 +95,6 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
|
|||||||
if (asset?.accessDetails?.type !== 'dynamic') return
|
if (asset?.accessDetails?.type !== 'dynamic') return
|
||||||
|
|
||||||
fetchAllData()
|
fetchAllData()
|
||||||
// initFetchInterval()
|
|
||||||
}, [fetchAllData, asset?.accessDetails?.type])
|
}, [fetchAllData, asset?.accessDetails?.type])
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -125,12 +103,19 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!poolData) return
|
if (!poolData) return
|
||||||
|
|
||||||
|
// once we have poolData, we need to get owner's pool shares (OVL)
|
||||||
|
calculateSharesVL(
|
||||||
|
poolData.id,
|
||||||
|
poolData.baseToken.address,
|
||||||
|
poolData.shares[0].shares,
|
||||||
|
asset.chainId
|
||||||
|
).then((shares) => {
|
||||||
|
setOwnerPoolShares(shares)
|
||||||
|
})
|
||||||
// Total Liquidity
|
// Total Liquidity
|
||||||
const totalLiquidityInOcean = isValidNumber(poolData.spotPrice)
|
const totalLiquidityInOcean = new Decimal(
|
||||||
? new Decimal(poolData.baseTokenLiquidity).add(
|
poolData.baseTokenLiquidity * 2 || 0
|
||||||
new Decimal(poolData.datatokenLiquidity).mul(poolData.spotPrice)
|
)
|
||||||
)
|
|
||||||
: new Decimal(0)
|
|
||||||
|
|
||||||
const newPoolInfo = {
|
const newPoolInfo = {
|
||||||
liquidityProviderSwapFee: getFee(poolData.liquidityProviderSwapFee),
|
liquidityProviderSwapFee: getFee(poolData.liquidityProviderSwapFee),
|
||||||
@ -145,48 +130,42 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
|
|||||||
totalPoolTokens: poolData.totalShares,
|
totalPoolTokens: poolData.totalShares,
|
||||||
totalLiquidityInOcean
|
totalLiquidityInOcean
|
||||||
}
|
}
|
||||||
|
|
||||||
setPoolInfo(newPoolInfo)
|
setPoolInfo(newPoolInfo)
|
||||||
LoggerInstance.log('[pool] Created new pool info:', newPoolInfo)
|
LoggerInstance.log('[pool] Created new pool info:', newPoolInfo)
|
||||||
}, [poolData])
|
}, [asset?.chainId, chainId, poolData, web3])
|
||||||
|
|
||||||
//
|
//
|
||||||
// 2 Pool Creator Info
|
// 2 Pool Creator Info
|
||||||
//
|
//
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!poolData || !poolInfo?.totalPoolTokens) return
|
if (
|
||||||
|
!poolData ||
|
||||||
// Staking bot receives half the pool shares so for display purposes
|
!poolInfo?.totalPoolTokens ||
|
||||||
// we can multiply by 2 as we have a hardcoded 50/50 pool weight.
|
!poolInfo.totalLiquidityInOcean ||
|
||||||
const ownerPoolShares = new Decimal(poolData.shares[0]?.shares)
|
ownerPoolShares === '0'
|
||||||
.mul(2)
|
)
|
||||||
.toString()
|
return
|
||||||
|
|
||||||
// Liquidity in base token, calculated from pool share tokens.
|
|
||||||
const liquidity =
|
|
||||||
isValidNumber(ownerPoolShares) &&
|
|
||||||
isValidNumber(poolInfo.totalPoolTokens) &&
|
|
||||||
isValidNumber(poolData.baseTokenLiquidity)
|
|
||||||
? new Decimal(ownerPoolShares)
|
|
||||||
.dividedBy(new Decimal(poolInfo.totalPoolTokens))
|
|
||||||
.mul(poolData.baseTokenLiquidity)
|
|
||||||
: new Decimal(0)
|
|
||||||
|
|
||||||
// Pool share tokens.
|
// Pool share tokens.
|
||||||
const poolShare =
|
const poolShare = new Decimal(ownerPoolShares)
|
||||||
isValidNumber(ownerPoolShares) && isValidNumber(poolInfo.totalPoolTokens)
|
.dividedBy(poolInfo.totalLiquidityInOcean)
|
||||||
? new Decimal(ownerPoolShares)
|
.mul(100)
|
||||||
.dividedBy(new Decimal(poolInfo.totalPoolTokens))
|
.toFixed(2)
|
||||||
.mul(100)
|
|
||||||
.toFixed(2)
|
|
||||||
: '0'
|
|
||||||
const newPoolOwnerInfo = {
|
const newPoolOwnerInfo = {
|
||||||
liquidity,
|
liquidity: new Decimal(ownerPoolShares), // liquidity in base token, values from from `calcSingleOutGivenPoolIn` method
|
||||||
poolShares: ownerPoolShares,
|
poolShares: ownerPoolShares,
|
||||||
poolShare
|
poolShare
|
||||||
}
|
}
|
||||||
setPoolInfoOwner(newPoolOwnerInfo)
|
setPoolInfoOwner(newPoolOwnerInfo)
|
||||||
LoggerInstance.log('[pool] Created new owner pool info:', newPoolOwnerInfo)
|
LoggerInstance.log('[pool] Created new owner pool info:', newPoolOwnerInfo)
|
||||||
}, [poolData, poolInfo?.totalPoolTokens])
|
}, [
|
||||||
|
ownerPoolShares,
|
||||||
|
poolData,
|
||||||
|
poolInfo.totalLiquidityInOcean,
|
||||||
|
poolInfo.totalPoolTokens
|
||||||
|
])
|
||||||
|
|
||||||
//
|
//
|
||||||
// 3 User Pool Info
|
// 3 User Pool Info
|
||||||
@ -196,40 +175,34 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
|
|||||||
!poolData ||
|
!poolData ||
|
||||||
!poolInfo?.totalPoolTokens ||
|
!poolInfo?.totalPoolTokens ||
|
||||||
!poolInfoUser?.poolShares ||
|
!poolInfoUser?.poolShares ||
|
||||||
|
!poolInfo?.totalLiquidityInOcean ||
|
||||||
|
!poolData?.baseTokenLiquidity ||
|
||||||
!asset?.chainId ||
|
!asset?.chainId ||
|
||||||
!accountId ||
|
!accountId ||
|
||||||
!poolInfoUser
|
!poolInfoUser
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
// Staking bot receives half the pool shares so for display purposes
|
|
||||||
// we can multiply by 2 as we have a hardcoded 50/50 pool weight.
|
// once we have poolData, we need to get user's pool shares (VL)
|
||||||
const userPoolShares = new Decimal(poolInfoUser.poolShares || 0)
|
calculateSharesVL(
|
||||||
.mul(2)
|
poolData.id,
|
||||||
.toString()
|
poolData.baseToken.address,
|
||||||
|
poolInfoUser.poolShares,
|
||||||
|
asset.chainId
|
||||||
|
).then((shares) => {
|
||||||
|
setUserPoolShares(shares)
|
||||||
|
})
|
||||||
|
|
||||||
// Pool share in %.
|
// Pool share in %.
|
||||||
const poolShare =
|
const poolShare = new Decimal(userPoolShares)
|
||||||
isValidNumber(userPoolShares) &&
|
.dividedBy(new Decimal(poolInfo.totalLiquidityInOcean))
|
||||||
isValidNumber(poolInfo.totalPoolTokens) &&
|
.mul(100)
|
||||||
new Decimal(userPoolShares)
|
.toFixed(2)
|
||||||
.dividedBy(new Decimal(poolInfo.totalPoolTokens))
|
|
||||||
.mul(100)
|
|
||||||
.toFixed(2)
|
|
||||||
|
|
||||||
setUserHasAddedLiquidity(Number(poolShare) > 0)
|
setUserHasAddedLiquidity(Number(poolShare) > 0)
|
||||||
|
|
||||||
// Liquidity in base token, calculated from pool share tokens.
|
|
||||||
const liquidity =
|
|
||||||
isValidNumber(userPoolShares) &&
|
|
||||||
isValidNumber(poolInfo.totalPoolTokens) &&
|
|
||||||
isValidNumber(poolData.baseTokenLiquidity)
|
|
||||||
? new Decimal(userPoolShares)
|
|
||||||
.dividedBy(new Decimal(poolInfo.totalPoolTokens))
|
|
||||||
.mul(poolData.baseTokenLiquidity)
|
|
||||||
: new Decimal(0)
|
|
||||||
|
|
||||||
const newPoolInfoUser = {
|
const newPoolInfoUser = {
|
||||||
liquidity,
|
liquidity: new Decimal(userPoolShares), // liquidity in base token, values from from `calcSingleOutGivenPoolIn` method
|
||||||
poolShare
|
poolShare
|
||||||
}
|
}
|
||||||
setPoolInfoUser((prevState: PoolInfoUser) => ({
|
setPoolInfoUser((prevState: PoolInfoUser) => ({
|
||||||
@ -247,6 +220,7 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
|
|||||||
poolData,
|
poolData,
|
||||||
poolInfoUser?.poolShares,
|
poolInfoUser?.poolShares,
|
||||||
accountId,
|
accountId,
|
||||||
|
userPoolShares,
|
||||||
asset?.chainId,
|
asset?.chainId,
|
||||||
owner,
|
owner,
|
||||||
poolInfo?.totalPoolTokens
|
poolInfo?.totalPoolTokens
|
||||||
|
@ -5,7 +5,8 @@ import {
|
|||||||
Nft,
|
Nft,
|
||||||
ProviderInstance,
|
ProviderInstance,
|
||||||
DDO,
|
DDO,
|
||||||
MetadataAndTokenURI
|
MetadataAndTokenURI,
|
||||||
|
NftCreateData
|
||||||
} from '@oceanprotocol/lib'
|
} from '@oceanprotocol/lib'
|
||||||
import { SvgWaves } from './SvgWaves'
|
import { SvgWaves } from './SvgWaves'
|
||||||
import Web3 from 'web3'
|
import Web3 from 'web3'
|
||||||
@ -62,12 +63,18 @@ export function generateNftMetadata(): NftMetadata {
|
|||||||
|
|
||||||
const tokenUriPrefix = 'data:application/json;base64,'
|
const tokenUriPrefix = 'data:application/json;base64,'
|
||||||
|
|
||||||
export function generateNftCreateData(nftMetadata: NftMetadata): any {
|
export function generateNftCreateData(
|
||||||
const nftCreateData = {
|
nftMetadata: NftMetadata,
|
||||||
|
accountId: string,
|
||||||
|
transferable = true
|
||||||
|
): any {
|
||||||
|
const nftCreateData: NftCreateData = {
|
||||||
name: nftMetadata.name,
|
name: nftMetadata.name,
|
||||||
symbol: nftMetadata.symbol,
|
symbol: nftMetadata.symbol,
|
||||||
templateIndex: 1,
|
templateIndex: 1,
|
||||||
tokenURI: ''
|
tokenURI: '',
|
||||||
|
transferable,
|
||||||
|
owner: accountId
|
||||||
}
|
}
|
||||||
|
|
||||||
return nftCreateData
|
return nftCreateData
|
||||||
|
@ -114,3 +114,26 @@ export function calculateUserTVL(
|
|||||||
const tvl = new Decimal(liquidity).mul(2) // we multiply by 2 because of 50/50 weight
|
const tvl = new Decimal(liquidity).mul(2) // we multiply by 2 because of 50/50 weight
|
||||||
return tvl.toDecimalPlaces(MAX_DECIMALS).toString()
|
return tvl.toDecimalPlaces(MAX_DECIMALS).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function calculateSharesVL(
|
||||||
|
pool: string,
|
||||||
|
tokenAddress: string,
|
||||||
|
shares: string,
|
||||||
|
chainId?: number
|
||||||
|
): Promise<string> {
|
||||||
|
if (!chainId) throw new Error("chainId can't be undefined at the same time!")
|
||||||
|
|
||||||
|
// we only use the dummyWeb3 connection here
|
||||||
|
const web3 = await getDummyWeb3(chainId)
|
||||||
|
|
||||||
|
const poolInstance = new Pool(web3)
|
||||||
|
// get shares VL in ocean
|
||||||
|
const amountOcean = await poolInstance.calcSingleOutGivenPoolIn(
|
||||||
|
pool,
|
||||||
|
tokenAddress,
|
||||||
|
shares
|
||||||
|
)
|
||||||
|
|
||||||
|
const tvl = new Decimal(amountOcean || 0).mul(2) // we multiply by 2 because of 50/50 weight
|
||||||
|
return tvl.toDecimalPlaces(MAX_DECIMALS).toString()
|
||||||
|
}
|
||||||
|
@ -6,21 +6,6 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hasError {
|
|
||||||
border-color: var(--brand-alert-red);
|
|
||||||
background-color: var(--brand-white);
|
|
||||||
}
|
|
||||||
|
|
||||||
.url {
|
|
||||||
margin: 0;
|
|
||||||
font-size: var(--font-size-base);
|
|
||||||
line-height: var(--line-height);
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
word-wrap: break-word;
|
|
||||||
word-break: break-all;
|
|
||||||
padding-right: calc(var(--spacer) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.info ul {
|
.info ul {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
@ -32,6 +17,25 @@
|
|||||||
color: var(--color-secondary);
|
color: var(--color-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.info li.success {
|
||||||
|
color: var(--brand-alert-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.url {
|
||||||
|
margin: 0;
|
||||||
|
font-size: var(--font-size-base);
|
||||||
|
line-height: var(--line-height);
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-wrap: break-word;
|
||||||
|
word-break: break-all;
|
||||||
|
padding-right: calc(var(--spacer) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
margin-top: calc(var(--spacer) / 3);
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.removeButton {
|
.removeButton {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: none;
|
border: none;
|
||||||
@ -43,11 +47,3 @@
|
|||||||
color: var(--font-color-text);
|
color: var(--font-color-text);
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info li.success {
|
|
||||||
color: var(--brand-alert-green);
|
|
||||||
}
|
|
||||||
|
|
||||||
.info li.error {
|
|
||||||
color: var(--brand-alert-red);
|
|
||||||
}
|
|
||||||
|
@ -3,6 +3,7 @@ import { prettySize } from './utils'
|
|||||||
import cleanupContentType from '@utils/cleanupContentType'
|
import cleanupContentType from '@utils/cleanupContentType'
|
||||||
import styles from './Info.module.css'
|
import styles from './Info.module.css'
|
||||||
import { FileMetadata } from '@oceanprotocol/lib'
|
import { FileMetadata } from '@oceanprotocol/lib'
|
||||||
|
import Alert from '@shared/atoms/Alert'
|
||||||
|
|
||||||
export default function FileInfo({
|
export default function FileInfo({
|
||||||
file,
|
file,
|
||||||
@ -11,29 +12,30 @@ export default function FileInfo({
|
|||||||
file: FileMetadata
|
file: FileMetadata
|
||||||
handleClose(): void
|
handleClose(): void
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
return file.valid ? (
|
const contentTypeCleaned = file.contentType
|
||||||
|
? cleanupContentType(file.contentType)
|
||||||
|
: null
|
||||||
|
|
||||||
|
// Prevent accidential publishing of error pages (e.g. 404) for
|
||||||
|
// popular file hosting services by warning about it.
|
||||||
|
// See https://github.com/oceanprotocol/market/issues/1246
|
||||||
|
const shouldWarnAboutFile = file.valid && contentTypeCleaned === 'html'
|
||||||
|
|
||||||
|
return (
|
||||||
<div className={styles.info}>
|
<div className={styles.info}>
|
||||||
<h3 className={styles.url}>{file.url}</h3>
|
<h3 className={styles.url}>{file.url}</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li className={styles.success}>✓ URL confirmed</li>
|
<li className={styles.success}>✓ URL confirmed</li>
|
||||||
{file.contentLength && <li>{prettySize(+file.contentLength)}</li>}
|
{file.contentLength && <li>{prettySize(+file.contentLength)}</li>}
|
||||||
{file.contentType && <li>{cleanupContentType(file.contentType)}</li>}
|
{contentTypeCleaned && <li>{contentTypeCleaned}</li>}
|
||||||
</ul>
|
|
||||||
<button className={styles.removeButton} onClick={handleClose}>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className={`${styles.info} ${!file.valid ? styles.hasError : ''}`}>
|
|
||||||
<h3 className={styles.url}>{file.url}</h3>
|
|
||||||
<ul>
|
|
||||||
<li className={styles.error}>
|
|
||||||
{' '}
|
|
||||||
✗ No valid file detected. Check your URL and try again.
|
|
||||||
</li>
|
|
||||||
{file.contentLength && <li>{prettySize(+file.contentLength)}</li>}
|
|
||||||
{file.contentType && <li>{cleanupContentType(file.contentType)}</li>}
|
|
||||||
</ul>
|
</ul>
|
||||||
|
{shouldWarnAboutFile && (
|
||||||
|
<Alert
|
||||||
|
state="info"
|
||||||
|
text={`Your file was detected as ${contentTypeCleaned}, which is unusal for a data asset. If you did not intend to use a ${contentTypeCleaned} file, try a different URL pointing directly to your data asset file.`}
|
||||||
|
className={styles.warning}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<button className={styles.removeButton} onClick={handleClose}>
|
<button className={styles.removeButton} onClick={handleClose}>
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
|
@ -1,80 +1,59 @@
|
|||||||
import React, { ReactElement, useCallback, useEffect, useState } from 'react'
|
import React, { ReactElement, useState } from 'react'
|
||||||
import { useField, useFormikContext } from 'formik'
|
import { useField, useFormikContext } from 'formik'
|
||||||
import { toast } from 'react-toastify'
|
|
||||||
import FileInfo from './Info'
|
import FileInfo from './Info'
|
||||||
import UrlInput from '../URLInput'
|
import UrlInput from '../URLInput'
|
||||||
import { InputProps } from '@shared/FormInput'
|
import { InputProps } from '@shared/FormInput'
|
||||||
import { initialValues } from 'src/components/Publish/_constants'
|
|
||||||
import { getFileUrlInfo } from '@utils/provider'
|
import { getFileUrlInfo } from '@utils/provider'
|
||||||
import { FormPublishData } from 'src/components/Publish/_types'
|
import { FormPublishData } from 'src/components/Publish/_types'
|
||||||
|
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||||
|
|
||||||
export default function FilesInput(props: InputProps): ReactElement {
|
export default function FilesInput(props: InputProps): ReactElement {
|
||||||
const [field, meta, helpers] = useField(props.name)
|
const [field, meta, helpers] = useField(props.name)
|
||||||
const [isInvalidUrl, setIsInvalidUrl] = useState(false)
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const { values } = useFormikContext<FormPublishData>()
|
const { values, setFieldError } = useFormikContext<FormPublishData>()
|
||||||
|
|
||||||
const loadFileInfo = useCallback(
|
async function handleValidation(e: React.SyntheticEvent, url: string) {
|
||||||
(url: string) => {
|
|
||||||
const providerUri =
|
|
||||||
(values.services && values.services[0].providerUrl.url) ||
|
|
||||||
'https://provider.mainnet.oceanprotocol.com'
|
|
||||||
|
|
||||||
async function validateUrl() {
|
|
||||||
try {
|
|
||||||
setIsLoading(true)
|
|
||||||
const checkedFile = await getFileUrlInfo(url, providerUri)
|
|
||||||
setIsInvalidUrl(!checkedFile[0].valid)
|
|
||||||
checkedFile && helpers.setValue([{ url, ...checkedFile[0] }])
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(
|
|
||||||
'Could not fetch file info. Please check URL and try again'
|
|
||||||
)
|
|
||||||
console.error(error.message)
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
validateUrl()
|
|
||||||
},
|
|
||||||
[helpers, values.services]
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// try load from initial values, kinda hacky but it works
|
|
||||||
if (
|
|
||||||
props.value &&
|
|
||||||
props.value.length > 0 &&
|
|
||||||
typeof props.value[0] === 'string'
|
|
||||||
) {
|
|
||||||
loadFileInfo(props.value[0].toString())
|
|
||||||
}
|
|
||||||
}, [loadFileInfo, props])
|
|
||||||
|
|
||||||
async function handleButtonClick(e: React.SyntheticEvent, url: string) {
|
|
||||||
// File example 'https://oceanprotocol.com/tech-whitepaper.pdf'
|
// File example 'https://oceanprotocol.com/tech-whitepaper.pdf'
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
loadFileInfo(url)
|
|
||||||
|
try {
|
||||||
|
const providerUrl = values?.services[0].providerUrl.url
|
||||||
|
setIsLoading(true)
|
||||||
|
const checkedFile = await getFileUrlInfo(url, providerUrl)
|
||||||
|
|
||||||
|
// error if something's not right from response
|
||||||
|
if (!checkedFile)
|
||||||
|
throw Error('Could not fetch file info. Is your network down?')
|
||||||
|
|
||||||
|
if (checkedFile[0].valid === false)
|
||||||
|
throw Error('✗ No valid file detected. Check your URL and try again.')
|
||||||
|
|
||||||
|
// if all good, add file to formik state
|
||||||
|
helpers.setValue([{ url, ...checkedFile[0] }])
|
||||||
|
} catch (error) {
|
||||||
|
setFieldError(`${field.name}[0].url`, error.message)
|
||||||
|
LoggerInstance.error(error.message)
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
helpers.setValue(initialValues.services[0].files)
|
helpers.setValue(meta.initialValue)
|
||||||
helpers.setTouched(false)
|
helpers.setTouched(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{field?.value && field?.value[0]?.valid !== undefined ? (
|
{field?.value?.[0]?.valid === true ? (
|
||||||
<FileInfo file={field.value[0]} handleClose={handleClose} />
|
<FileInfo file={field.value[0]} handleClose={handleClose} />
|
||||||
) : (
|
) : (
|
||||||
<UrlInput
|
<UrlInput
|
||||||
submitText="Validate"
|
submitText="Validate"
|
||||||
{...props}
|
{...props}
|
||||||
name={`${field.name}[0].url`}
|
name={`${field.name}[0].url`}
|
||||||
hasError={Boolean(meta.touched && isInvalidUrl)}
|
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
handleButtonClick={handleButtonClick}
|
handleButtonClick={handleValidation}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -17,7 +17,7 @@ const getEstGasFee = async (
|
|||||||
return
|
return
|
||||||
|
|
||||||
const { web3 } = nftFactory
|
const { web3 } = nftFactory
|
||||||
const nft = generateNftCreateData(nftMetadata)
|
const nft = generateNftCreateData(nftMetadata, address)
|
||||||
|
|
||||||
const gasPrice = await web3.eth.getGasPrice()
|
const gasPrice = await web3.eth.getGasPrice()
|
||||||
const gasLimit = await nftFactory?.estGasCreateNFT(address, nft)
|
const gasLimit = await nftFactory?.estGasCreateNFT(address, nft)
|
||||||
|
@ -2,8 +2,14 @@
|
|||||||
composes: error from '@shared/FormInput/index.module.css';
|
composes: error from '@shared/FormInput/index.module.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.restore {
|
.default {
|
||||||
font-family: var(--font-family-base);
|
font-family: var(--font-family-base);
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
font-weight: var(--font-weight-base);
|
font-weight: var(--font-weight-base);
|
||||||
|
|
||||||
|
/* take it out of layout so error messages
|
||||||
|
are not pushed down */
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: -30%;
|
||||||
}
|
}
|
||||||
|
@ -1,52 +1,56 @@
|
|||||||
import React, { ReactElement, useState } from 'react'
|
import React, { ReactElement, useState } from 'react'
|
||||||
import { ErrorMessage, useField } from 'formik'
|
import { useField, useFormikContext } from 'formik'
|
||||||
import UrlInput from '../URLInput'
|
import UrlInput from '../URLInput'
|
||||||
import { InputProps } from '@shared/FormInput'
|
import { InputProps } from '@shared/FormInput'
|
||||||
import FileInfo from '../FilesInput/Info'
|
import FileInfo from '../FilesInput/Info'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import Button from '@shared/atoms/Button'
|
import Button from '@shared/atoms/Button'
|
||||||
import { initialValues } from 'src/components/Publish/_constants'
|
import { initialValues } from 'src/components/Publish/_constants'
|
||||||
import { ProviderInstance } from '@oceanprotocol/lib'
|
import { LoggerInstance, ProviderInstance } from '@oceanprotocol/lib'
|
||||||
|
import { FormPublishData } from 'src/components/Publish/_types'
|
||||||
|
|
||||||
export default function CustomProvider(props: InputProps): ReactElement {
|
export default function CustomProvider(props: InputProps): ReactElement {
|
||||||
const [field, meta, helpers] = useField(props.name)
|
const [field, meta, helpers] = useField(props.name)
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const { setFieldError } = useFormikContext<FormPublishData>()
|
||||||
|
|
||||||
async function validateProvider(url: string) {
|
async function handleValidation(e: React.SyntheticEvent) {
|
||||||
setIsLoading(true)
|
e.preventDefault()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const isValid = await ProviderInstance.isValidProvider(url)
|
setIsLoading(true)
|
||||||
helpers.setValue({ url, valid: isValid })
|
const isValid = await ProviderInstance.isValidProvider(field.value.url)
|
||||||
helpers.setError(undefined)
|
|
||||||
|
// error if something's not right from response
|
||||||
|
// No way to detect a failed request with ProviderInstance.isValidProvider,
|
||||||
|
// making this error show up for multiple cases it shouldn't, like network
|
||||||
|
// down.
|
||||||
|
if (!isValid)
|
||||||
|
throw Error(
|
||||||
|
'✗ No valid provider detected. Check your network, your URL and try again.'
|
||||||
|
)
|
||||||
|
|
||||||
|
// if all good, add provider to formik state
|
||||||
|
helpers.setValue({ url: field.value.url, valid: isValid })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
helpers.setError(
|
setFieldError(`${field.name}.url`, error.message)
|
||||||
'Could not validate provider. Please check URL and try again.'
|
LoggerInstance.error(error.message)
|
||||||
)
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleValidateButtonClick(
|
|
||||||
e: React.SyntheticEvent,
|
|
||||||
url: string
|
|
||||||
) {
|
|
||||||
e.preventDefault()
|
|
||||||
validateProvider(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleFileInfoClose() {
|
function handleFileInfoClose() {
|
||||||
helpers.setValue({ url: '', valid: false })
|
helpers.setValue({ url: '', valid: false })
|
||||||
helpers.setTouched(false)
|
helpers.setTouched(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRestore(e: React.SyntheticEvent) {
|
function handleDefault(e: React.SyntheticEvent) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
helpers.setValue(initialValues.services[0].providerUrl)
|
helpers.setValue(initialValues.services[0].providerUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
return field?.value?.valid ? (
|
return field?.value?.valid === true ? (
|
||||||
<FileInfo file={field.value} handleClose={handleFileInfoClose} />
|
<FileInfo file={field.value} handleClose={handleFileInfoClose} />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@ -54,23 +58,17 @@ export default function CustomProvider(props: InputProps): ReactElement {
|
|||||||
submitText="Validate"
|
submitText="Validate"
|
||||||
{...props}
|
{...props}
|
||||||
name={`${field.name}.url`}
|
name={`${field.name}.url`}
|
||||||
hasError={Boolean(meta.touched && meta.error)}
|
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
handleButtonClick={handleValidateButtonClick}
|
handleButtonClick={handleValidation}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
style="text"
|
style="text"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={handleRestore}
|
onClick={handleDefault}
|
||||||
className={styles.restore}
|
className={styles.default}
|
||||||
>
|
>
|
||||||
Use Default Provider
|
Use Default Provider
|
||||||
</Button>
|
</Button>
|
||||||
{typeof meta.error === 'string' && meta.touched && meta.error && (
|
|
||||||
<div className={styles.error}>
|
|
||||||
<ErrorMessage name={field.name} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,3 @@
|
|||||||
.error {
|
.error {
|
||||||
composes: error from '@shared/FormInput/index.module.css';
|
composes: error from '@shared/FormInput/index.module.css';
|
||||||
}
|
}
|
||||||
|
|
||||||
.success {
|
|
||||||
background: var(--brand-alert-green);
|
|
||||||
opacity: 1 !important;
|
|
||||||
}
|
|
||||||
|
@ -12,14 +12,12 @@ export default function URLInput({
|
|||||||
handleButtonClick,
|
handleButtonClick,
|
||||||
isLoading,
|
isLoading,
|
||||||
name,
|
name,
|
||||||
hasError,
|
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
submitText: string
|
submitText: string
|
||||||
handleButtonClick(e: React.SyntheticEvent, data: string): void
|
handleButtonClick(e: React.SyntheticEvent, data: string): void
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
name: string
|
name: string
|
||||||
hasError: boolean
|
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const [field, meta] = useField(name)
|
const [field, meta] = useField(name)
|
||||||
const [isButtonDisabled, setIsButtonDisabled] = useState(true)
|
const [isButtonDisabled, setIsButtonDisabled] = useState(true)
|
||||||
|
@ -5,8 +5,6 @@ import Link from 'next/link'
|
|||||||
import get3BoxProfile from '@utils/profile'
|
import get3BoxProfile from '@utils/profile'
|
||||||
import { accountTruncate } from '@utils/web3'
|
import { accountTruncate } from '@utils/web3'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import Add from './Add'
|
|
||||||
import { useWeb3 } from '@context/Web3'
|
|
||||||
import { getEnsName } from '@utils/ens'
|
import { getEnsName } from '@utils/ens'
|
||||||
import { useIsMounted } from '@hooks/useIsMounted'
|
import { useIsMounted } from '@hooks/useIsMounted'
|
||||||
|
|
||||||
@ -21,17 +19,18 @@ export default function Publisher({
|
|||||||
minimal?: boolean
|
minimal?: boolean
|
||||||
className?: string
|
className?: string
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
// const { accountId } = useWeb3()
|
|
||||||
const isMounted = useIsMounted()
|
const isMounted = useIsMounted()
|
||||||
const [profile, setProfile] = useState<Profile>()
|
const [profile, setProfile] = useState<Profile>()
|
||||||
const [name, setName] = useState(accountTruncate(account))
|
const [name, setName] = useState('')
|
||||||
const [accountEns, setAccountEns] = useState<string>()
|
const [accountEns, setAccountEns] = useState<string>()
|
||||||
|
|
||||||
// const showAdd = account === accountId && !profile
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!account) return
|
if (!account) return
|
||||||
|
|
||||||
|
// set default name on hook
|
||||||
|
// to avoid side effect (UI not updating on account's change)
|
||||||
|
setName(accountTruncate(account))
|
||||||
|
|
||||||
const source = axios.CancelToken.source()
|
const source = axios.CancelToken.source()
|
||||||
|
|
||||||
async function getExternalName() {
|
async function getExternalName() {
|
||||||
@ -70,7 +69,6 @@ export default function Publisher({
|
|||||||
<Link href={`/profile/${accountEns || account}`}>
|
<Link href={`/profile/${accountEns || account}`}>
|
||||||
<a title="Show profile page.">{name}</a>
|
<a title="Show profile page.">{name}</a>
|
||||||
</Link>
|
</Link>
|
||||||
{/* {showAdd && <Add />} */}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -53,6 +53,7 @@ export const initialValues: FormPublishData = {
|
|||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
nft: { name: '', symbol: '', description: '', image_data: '' },
|
nft: { name: '', symbol: '', description: '', image_data: '' },
|
||||||
|
transferable: true,
|
||||||
type: 'dataset',
|
type: 'dataset',
|
||||||
name: '',
|
name: '',
|
||||||
author: '',
|
author: '',
|
||||||
|
@ -29,6 +29,7 @@ export interface FormPublishData {
|
|||||||
}
|
}
|
||||||
metadata: {
|
metadata: {
|
||||||
nft: NftMetadata
|
nft: NftMetadata
|
||||||
|
transferable: boolean
|
||||||
type: 'dataset' | 'algorithm'
|
type: 'dataset' | 'algorithm'
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
|
@ -181,8 +181,7 @@ export async function transformPublishFormToDdo(
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
nft: {
|
nft: {
|
||||||
...generateNftCreateData(values?.metadata.nft),
|
...generateNftCreateData(values?.metadata.nft, accountId)
|
||||||
owner: accountId
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -198,7 +197,9 @@ export async function createTokensAndPricing(
|
|||||||
web3: Web3
|
web3: Web3
|
||||||
) {
|
) {
|
||||||
const nftCreateData: NftCreateData = generateNftCreateData(
|
const nftCreateData: NftCreateData = generateNftCreateData(
|
||||||
values.metadata.nft
|
values.metadata.nft,
|
||||||
|
accountId,
|
||||||
|
values.metadata.transferable
|
||||||
)
|
)
|
||||||
const { appConfig } = getSiteMetadata()
|
const { appConfig } = getSiteMetadata()
|
||||||
LoggerInstance.log('[publish] Creating NFT with metadata', nftCreateData)
|
LoggerInstance.log('[publish] Creating NFT with metadata', nftCreateData)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user