mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
merged v4 into c2d
This commit is contained in:
commit
d4570aa1cb
@ -1,6 +1,15 @@
|
|||||||
|
|
||||||
#NEXT_PUBLIC_INFURA_PROJECT_ID="xxx"
|
#NEXT_PUBLIC_INFURA_PROJECT_ID="xxx"
|
||||||
#NEXT_PUBLIC_MARKET_FEE_ADDRESS="0xxx"
|
#NEXT_PUBLIC_MARKET_FEE_ADDRESS="0xxx"
|
||||||
|
#NEXT_PUBLIC_PUBLISHER_MARKET_ORDER_FEE="1"
|
||||||
|
#NEXT_PUBLIC_PUBLISHER_MARKET_POOL_SWAP_FEE="1"
|
||||||
|
#NEXT_PUBLIC_PUBLISHER_MARKET_FIXED_SWAP_FEE="1"
|
||||||
|
#NEXT_PUBLIC_CONSUME_MARKET_ORDER_FEE="1"
|
||||||
|
#NEXT_PUBLIC_CONSUME_MARKET_POOL_SWAP_FEE="1"
|
||||||
|
#NEXT_PUBLIC_CONSUME_MARKET_FIXED_SWAP_FEE="1"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#NEXT_PUBLIC_PORTIS_ID="xxx"
|
#NEXT_PUBLIC_PORTIS_ID="xxx"
|
||||||
|
|
||||||
|
|
||||||
|
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@ -1 +1 @@
|
|||||||
* @mihaisc @kremalicious @claudiaHash @bogdanfazakas @KatunaNorbert @jamiehewitt15 @DimitarSD
|
* @mihaisc @kremalicious @claudiaHash @bogdanfazakas @EnzoVezzaro
|
||||||
|
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@ -4,6 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- v4
|
||||||
tags:
|
tags:
|
||||||
- '**'
|
- '**'
|
||||||
pull_request:
|
pull_request:
|
||||||
|
4
.github/workflows/codeql-analysis.yml
vendored
4
.github/workflows/codeql-analysis.yml
vendored
@ -13,10 +13,10 @@ name: 'CodeQL'
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main, v4]
|
||||||
pull_request:
|
pull_request:
|
||||||
# The branches below must be a subset of the branches above
|
# The branches below must be a subset of the branches above
|
||||||
branches: [main]
|
branches: [main, v4]
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '21 8 * * 4'
|
- cron: '21 8 * * 4'
|
||||||
|
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,6 +11,7 @@ coverage
|
|||||||
repo-metadata.json
|
repo-metadata.json
|
||||||
networks-metadata.json
|
networks-metadata.json
|
||||||
src/@types/subgraph
|
src/@types/subgraph
|
||||||
|
src/@types/apollo/
|
||||||
graphql.schema.json
|
graphql.schema.json
|
||||||
src/@types/graph.types.ts
|
src/@types/graph.types.ts
|
||||||
tsconfig.tsbuildinfo
|
tsconfig.tsbuildinfo
|
||||||
|
@ -7,7 +7,7 @@ module.exports = {
|
|||||||
// return appConfig.metadataCacheUri
|
// return appConfig.metadataCacheUri
|
||||||
metadataCacheUri:
|
metadataCacheUri:
|
||||||
process.env.NEXT_PUBLIC_METADATACACHE_URI ||
|
process.env.NEXT_PUBLIC_METADATACACHE_URI ||
|
||||||
'https://aquariusv4.oceanprotocol.com',
|
'https://v4.aquarius.oceanprotocol.com',
|
||||||
|
|
||||||
// List of chainIds which metadata cache queries will return by default.
|
// List of chainIds which metadata cache queries will return by default.
|
||||||
// This preselects the Chains user preferences.
|
// This preselects the Chains user preferences.
|
||||||
@ -22,6 +22,25 @@ module.exports = {
|
|||||||
marketFeeAddress:
|
marketFeeAddress:
|
||||||
process.env.NEXT_PUBLIC_MARKET_FEE_ADDRESS ||
|
process.env.NEXT_PUBLIC_MARKET_FEE_ADDRESS ||
|
||||||
'0x9984b2453eC7D99a73A5B3a46Da81f197B753C8d',
|
'0x9984b2453eC7D99a73A5B3a46Da81f197B753C8d',
|
||||||
|
// publisher market fee that is taken upon ordering an asset, it is an absolute value, it is declared on erc20 creation
|
||||||
|
publisherMarketOrderFee:
|
||||||
|
process.env.NEXT_PUBLIC_PUBLISHER_MARKET_ORDER_FEE || '0',
|
||||||
|
// fee recieved by the publisher market when a dt is swaped from a pool, percent
|
||||||
|
publisherMarketPoolSwapFee:
|
||||||
|
process.env.NEXT_PUBLIC_PUBLISHER_MARKET_POOL_SWAP_FEE || '0',
|
||||||
|
// fee recieved by the publisher market when a dt is bought from a fixed rate exchange, percent
|
||||||
|
publisherMarketFixedSwapFee:
|
||||||
|
process.env.NEXT_PUBLIC_PUBLISHER_MARKET_FIXED_SWAP_FEE || '0',
|
||||||
|
|
||||||
|
// consume market fee that is taken upon ordering an asset, it is an absolute value, it is specified on order
|
||||||
|
consumeMarketOrderFee:
|
||||||
|
process.env.NEXT_PUBLIC_CONSUME_MARKET_ORDER_FEE || '0',
|
||||||
|
// fee recieved by the consume market when a dt is swaped from a pool, percent
|
||||||
|
consumeMarketPoolSwapFee:
|
||||||
|
process.env.NEXT_PUBLIC_CONSUME_MARKET_POOL_SWAP_FEE || '0',
|
||||||
|
// fee recieved by the consume market when a dt is bought from a fixed rate exchange, percent
|
||||||
|
consumeMarketFixedSwapFee:
|
||||||
|
process.env.NEXT_PUBLIC_CONSUME_MARKET_FIXED_SWAP_FEE || '0',
|
||||||
|
|
||||||
// Used for conversion display, can be whatever coingecko API supports
|
// Used for conversion display, can be whatever coingecko API supports
|
||||||
// see: https://api.coingecko.com/api/v3/simple/supported_vs_currencies
|
// see: https://api.coingecko.com/api/v3/simple/supported_vs_currencies
|
||||||
|
@ -28,7 +28,16 @@
|
|||||||
},
|
},
|
||||||
"free": {
|
"free": {
|
||||||
"title": "Free",
|
"title": "Free",
|
||||||
"info": "Set your data set as free. The datatoken for this data set will be given for free via creating a faucet."
|
"info": "Set your data set as free. The datatoken for this data set will be given for free via creating a faucet.",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "freeAgreement",
|
||||||
|
"type": "checkbox",
|
||||||
|
"options": [
|
||||||
|
"I want this asset to be free. I understand network fees are still to be paid"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pool": {
|
"pool": {
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"name": "nft",
|
"name": "nft",
|
||||||
"label": "Data NFT",
|
"label": "Data NFT",
|
||||||
"type": "nft",
|
"type": "nft",
|
||||||
"help": "All metadata is stored on-chain in a newly deployed ERC-721 contract representing this asset, created with this name & symbol.",
|
"help": "All metadata is stored on-chain in a newly deployed ERC-721 contract representing this asset, created with this name, symbol, description and image.",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -17,24 +17,35 @@ module.exports = (phase, { defaultConfig }) => {
|
|||||||
type: 'asset/resource'
|
type: 'asset/resource'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// for old ocean.js, most likely can be removed later on
|
// for old ocean.js, most likely can be removed later on
|
||||||
config.plugins.push(
|
config.plugins.push(
|
||||||
new options.webpack.IgnorePlugin({
|
new options.webpack.IgnorePlugin({
|
||||||
resourceRegExp: /^electron$/
|
resourceRegExp: /^electron$/
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
const fallback = config.resolve.fallback || {}
|
||||||
config.resolve.fallback = {
|
Object.assign(fallback, {
|
||||||
|
// crypto: require.resolve('crypto-browserify'),
|
||||||
|
// stream: require.resolve('stream-browserify'),
|
||||||
|
// assert: require.resolve('assert'),
|
||||||
|
// os: require.resolve('os-browserify'),
|
||||||
|
// url: require.resolve('url'),
|
||||||
|
http: require.resolve('stream-http'),
|
||||||
|
https: require.resolve('https-browserify'),
|
||||||
fs: false,
|
fs: false,
|
||||||
crypto: false,
|
crypto: false,
|
||||||
os: false,
|
os: false,
|
||||||
stream: false,
|
stream: false,
|
||||||
http: false,
|
|
||||||
https: false,
|
|
||||||
assert: false
|
assert: false
|
||||||
}
|
})
|
||||||
|
config.resolve.fallback = fallback
|
||||||
|
|
||||||
|
config.plugins = (config.plugins || []).concat([
|
||||||
|
new options.webpack.ProvidePlugin({
|
||||||
|
process: 'process/browser',
|
||||||
|
Buffer: ['buffer', 'Buffer']
|
||||||
|
})
|
||||||
|
])
|
||||||
return typeof defaultConfig.webpack === 'function'
|
return typeof defaultConfig.webpack === 'function'
|
||||||
? defaultConfig.webpack(config, options)
|
? defaultConfig.webpack(config, options)
|
||||||
: config
|
: config
|
||||||
|
21513
package-lock.json
generated
21513
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@ -15,13 +15,13 @@
|
|||||||
"type-check": "tsc --noEmit",
|
"type-check": "tsc --noEmit",
|
||||||
"deploy:s3": "bash scripts/deploy-s3.sh",
|
"deploy:s3": "bash scripts/deploy-s3.sh",
|
||||||
"postinstall": "husky install",
|
"postinstall": "husky install",
|
||||||
"codegen:apollo": "apollo client:codegen --endpoint=https://subgraphv4.rinkeby.oceanprotocol.com/subgraphs/name/oceanprotocol/ocean-subgraph --target typescript --tsFileExtension=d.ts --outputFlat src/@types/subgraph/"
|
"codegen:apollo": "apollo client:codegen --endpoint=https://v4.subgraph.rinkeby.oceanprotocol.com/subgraphs/name/oceanprotocol/ocean-subgraph --target typescript --tsFileExtension=d.ts --outputFlat src/@types/subgraph/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@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.11",
|
"@oceanprotocol/lib": "^1.0.0-next.21",
|
||||||
"@oceanprotocol/typographies": "^0.1.0",
|
"@oceanprotocol/typographies": "^0.1.0",
|
||||||
"@portis/web3": "^4.0.6",
|
"@portis/web3": "^4.0.6",
|
||||||
"@tippyjs/react": "^4.2.6",
|
"@tippyjs/react": "^4.2.6",
|
||||||
@ -30,7 +30,6 @@
|
|||||||
"bignumber.js": "^9.0.2",
|
"bignumber.js": "^9.0.2",
|
||||||
"chart.js": "^3.7.0",
|
"chart.js": "^3.7.0",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"d3": "^7.3.0",
|
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
"decimal.js": "^10.3.1",
|
"decimal.js": "^10.3.1",
|
||||||
"dom-confetti": "^0.2.2",
|
"dom-confetti": "^0.2.2",
|
||||||
@ -44,7 +43,7 @@
|
|||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"lodash.debounce": "^4.0.8",
|
"lodash.debounce": "^4.0.8",
|
||||||
"lodash.omit": "^4.5.0",
|
"lodash.omit": "^4.5.0",
|
||||||
"next": "^12.0.9",
|
"next": "^12.1.0",
|
||||||
"query-string": "^7.1.0",
|
"query-string": "^7.1.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-chartjs-2": "^4.0.1",
|
"react-chartjs-2": "^4.0.1",
|
||||||
@ -95,10 +94,13 @@
|
|||||||
"eslint-plugin-react": "^7.28.0",
|
"eslint-plugin-react": "^7.28.0",
|
||||||
"eslint-plugin-react-hooks": "^4.3.0",
|
"eslint-plugin-react-hooks": "^4.3.0",
|
||||||
"file-loader": "^6.2.0",
|
"file-loader": "^6.2.0",
|
||||||
|
"https-browserify": "^1.0.0",
|
||||||
"husky": "^7.0.4",
|
"husky": "^7.0.4",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.5.1",
|
||||||
"pretty-quick": "^3.1.3",
|
"pretty-quick": "^3.1.3",
|
||||||
|
"process": "^0.11.10",
|
||||||
"serve": "^13.0.2",
|
"serve": "^13.0.2",
|
||||||
|
"stream-http": "^2.8.3",
|
||||||
"typescript": "^4.5.4"
|
"typescript": "^4.5.4"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -69,7 +69,8 @@ function AssetProvider({
|
|||||||
|
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
setError(
|
setError(
|
||||||
`[asset] The asset for ${did} was not found in MetadataCache. If you just published a new data set, wait some seconds and refresh this page.`
|
`\`${did}\`` +
|
||||||
|
'\n\nWe could not find an asset for this DID in the cache. If you just published a new asset, wait some seconds and refresh this page.'
|
||||||
)
|
)
|
||||||
LoggerInstance.error(`[asset] Failed getting asset for ${did}`, asset)
|
LoggerInstance.error(`[asset] Failed getting asset for ${did}`, asset)
|
||||||
} else {
|
} else {
|
||||||
@ -95,7 +96,6 @@ function AssetProvider({
|
|||||||
// -----------------------------------
|
// -----------------------------------
|
||||||
const fetchAccessDetails = useCallback(async (): Promise<void> => {
|
const fetchAccessDetails = useCallback(async (): Promise<void> => {
|
||||||
if (!asset?.chainId || !asset?.services) return
|
if (!asset?.chainId || !asset?.services) return
|
||||||
|
|
||||||
const accessDetails = await getAccessDetails(
|
const accessDetails = await getAccessDetails(
|
||||||
asset.chainId,
|
asset.chainId,
|
||||||
asset.services[0].datatokenAddress,
|
asset.services[0].datatokenAddress,
|
||||||
|
@ -10,9 +10,9 @@ export const poolDataQuery = gql`
|
|||||||
poolData: pool(id: $pool) {
|
poolData: pool(id: $pool) {
|
||||||
id
|
id
|
||||||
totalShares
|
totalShares
|
||||||
poolFee
|
liquidityProviderFee
|
||||||
opfFee
|
opcFee
|
||||||
marketFee
|
marketSwapFee
|
||||||
spotPrice
|
spotPrice
|
||||||
baseToken {
|
baseToken {
|
||||||
address
|
address
|
||||||
|
@ -30,7 +30,8 @@ const initialPoolInfo: Partial<PoolInfo> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const initialPoolInfoUser: Partial<PoolInfoUser> = {
|
const initialPoolInfoUser: Partial<PoolInfoUser> = {
|
||||||
liquidity: new Decimal(0)
|
liquidity: new Decimal(0),
|
||||||
|
poolShares: '0'
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialPoolInfoCreator: Partial<PoolInfoUser> = initialPoolInfoUser
|
const initialPoolInfoCreator: Partial<PoolInfoUser> = initialPoolInfoUser
|
||||||
@ -68,7 +69,7 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
|
|||||||
setPoolData(response.poolData)
|
setPoolData(response.poolData)
|
||||||
setPoolInfoUser((prevState) => ({
|
setPoolInfoUser((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
poolShares: response.poolDataUser?.shares[0]?.shares
|
poolShares: response.poolDataUser?.shares[0]?.shares || '0'
|
||||||
}))
|
}))
|
||||||
setPoolSnapshots(response.poolSnapshots)
|
setPoolSnapshots(response.poolSnapshots)
|
||||||
LoggerInstance.log('[pool] Fetched pool data:', response.poolData)
|
LoggerInstance.log('[pool] Fetched pool data:', response.poolData)
|
||||||
@ -77,8 +78,6 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
|
|||||||
}, [asset?.chainId, asset?.accessDetails?.addressOrId, owner, accountId])
|
}, [asset?.chainId, asset?.accessDetails?.addressOrId, owner, accountId])
|
||||||
|
|
||||||
// Helper: start interval fetching
|
// Helper: start interval fetching
|
||||||
// Having `accountId` as dependency is important for interval to
|
|
||||||
// change after user account switch.
|
|
||||||
const initFetchInterval = useCallback(() => {
|
const initFetchInterval = useCallback(() => {
|
||||||
if (fetchInterval) return
|
if (fetchInterval) return
|
||||||
|
|
||||||
@ -89,6 +88,10 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
|
|||||||
)
|
)
|
||||||
}, refreshInterval)
|
}, refreshInterval)
|
||||||
setFetchInterval(newInterval)
|
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])
|
}, [fetchInterval, fetchAllData, accountId])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -115,10 +118,10 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!poolData) return
|
if (!poolData) return
|
||||||
|
|
||||||
// Fees
|
// Fees - this will be renamed again in subgraph
|
||||||
const poolFee = getFee(poolData.poolFee)
|
const poolFee = getFee(poolData.liquidityProviderFee)
|
||||||
const marketFee = getFee(poolData.marketFee)
|
const marketFee = getFee(poolData.marketSwapFee)
|
||||||
const opfFee = getFee(poolData.opfFee)
|
const opfFee = getFee(poolData.opcFee)
|
||||||
|
|
||||||
// Total Liquidity
|
// Total Liquidity
|
||||||
const totalLiquidityInOcean = isValidNumber(poolData.spotPrice)
|
const totalLiquidityInOcean = isValidNumber(poolData.spotPrice)
|
||||||
@ -191,13 +194,13 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
|
|||||||
!poolData ||
|
!poolData ||
|
||||||
!poolInfo?.totalPoolTokens ||
|
!poolInfo?.totalPoolTokens ||
|
||||||
!asset?.chainId ||
|
!asset?.chainId ||
|
||||||
!accountId
|
!accountId ||
|
||||||
|
!poolInfoUser
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
// Staking bot receives half the pool shares so for display purposes
|
// 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.
|
// we can multiply by 2 as we have a hardcoded 50/50 pool weight.
|
||||||
const userPoolShares = new Decimal(poolInfoUser.poolShares)
|
const userPoolShares = new Decimal(poolInfoUser.poolShares || 0)
|
||||||
.mul(2)
|
.mul(2)
|
||||||
.toString()
|
.toString()
|
||||||
|
|
||||||
@ -235,6 +238,8 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
|
|||||||
poolShares: userPoolShares,
|
poolShares: userPoolShares,
|
||||||
...newPoolInfoUser
|
...newPoolInfoUser
|
||||||
})
|
})
|
||||||
|
// poolInfoUser was not added on purpose, we use setPoolInfoUser so it will just loop
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [
|
}, [
|
||||||
poolData,
|
poolData,
|
||||||
poolInfoUser?.poolShares,
|
poolInfoUser?.poolShares,
|
||||||
|
@ -220,18 +220,14 @@ function ProfileProvider({
|
|||||||
async (cancelToken: CancelToken) => {
|
async (cancelToken: CancelToken) => {
|
||||||
if (!accountId || !chainIds) return
|
if (!accountId || !chainIds) return
|
||||||
|
|
||||||
const didList: string[] = []
|
const dtList: string[] = []
|
||||||
const tokenOrders = await getUserTokenOrders(accountId, chainIds)
|
const tokenOrders = await getUserTokenOrders(accountId, chainIds)
|
||||||
|
|
||||||
for (let i = 0; i < tokenOrders?.length; i++) {
|
for (let i = 0; i < tokenOrders?.length; i++) {
|
||||||
const did = web3.utils
|
dtList.push(tokenOrders[i].datatoken.address)
|
||||||
.toChecksumAddress(tokenOrders[i].datatoken.address)
|
|
||||||
.replace('0x', 'did:op:')
|
|
||||||
didList.push(did)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloads = await getDownloadAssets(
|
const downloads = await getDownloadAssets(
|
||||||
didList,
|
dtList,
|
||||||
tokenOrders,
|
tokenOrders,
|
||||||
chainIds,
|
chainIds,
|
||||||
cancelToken
|
cancelToken
|
||||||
|
@ -1,159 +0,0 @@
|
|||||||
import { useState } from 'react'
|
|
||||||
import { consumeFeedback } from '@utils/feedback'
|
|
||||||
import {
|
|
||||||
approve,
|
|
||||||
Datatoken,
|
|
||||||
FreOrderParams,
|
|
||||||
LoggerInstance,
|
|
||||||
OrderParams,
|
|
||||||
Pool,
|
|
||||||
ProviderFees,
|
|
||||||
ProviderInstance,
|
|
||||||
signHash,
|
|
||||||
ZERO_ADDRESS
|
|
||||||
} from '@oceanprotocol/lib'
|
|
||||||
import { useWeb3 } from '@context/Web3'
|
|
||||||
import { getOceanConfig } from '@utils/ocean'
|
|
||||||
|
|
||||||
interface UseConsume {
|
|
||||||
consume: (
|
|
||||||
did: string,
|
|
||||||
dataTokenAddress: string,
|
|
||||||
serviceType: string,
|
|
||||||
marketFeeAddress: string,
|
|
||||||
orderId?: string
|
|
||||||
) => Promise<string>
|
|
||||||
consumeStep?: number
|
|
||||||
consumeStepText?: string
|
|
||||||
consumeError?: string
|
|
||||||
isLoading: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
function useConsume(): UseConsume {
|
|
||||||
const { accountId, web3, chainId } = useWeb3()
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
|
||||||
const [consumeStep, setConsumeStep] = useState<number | undefined>()
|
|
||||||
const [consumeStepText, setConsumeStepText] = useState<string | undefined>()
|
|
||||||
const [consumeError, setConsumeError] = useState<string | undefined>()
|
|
||||||
|
|
||||||
function setStep(index: number) {
|
|
||||||
setConsumeStep(index)
|
|
||||||
setConsumeStepText(consumeFeedback[index])
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: this will be done in another PR
|
|
||||||
async function consume(
|
|
||||||
did: string,
|
|
||||||
datatokenAddress: string,
|
|
||||||
serviceType = 'access',
|
|
||||||
marketFeeAddress: string,
|
|
||||||
orderId?: string
|
|
||||||
): Promise<string> {
|
|
||||||
if (!accountId) return
|
|
||||||
|
|
||||||
setIsLoading(true)
|
|
||||||
setConsumeError(undefined)
|
|
||||||
|
|
||||||
try {
|
|
||||||
setStep(0)
|
|
||||||
if (!orderId) {
|
|
||||||
const datatoken = new Datatoken(web3)
|
|
||||||
// if we don't have a previous valid order, get one
|
|
||||||
const userOwnedTokens = await datatoken.balance(
|
|
||||||
datatokenAddress,
|
|
||||||
accountId
|
|
||||||
)
|
|
||||||
|
|
||||||
setStep(1)
|
|
||||||
try {
|
|
||||||
const config = getOceanConfig(chainId)
|
|
||||||
// const txApprove = await approve(
|
|
||||||
// web3,
|
|
||||||
// accountId,
|
|
||||||
// config.oceanTokenAddress,
|
|
||||||
// accountId,
|
|
||||||
// '1',
|
|
||||||
// false
|
|
||||||
// )
|
|
||||||
// console.log('approve tx', txApprove)
|
|
||||||
|
|
||||||
// const txApprove1 = await approve(
|
|
||||||
// web3,
|
|
||||||
// accountId,
|
|
||||||
// config.oceanTokenAddress,
|
|
||||||
// datatokenAddress,
|
|
||||||
// '1',
|
|
||||||
// false
|
|
||||||
// )
|
|
||||||
// console.log('approve tx', txApprove1)
|
|
||||||
|
|
||||||
// diference between timeout and validUntil?
|
|
||||||
const initializeData = await ProviderInstance.initialize(
|
|
||||||
did,
|
|
||||||
'fca052c239a62523be30ab8ee70c4046867f6cd89f228185fe2996ded3d23c3c',
|
|
||||||
0,
|
|
||||||
accountId,
|
|
||||||
'https://providerv4.rinkeby.oceanprotocol.com'
|
|
||||||
)
|
|
||||||
const orderParams = {
|
|
||||||
consumer: accountId,
|
|
||||||
serviceIndex: 1,
|
|
||||||
_providerFees: initializeData.providerFee
|
|
||||||
} as OrderParams
|
|
||||||
const freParams = {
|
|
||||||
exchangeContract: config.fixedRateExchangeAddress,
|
|
||||||
exchangeId:
|
|
||||||
'0x7ac824fef114255e5e3521a161ef692ec32003916fb6f3fe985cb74790d053ca',
|
|
||||||
maxBaseTokenAmount: web3.utils.toWei('2'),
|
|
||||||
swapMarketFee: web3.utils.toWei('0'),
|
|
||||||
marketFeeAddress: ZERO_ADDRESS
|
|
||||||
} as FreOrderParams
|
|
||||||
|
|
||||||
const esttx = await datatoken.estGasBuyFromFreAndOrder(
|
|
||||||
datatokenAddress,
|
|
||||||
accountId,
|
|
||||||
orderParams,
|
|
||||||
freParams
|
|
||||||
)
|
|
||||||
const tx = await datatoken.buyFromFreAndOrder(
|
|
||||||
datatokenAddress,
|
|
||||||
accountId,
|
|
||||||
orderParams,
|
|
||||||
freParams
|
|
||||||
)
|
|
||||||
|
|
||||||
LoggerInstance.log('ordercreated', orderId)
|
|
||||||
setStep(2)
|
|
||||||
} catch (error) {
|
|
||||||
setConsumeError(error.message)
|
|
||||||
return error.message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setStep(3)
|
|
||||||
if (orderId)
|
|
||||||
// await ocean.assets.download(
|
|
||||||
// did as string,
|
|
||||||
// orderId,
|
|
||||||
// dataTokenAddress,
|
|
||||||
// account,
|
|
||||||
// ''
|
|
||||||
// )
|
|
||||||
setStep(4)
|
|
||||||
} catch (error) {
|
|
||||||
setConsumeError(error.message)
|
|
||||||
LoggerInstance.error(error)
|
|
||||||
return error.message
|
|
||||||
} finally {
|
|
||||||
setConsumeStep(undefined)
|
|
||||||
setConsumeStepText(undefined)
|
|
||||||
setIsLoading(false)
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return { consume, consumeStep, consumeStepText, consumeError, isLoading }
|
|
||||||
}
|
|
||||||
|
|
||||||
export { useConsume }
|
|
||||||
export default useConsume
|
|
@ -1,244 +0,0 @@
|
|||||||
import { Asset, Config, LoggerInstance } from '@oceanprotocol/lib'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { TransactionReceipt } from 'web3-core'
|
|
||||||
import { Decimal } from 'decimal.js'
|
|
||||||
import {
|
|
||||||
getCreatePricingPoolFeedback,
|
|
||||||
getCreatePricingExchangeFeedback,
|
|
||||||
getBuyDTFeedback,
|
|
||||||
getCreateFreePricingFeedback,
|
|
||||||
getDispenseFeedback
|
|
||||||
} from '@utils/feedback'
|
|
||||||
import { useWeb3 } from '@context/Web3'
|
|
||||||
import { getOceanConfig } from '@utils/ocean'
|
|
||||||
|
|
||||||
interface UsePricing {
|
|
||||||
getDTSymbol: (ddo: Asset) => Promise<string>
|
|
||||||
getDTName: (ddo: Asset) => Promise<string>
|
|
||||||
mint: (tokensToMint: string, ddo: Asset) => Promise<TransactionReceipt | void>
|
|
||||||
buyDT: (
|
|
||||||
amountDataToken: number | string,
|
|
||||||
accessDetails: AccessDetails,
|
|
||||||
ddo: Asset
|
|
||||||
) => Promise<TransactionReceipt | void>
|
|
||||||
pricingStep?: number
|
|
||||||
pricingStepText?: string
|
|
||||||
pricingError?: string
|
|
||||||
pricingIsLoading: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
function usePricing(): UsePricing {
|
|
||||||
const { accountId, networkId } = useWeb3()
|
|
||||||
const [pricingIsLoading, setPricingIsLoading] = useState(false)
|
|
||||||
const [pricingStep, setPricingStep] = useState<number>()
|
|
||||||
const [pricingStepText, setPricingStepText] = useState<string>()
|
|
||||||
const [pricingError, setPricingError] = useState<string>()
|
|
||||||
const [oceanConfig, setOceanConfig] = useState<Config>()
|
|
||||||
|
|
||||||
// Grab ocen config based on passed networkId
|
|
||||||
useEffect(() => {
|
|
||||||
if (!networkId) return
|
|
||||||
|
|
||||||
const oceanConfig = getOceanConfig(networkId)
|
|
||||||
setOceanConfig(oceanConfig)
|
|
||||||
}, [networkId])
|
|
||||||
|
|
||||||
async function getDTSymbol(ddo: Asset): Promise<string> {
|
|
||||||
if (!accountId) return
|
|
||||||
|
|
||||||
const { datatokens } = ddo
|
|
||||||
return datatokens[0].symbol
|
|
||||||
// return dataTokenInfo
|
|
||||||
// ? dataTokenInfo.symbol
|
|
||||||
// : await ocean?.datatokens.getSymbol(dataTokenInfo.address)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getDTName(ddo: Asset): Promise<string> {
|
|
||||||
if (!accountId) return
|
|
||||||
const { datatokens } = ddo
|
|
||||||
return datatokens[0].name
|
|
||||||
// return dataTokenInfo
|
|
||||||
// ? dataTokenInfo.name
|
|
||||||
// : await ocean?.datatokens.getName(dataTokenInfo.address)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper for setting steps & feedback for all flows
|
|
||||||
async function setStep(
|
|
||||||
index: number,
|
|
||||||
type: 'pool' | 'exchange' | 'free' | 'buy' | 'dispense',
|
|
||||||
ddo: Asset
|
|
||||||
) {
|
|
||||||
const dtSymbol = await getDTSymbol(ddo)
|
|
||||||
setPricingStep(index)
|
|
||||||
if (!dtSymbol) return
|
|
||||||
|
|
||||||
let messages
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'pool':
|
|
||||||
messages = getCreatePricingPoolFeedback(dtSymbol)
|
|
||||||
break
|
|
||||||
case 'exchange':
|
|
||||||
messages = getCreatePricingExchangeFeedback(dtSymbol)
|
|
||||||
break
|
|
||||||
case 'free':
|
|
||||||
messages = getCreateFreePricingFeedback(dtSymbol)
|
|
||||||
break
|
|
||||||
case 'buy':
|
|
||||||
messages = getBuyDTFeedback(dtSymbol)
|
|
||||||
break
|
|
||||||
case 'dispense':
|
|
||||||
messages = getDispenseFeedback(dtSymbol)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
setPricingStepText(messages[index])
|
|
||||||
}
|
|
||||||
|
|
||||||
async function mint(
|
|
||||||
tokensToMint: string,
|
|
||||||
ddo: Asset
|
|
||||||
): Promise<TransactionReceipt | void> {
|
|
||||||
const { datatokens } = ddo
|
|
||||||
LoggerInstance.log('mint function', datatokens[0].address, accountId)
|
|
||||||
// const balance = new Decimal(
|
|
||||||
// await ocean.datatokens.balance(dataTokenInfo.address, accountId)
|
|
||||||
// )
|
|
||||||
// const tokens = new Decimal(tokensToMint)
|
|
||||||
// if (tokens.greaterThan(balance)) {
|
|
||||||
// const mintAmount = tokens.minus(balance)
|
|
||||||
// const tx = await ocean.datatokens.mint(
|
|
||||||
// dataTokenInfo.address,
|
|
||||||
// accountId,
|
|
||||||
// mintAmount.toString()
|
|
||||||
// )
|
|
||||||
// return tx
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buyDT(
|
|
||||||
amountDataToken: number | string,
|
|
||||||
accessDetails: AccessDetails,
|
|
||||||
ddo: Asset
|
|
||||||
): Promise<TransactionReceipt | void> {
|
|
||||||
if (!accountId) return
|
|
||||||
|
|
||||||
let tx
|
|
||||||
|
|
||||||
try {
|
|
||||||
setPricingIsLoading(true)
|
|
||||||
setPricingError(undefined)
|
|
||||||
setStep(1, 'buy', ddo)
|
|
||||||
|
|
||||||
LoggerInstance.log('Price found for buying', accessDetails)
|
|
||||||
Decimal.set({ precision: 18 })
|
|
||||||
|
|
||||||
switch (accessDetails?.type) {
|
|
||||||
case 'dynamic': {
|
|
||||||
const oceanAmmount = new Decimal(accessDetails.price)
|
|
||||||
.times(1.05)
|
|
||||||
.toString()
|
|
||||||
const maxPrice = new Decimal(accessDetails.price).times(2).toString()
|
|
||||||
|
|
||||||
setStep(2, 'buy', ddo)
|
|
||||||
LoggerInstance.log(
|
|
||||||
'Buying token from pool',
|
|
||||||
accessDetails,
|
|
||||||
accountId,
|
|
||||||
oceanAmmount,
|
|
||||||
maxPrice
|
|
||||||
)
|
|
||||||
// tx = await ocean.pool.buyDT(
|
|
||||||
// accountId,
|
|
||||||
// price.address,
|
|
||||||
// String(amountDataToken),
|
|
||||||
// oceanAmmount,
|
|
||||||
// maxPrice
|
|
||||||
// )
|
|
||||||
setStep(3, 'buy', ddo)
|
|
||||||
LoggerInstance.log('DT buy response', tx)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'fixed': {
|
|
||||||
if (!oceanConfig.oceanTokenAddress) {
|
|
||||||
LoggerInstance.error(`'oceanTokenAddress' not set in oceanConfig`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!oceanConfig.fixedRateExchangeAddress) {
|
|
||||||
LoggerInstance.error(
|
|
||||||
`'fixedRateExchangeAddress' not set in oceanConfig`
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
LoggerInstance.log(
|
|
||||||
'Buying token from exchange',
|
|
||||||
accessDetails,
|
|
||||||
accountId
|
|
||||||
)
|
|
||||||
// await ocean.datatokens.approve(
|
|
||||||
// oceanConfig.oceanTokenAddress,
|
|
||||||
// oceanConfig.fixedRateExchangeAddress,
|
|
||||||
// `${price.value}`,
|
|
||||||
// accountId
|
|
||||||
// )
|
|
||||||
setStep(2, 'buy', ddo)
|
|
||||||
// tx = await ocean.fixedRateExchange.buyDT(
|
|
||||||
// price.address,
|
|
||||||
// `${amountDataToken}`,
|
|
||||||
// accountId
|
|
||||||
// )
|
|
||||||
setStep(3, 'buy', ddo)
|
|
||||||
LoggerInstance.log('DT exchange buy response', tx)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'free': {
|
|
||||||
setStep(1, 'dispense', ddo)
|
|
||||||
// const isDispensable = await ocean.OceanDispenser.isDispensable(
|
|
||||||
// ddo?.services[0].datatokenAddress,
|
|
||||||
// accountId,
|
|
||||||
// '1'
|
|
||||||
// )
|
|
||||||
|
|
||||||
// if (!isDispensable) {
|
|
||||||
// LoggerInstance.error(
|
|
||||||
// `Dispenser for ${ddo?.services[0].datatokenAddress} failed to dispense`
|
|
||||||
// )
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// tx = await ocean.OceanDispenser.dispense(
|
|
||||||
// ddo?.services[0].datatokenAddress,
|
|
||||||
// accountId,
|
|
||||||
// '1'
|
|
||||||
// )
|
|
||||||
setStep(2, 'dispense', ddo)
|
|
||||||
LoggerInstance.log('DT dispense response', tx)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setPricingError(error.message)
|
|
||||||
LoggerInstance.error(error)
|
|
||||||
} finally {
|
|
||||||
setStep(0, 'buy', ddo)
|
|
||||||
setPricingStepText(undefined)
|
|
||||||
setPricingIsLoading(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return tx
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
getDTSymbol,
|
|
||||||
getDTName,
|
|
||||||
buyDT,
|
|
||||||
mint,
|
|
||||||
pricingStep,
|
|
||||||
pricingStepText,
|
|
||||||
pricingIsLoading,
|
|
||||||
pricingError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { usePricing }
|
|
||||||
export default usePricing
|
|
@ -1,12 +1,6 @@
|
|||||||
import { UseSiteMetadata } from './types'
|
import { UseSiteMetadata } from './types'
|
||||||
import siteContent from '../../../content/site.json'
|
import { getSiteMetadata } from '@utils/siteConfig'
|
||||||
import appConfig from '../../../app.config'
|
|
||||||
|
|
||||||
export function useSiteMetadata(): UseSiteMetadata {
|
export function useSiteMetadata(): UseSiteMetadata {
|
||||||
const siteMeta: UseSiteMetadata = {
|
return getSiteMetadata()
|
||||||
...siteContent,
|
|
||||||
appConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
return siteMeta
|
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,12 @@ export interface UseSiteMetadata {
|
|||||||
chainIds: number[]
|
chainIds: number[]
|
||||||
chainIdsSupported: number[]
|
chainIdsSupported: number[]
|
||||||
marketFeeAddress: string
|
marketFeeAddress: string
|
||||||
|
publisherMarketOrderFee: string
|
||||||
|
publisherMarketPoolSwapFee: string
|
||||||
|
publisherMarketFixedSwapFee: string
|
||||||
|
consumeMarketOrderFee: string
|
||||||
|
consumeMarketPoolSwapFee: string
|
||||||
|
consumeMarketFixedSwapFee: string
|
||||||
currencies: string[]
|
currencies: string[]
|
||||||
portisId: string
|
portisId: string
|
||||||
allowFixedPricing: string
|
allowFixedPricing: string
|
||||||
|
51
src/@types/Price.d.ts
vendored
51
src/@types/Price.d.ts
vendored
@ -1,14 +1,54 @@
|
|||||||
|
import { ProviderFees } from '@oceanprotocol/lib'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @interface OrderPriceAndFee
|
||||||
|
* @prop {string} price total price including fees
|
||||||
|
* @prop {string} publisherMarketOrderFee fee received by the market where the asset was published. It is set on erc20 creation. It is a absolute value
|
||||||
|
* @prop {string} publisherMarketPoolSwapFee fee received by the market where the asset was published on any swap (pool or fre). Absolute value based on the configured percentage
|
||||||
|
* @prop {string} publisherMarketFixedSwapFee fee received by the market where the asset was published on any swap (pool or fre). Absolute value based on the configured percentage
|
||||||
|
* @prop {string} consumeMarketOrderFee fee received by the market where the asset is ordered. It is set on erc20 creation. It is a absolute value
|
||||||
|
* @prop {string} consumeMarketPoolSwapFee fee received by the market where the asset is ordered on any swap (pool or fre). Absolute value based on the configured percentage
|
||||||
|
* @prop {string} consumeMarketFixedSwapFee fee received by the market where the asset is ordered on any swap (pool or fre). Absolute value based on the configured percentage
|
||||||
|
* @prop {string} liquidityProviderSwapFee fee received by the liquidity providers of the pool. It is a percentage ( ex 50% means liquidityProviderSwapFee=0.5)
|
||||||
|
* @prop {ProviderFees} providerFee received from provider
|
||||||
|
* @prop {string} opcFee ocean protocol community fee, Absolute value based on the configured percentage
|
||||||
|
*/
|
||||||
|
interface OrderPriceAndFees {
|
||||||
|
price: string
|
||||||
|
publisherMarketOrderFee: string
|
||||||
|
publisherMarketPoolSwapFee: string
|
||||||
|
publisherMarketFixedSwapFee: string
|
||||||
|
consumeMarketOrderFee: string
|
||||||
|
consumeMarketPoolSwapFee: string
|
||||||
|
consumeMarketFixedSwapFee: string
|
||||||
|
liquidityProviderSwapFee: string
|
||||||
|
providerFee: ProviderFees
|
||||||
|
opcFee: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @interface AccessDetails
|
||||||
|
* @prop {'dynamic' | 'fixed' | 'free' | ''} type
|
||||||
|
* @prop {string} price can be either spotPrice/rate
|
||||||
|
* @prop {string} addressOrId if type is dynamic this is the pool address, for fixed/free this is an id.
|
||||||
|
* @prop {TokenInfo} baseToken
|
||||||
|
* @prop {TokenInfo} datatoken
|
||||||
|
* @prop {bool} isPurchasable checks if you can buy a datatoken from fixed rate exchange/pool/dispenser. For pool it also checks if there is enough dt liquidity
|
||||||
|
* @prop {bool} isOwned checks if there are valid orders for this, it also takes in consideration timeout
|
||||||
|
* @prop {string} validOrderTx the latest valid order tx, it also takes in consideration timeout
|
||||||
|
* @prop {string} publisherMarketOrderFee this is here just because it's more efficient, it's allready in the query
|
||||||
|
* @prop {FeeInfo} feeInfo values of the relevant fees
|
||||||
|
*/
|
||||||
interface AccessDetails {
|
interface AccessDetails {
|
||||||
type: 'dynamic' | 'fixed' | 'free' | ''
|
type: 'dynamic' | 'fixed' | 'free' | ''
|
||||||
price: number
|
price: string
|
||||||
// if type is dynamic this is the pool address, for fixed/free this is an id.
|
|
||||||
addressOrId: string
|
addressOrId: string
|
||||||
baseToken: TokenInfo
|
baseToken: TokenInfo
|
||||||
datatoken: TokenInfo
|
datatoken: TokenInfo
|
||||||
isConsumable?: boolean
|
isPurchasable?: boolean
|
||||||
// if there are valid orders for this
|
isOwned: bool
|
||||||
owned: bool
|
|
||||||
validOrderTx: string
|
validOrderTx: string
|
||||||
|
publisherMarketOrderFee: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PriceOptions {
|
interface PriceOptions {
|
||||||
@ -20,4 +60,5 @@ interface PriceOptions {
|
|||||||
weightOnOcean: string
|
weightOnOcean: string
|
||||||
// easier to keep this as number for Yup input validation
|
// easier to keep this as number for Yup input validation
|
||||||
swapFee: number
|
swapFee: number
|
||||||
|
freeAgreement: boolean
|
||||||
}
|
}
|
||||||
|
5
src/@types/aquarius/SearchResponse.d.ts
vendored
5
src/@types/aquarius/SearchResponse.d.ts
vendored
@ -20,7 +20,10 @@ interface SearchResponse {
|
|||||||
_scroll_id?: string | undefined
|
_scroll_id?: string | undefined
|
||||||
_shards: ShardsResponse
|
_shards: ShardsResponse
|
||||||
hits: {
|
hits: {
|
||||||
total: number
|
total: {
|
||||||
|
relation: string
|
||||||
|
value: number
|
||||||
|
}
|
||||||
max_score: number
|
max_score: number
|
||||||
hits: Array<{
|
hits: Array<{
|
||||||
_index: string
|
_index: string
|
||||||
|
191
src/@utils/SvgWaves.ts
Normal file
191
src/@utils/SvgWaves.ts
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
import { computeControlPoints } from './bezier-spline'
|
||||||
|
import { randomIntFromInterval } from './numbers'
|
||||||
|
|
||||||
|
export interface WaveProperties {
|
||||||
|
width?: number
|
||||||
|
height?: number
|
||||||
|
color?: string
|
||||||
|
fill?: boolean
|
||||||
|
layerCount?: number
|
||||||
|
pointsPerLayer?: number
|
||||||
|
variance?: number
|
||||||
|
maxOpacity?: number
|
||||||
|
opacitySteps?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
class Point {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
|
||||||
|
constructor(x: number, y: number) {
|
||||||
|
this.x = Math.floor(x)
|
||||||
|
this.y = Math.floor(y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum WaveColors {
|
||||||
|
Violet = '#e000cf',
|
||||||
|
Pink = '#ff4092',
|
||||||
|
Grey = '#8b98a9'
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SvgWaves {
|
||||||
|
properties: WaveProperties
|
||||||
|
layers: Point[][]
|
||||||
|
|
||||||
|
static xmlns = 'http://www.w3.org/2000/svg'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to get randomly generated WaveProperties
|
||||||
|
* These are generated with a focus on small file size for the svg
|
||||||
|
* meaning low character count.
|
||||||
|
* - width & height: default is 2 digits max (99)
|
||||||
|
* - color pink is selected per default
|
||||||
|
* - set coloring to fill or stroke (fill is selected per default)
|
||||||
|
* - randomly decide if fill or stroke coloring should be used
|
||||||
|
* - create 4 layers with 4 - 5 points per layers
|
||||||
|
* -> results in random looking, yet small enough svgs
|
||||||
|
* - variance between 0.5 and 0.7 returns smooth & different results
|
||||||
|
*
|
||||||
|
* @returns new randomly generated WaveProperties
|
||||||
|
*/
|
||||||
|
static getProps(): WaveProperties {
|
||||||
|
return {
|
||||||
|
width: 99,
|
||||||
|
height: 99,
|
||||||
|
color: WaveColors.Pink,
|
||||||
|
fill: true,
|
||||||
|
layerCount: 4,
|
||||||
|
pointsPerLayer: randomIntFromInterval(3, 4),
|
||||||
|
variance: Math.random() * 0.2 + 0.5, // 0.5 - 0.7
|
||||||
|
maxOpacity: 255, // 0xff
|
||||||
|
opacitySteps: 68 // 0x44
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static generatePoint(
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
moveX: number,
|
||||||
|
moveY: number,
|
||||||
|
width: number
|
||||||
|
): Point {
|
||||||
|
const varianceY = y - moveY / 2 + Math.random() * moveY
|
||||||
|
const varianceX =
|
||||||
|
x === 0 || x === width ? x : x - moveX / 2 + Math.random() * moveX
|
||||||
|
return new Point(varianceX, varianceY)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.properties = SvgWaves.getProps()
|
||||||
|
this.layers = this.generateLayers()
|
||||||
|
}
|
||||||
|
|
||||||
|
generateLayers(): Point[][] {
|
||||||
|
const { width, height, layerCount, pointsPerLayer, variance } =
|
||||||
|
this.properties
|
||||||
|
|
||||||
|
const cellWidth = width / pointsPerLayer
|
||||||
|
const cellHeight = height / layerCount
|
||||||
|
|
||||||
|
// define movement constraints for point generation
|
||||||
|
// lower smoothness results in steeper curves in waves
|
||||||
|
const horizontalSmoothness = 0.5
|
||||||
|
const moveX = cellWidth * variance * (1 - horizontalSmoothness)
|
||||||
|
const moveY = cellHeight * variance
|
||||||
|
|
||||||
|
const layers = []
|
||||||
|
for (let y = cellHeight; y < height; y += cellHeight) {
|
||||||
|
const points = []
|
||||||
|
for (let x = 0; x <= width; x += cellWidth) {
|
||||||
|
points.push(SvgWaves.generatePoint(x, y, moveX, moveY, width))
|
||||||
|
}
|
||||||
|
layers.push(points)
|
||||||
|
}
|
||||||
|
return layers
|
||||||
|
}
|
||||||
|
|
||||||
|
generateSvg(): Element {
|
||||||
|
const svg = document.createElementNS(SvgWaves.xmlns, 'svg')
|
||||||
|
svg.setAttribute('width', this.properties.width.toString())
|
||||||
|
svg.setAttribute('height', this.properties.height.toString())
|
||||||
|
svg.setAttribute('fill', this.properties.fill ? undefined : 'transparent')
|
||||||
|
svg.setAttribute('xmlns', SvgWaves.xmlns)
|
||||||
|
|
||||||
|
for (let i = 0; i < this.layers.length; i++) {
|
||||||
|
const path = this.generatePath(
|
||||||
|
this.layers[i],
|
||||||
|
this.getOpacityForLayer(i + 1)
|
||||||
|
)
|
||||||
|
svg.appendChild(path)
|
||||||
|
}
|
||||||
|
return svg
|
||||||
|
}
|
||||||
|
|
||||||
|
generatePath(points: Point[], opacity = 'ff'): Element {
|
||||||
|
const xPoints = points.map((p) => Math.floor(p.x))
|
||||||
|
const yPoints = points.map((p) => Math.floor(p.y))
|
||||||
|
|
||||||
|
// get bezier control points
|
||||||
|
const xControlPoints = computeControlPoints(xPoints)
|
||||||
|
const yControlPoints = computeControlPoints(yPoints)
|
||||||
|
|
||||||
|
// Should the path be closed & filled or stroke only
|
||||||
|
const closed = this.properties.fill
|
||||||
|
|
||||||
|
// For closed paths define bottom corners as start & end point
|
||||||
|
const bottomLeftPoint = new Point(0, this.properties.height)
|
||||||
|
const bottomRightPoint = new Point(
|
||||||
|
this.properties.width,
|
||||||
|
this.properties.height
|
||||||
|
)
|
||||||
|
const startPoint = closed ? bottomLeftPoint : points[0]
|
||||||
|
const endPoint = closed ? bottomRightPoint : points[points.length - 1]
|
||||||
|
|
||||||
|
// start constructing the 'd' attribute for the <path>
|
||||||
|
let path = `M${startPoint.x},${startPoint.y}`
|
||||||
|
|
||||||
|
// if starting in bottom corner, move to start of wave first
|
||||||
|
if (closed) path += `L${xPoints[0]},${yPoints[0]}`
|
||||||
|
|
||||||
|
// add path curves
|
||||||
|
for (let i = 0; i < xPoints.length - 1; i++) {
|
||||||
|
path +=
|
||||||
|
`C${xControlPoints.p1[i]},${yControlPoints.p1[i]} ` +
|
||||||
|
`${xControlPoints.p2[i]},${yControlPoints.p2[i]} ` +
|
||||||
|
`${xPoints[i + 1]},${yPoints[i + 1]}`
|
||||||
|
}
|
||||||
|
|
||||||
|
path = closed
|
||||||
|
? `${path}L${endPoint.x},${endPoint.y}Z` // if closing in bottom corners move there and close
|
||||||
|
: `${path}AZ` // else just close the path
|
||||||
|
|
||||||
|
// create the path element
|
||||||
|
const svgPath = document.createElementNS(SvgWaves.xmlns, 'path')
|
||||||
|
const colorStyle = closed ? 'fill' : 'stroke'
|
||||||
|
svgPath.setAttributeNS(
|
||||||
|
null,
|
||||||
|
colorStyle,
|
||||||
|
`${this.properties.color}${opacity}`
|
||||||
|
)
|
||||||
|
svgPath.setAttributeNS(null, 'd', path)
|
||||||
|
|
||||||
|
return svgPath
|
||||||
|
}
|
||||||
|
|
||||||
|
getOpacityForLayer(layer: number): string {
|
||||||
|
const { layerCount, maxOpacity, opacitySteps } = this.properties
|
||||||
|
|
||||||
|
const minOpacity = maxOpacity - (layerCount - 1) * opacitySteps
|
||||||
|
// calculate decimal opacity value for layer
|
||||||
|
const opacityDec = minOpacity + layer * opacitySteps
|
||||||
|
|
||||||
|
// translate to hex string
|
||||||
|
return opacityDec.toString(16)
|
||||||
|
}
|
||||||
|
|
||||||
|
setProps(properties: WaveProperties): void {
|
||||||
|
this.properties = { ...SvgWaves.getProps(), ...properties }
|
||||||
|
this.layers = this.generateLayers()
|
||||||
|
}
|
||||||
|
}
|
@ -8,8 +8,13 @@ import {
|
|||||||
TokensPriceQuery,
|
TokensPriceQuery,
|
||||||
TokensPriceQuery_tokens as TokensPrice
|
TokensPriceQuery_tokens as TokensPrice
|
||||||
} from '../@types/subgraph/TokensPriceQuery'
|
} from '../@types/subgraph/TokensPriceQuery'
|
||||||
import { Asset } from '@oceanprotocol/lib'
|
import { Asset, ProviderInstance } from '@oceanprotocol/lib'
|
||||||
import { AssetExtended } from 'src/@types/AssetExtended'
|
import { AssetExtended } from 'src/@types/AssetExtended'
|
||||||
|
import { calculateBuyPrice } from './pool'
|
||||||
|
import { getFixedBuyPrice } from './fixedRateExchange'
|
||||||
|
import { getSiteMetadata } from './siteConfig'
|
||||||
|
import { AccessDetails, OrderPriceAndFees } from 'src/@types/Price'
|
||||||
|
import Decimal from 'decimal.js'
|
||||||
|
|
||||||
const TokensPriceQuery = gql`
|
const TokensPriceQuery = gql`
|
||||||
query TokensPriceQuery($datatokenIds: [ID!], $account: String) {
|
query TokensPriceQuery($datatokenIds: [ID!], $account: String) {
|
||||||
@ -39,6 +44,7 @@ const TokensPriceQuery = gql`
|
|||||||
}
|
}
|
||||||
fixedRateExchanges {
|
fixedRateExchanges {
|
||||||
id
|
id
|
||||||
|
exchangeId
|
||||||
price
|
price
|
||||||
baseToken {
|
baseToken {
|
||||||
symbol
|
symbol
|
||||||
@ -99,6 +105,7 @@ const TokenPriceQuery = gql`
|
|||||||
}
|
}
|
||||||
fixedRateExchanges {
|
fixedRateExchanges {
|
||||||
id
|
id
|
||||||
|
exchangeId
|
||||||
price
|
price
|
||||||
baseToken {
|
baseToken {
|
||||||
symbol
|
symbol
|
||||||
@ -132,25 +139,33 @@ const TokenPriceQuery = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
// TODO: fill in fees after subgraph update
|
||||||
function getAccessDetailsFromTokenPrice(
|
function getAccessDetailsFromTokenPrice(
|
||||||
tokenPrice: TokenPrice | TokensPrice,
|
tokenPrice: TokenPrice | TokensPrice,
|
||||||
timeout?: number
|
timeout?: number
|
||||||
): AccessDetails {
|
): AccessDetails {
|
||||||
const accessDetails = {} as AccessDetails
|
const accessDetails = {} as AccessDetails
|
||||||
|
if (
|
||||||
if (!timeout && !tokenPrice.orders && tokenPrice.orders.length > 0) {
|
tokenPrice &&
|
||||||
|
timeout &&
|
||||||
|
tokenPrice.orders &&
|
||||||
|
tokenPrice.orders.length > 0
|
||||||
|
) {
|
||||||
const order = tokenPrice.orders[0]
|
const order = tokenPrice.orders[0]
|
||||||
accessDetails.owned = Date.now() / 1000 - order.createdTimestamp < timeout
|
accessDetails.isOwned = Date.now() / 1000 - order.createdTimestamp < timeout
|
||||||
accessDetails.validOrderTx = order.tx
|
accessDetails.validOrderTx = order.tx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: fetch order fee from sub query
|
||||||
|
accessDetails.publisherMarketOrderFee = '0'
|
||||||
|
|
||||||
// free is always the best price
|
// free is always the best price
|
||||||
if (tokenPrice.dispensers && tokenPrice.dispensers.length > 0) {
|
if (tokenPrice.dispensers && tokenPrice.dispensers.length > 0) {
|
||||||
const dispenser = tokenPrice.dispensers[0]
|
const dispenser = tokenPrice.dispensers[0]
|
||||||
accessDetails.type = 'free'
|
accessDetails.type = 'free'
|
||||||
accessDetails.addressOrId = dispenser.id
|
accessDetails.addressOrId = dispenser.token.id
|
||||||
accessDetails.price = 0
|
accessDetails.price = '0'
|
||||||
accessDetails.isConsumable = dispenser.active
|
accessDetails.isPurchasable = dispenser.active
|
||||||
accessDetails.datatoken = {
|
accessDetails.datatoken = {
|
||||||
address: dispenser.token.id,
|
address: dispenser.token.id,
|
||||||
name: dispenser.token.name,
|
name: dispenser.token.name,
|
||||||
@ -164,21 +179,21 @@ function getAccessDetailsFromTokenPrice(
|
|||||||
tokenPrice.fixedRateExchanges &&
|
tokenPrice.fixedRateExchanges &&
|
||||||
tokenPrice.fixedRateExchanges.length > 0
|
tokenPrice.fixedRateExchanges.length > 0
|
||||||
) {
|
) {
|
||||||
const fre = tokenPrice.fixedRateExchanges[0]
|
const fixed = tokenPrice.fixedRateExchanges[0]
|
||||||
accessDetails.type = 'fixed'
|
accessDetails.type = 'fixed'
|
||||||
accessDetails.addressOrId = fre.id
|
accessDetails.addressOrId = fixed.exchangeId
|
||||||
accessDetails.price = fre.price
|
accessDetails.price = fixed.price
|
||||||
// in theory we should check dt balance here, we can skip this because in the market we always create fre with minting capabilities.
|
// in theory we should check dt balance here, we can skip this because in the market we always create fre with minting capabilities.
|
||||||
accessDetails.isConsumable = fre.active
|
accessDetails.isPurchasable = fixed.active
|
||||||
accessDetails.baseToken = {
|
accessDetails.baseToken = {
|
||||||
address: fre.baseToken.address,
|
address: fixed.baseToken.address,
|
||||||
name: fre.baseToken.name,
|
name: fixed.baseToken.name,
|
||||||
symbol: fre.baseToken.symbol
|
symbol: fixed.baseToken.symbol
|
||||||
}
|
}
|
||||||
accessDetails.datatoken = {
|
accessDetails.datatoken = {
|
||||||
address: fre.datatoken.address,
|
address: fixed.datatoken.address,
|
||||||
name: fre.datatoken.name,
|
name: fixed.datatoken.name,
|
||||||
symbol: fre.datatoken.symbol
|
symbol: fixed.datatoken.symbol
|
||||||
}
|
}
|
||||||
return accessDetails
|
return accessDetails
|
||||||
}
|
}
|
||||||
@ -188,10 +203,10 @@ function getAccessDetailsFromTokenPrice(
|
|||||||
const pool = tokenPrice.pools[0]
|
const pool = tokenPrice.pools[0]
|
||||||
accessDetails.type = 'dynamic'
|
accessDetails.type = 'dynamic'
|
||||||
accessDetails.addressOrId = pool.id
|
accessDetails.addressOrId = pool.id
|
||||||
// TODO: this needs to be consumePrice
|
|
||||||
accessDetails.price = pool.spotPrice
|
accessDetails.price = pool.spotPrice
|
||||||
// TODO: pool.datatokenLiquidity > 3 is kinda random here, we shouldn't run into this anymore now , needs more thinking/testing
|
// TODO: pool.datatokenLiquidity > 3 is kinda random here, we shouldn't run into this anymore now , needs more thinking/testing
|
||||||
accessDetails.isConsumable = pool.isFinalized && pool.datatokenLiquidity > 3
|
accessDetails.isPurchasable =
|
||||||
|
pool.isFinalized && pool.datatokenLiquidity > 3
|
||||||
accessDetails.baseToken = {
|
accessDetails.baseToken = {
|
||||||
address: pool.baseToken.address,
|
address: pool.baseToken.address,
|
||||||
name: pool.baseToken.name,
|
name: pool.baseToken.name,
|
||||||
@ -208,20 +223,92 @@ function getAccessDetailsFromTokenPrice(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* returns various consume details for the desired datatoken
|
* This will be used to get price including feed before ordering
|
||||||
* @param chain chain on which the datatoken is preset
|
* @param {AssetExtended} asset
|
||||||
* @param datatokenAddress address of the datatoken
|
* @return {Promise<OrdePriceAndFee>}
|
||||||
* @param timeout timeout of the service, only needed if you want order details like owned and validOrderId
|
*/
|
||||||
* @param account account that wants to consume, only needed if you want order details like owned and validOrderId
|
export async function getOrderPriceAndFees(
|
||||||
* @returns AccessDetails
|
asset: AssetExtended,
|
||||||
|
accountId?: string
|
||||||
|
): Promise<OrderPriceAndFees> {
|
||||||
|
const orderPriceAndFee = {
|
||||||
|
price: '0',
|
||||||
|
publisherMarketOrderFee: '0',
|
||||||
|
publisherMarketPoolSwapFee: '0',
|
||||||
|
publisherMarketFixedSwapFee: '0',
|
||||||
|
consumeMarketOrderFee: '0',
|
||||||
|
consumeMarketPoolSwapFee: '0',
|
||||||
|
consumeMarketFixedSwapFee: '0',
|
||||||
|
providerFee: {},
|
||||||
|
opcFee: '0'
|
||||||
|
} as OrderPriceAndFees
|
||||||
|
const { accessDetails } = asset
|
||||||
|
const { appConfig } = getSiteMetadata()
|
||||||
|
|
||||||
|
// fetch publish market order fee
|
||||||
|
orderPriceAndFee.publisherMarketOrderFee =
|
||||||
|
asset.accessDetails.publisherMarketOrderFee
|
||||||
|
// fetch consume market order fee
|
||||||
|
orderPriceAndFee.consumeMarketOrderFee = appConfig.consumeMarketOrderFee
|
||||||
|
// fetch provider fee
|
||||||
|
const initializeData = await ProviderInstance.initialize(
|
||||||
|
asset.id,
|
||||||
|
asset.services[0].id,
|
||||||
|
0,
|
||||||
|
accountId,
|
||||||
|
asset.services[0].serviceEndpoint
|
||||||
|
)
|
||||||
|
orderPriceAndFee.providerFee = initializeData.providerFee
|
||||||
|
|
||||||
|
// fetch price and swap fees
|
||||||
|
switch (accessDetails.type) {
|
||||||
|
case 'dynamic': {
|
||||||
|
const poolPrice = await calculateBuyPrice(accessDetails, asset.chainId)
|
||||||
|
orderPriceAndFee.price = poolPrice.tokenAmount
|
||||||
|
orderPriceAndFee.liquidityProviderSwapFee =
|
||||||
|
poolPrice.liquidityProviderSwapFeeAmount
|
||||||
|
orderPriceAndFee.publisherMarketPoolSwapFee =
|
||||||
|
poolPrice.publishMarketSwapFeeAmount
|
||||||
|
orderPriceAndFee.consumeMarketPoolSwapFee =
|
||||||
|
poolPrice.consumeMarketSwapFeeAmount
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'fixed': {
|
||||||
|
const fixed = await getFixedBuyPrice(accessDetails, asset.chainId)
|
||||||
|
orderPriceAndFee.price = fixed.baseTokenAmount
|
||||||
|
orderPriceAndFee.opcFee = fixed.oceanFeeAmount
|
||||||
|
orderPriceAndFee.publisherMarketFixedSwapFee = fixed.marketFeeAmount
|
||||||
|
// hack because we don't have it in contracts
|
||||||
|
orderPriceAndFee.consumeMarketFixedSwapFee = fixed.consumeMarketFeeAmount
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate full price, we assume that all the values are in ocean, otherwise this will be incorrect
|
||||||
|
orderPriceAndFee.price = new Decimal(orderPriceAndFee.price)
|
||||||
|
.add(new Decimal(orderPriceAndFee.consumeMarketOrderFee))
|
||||||
|
.add(new Decimal(orderPriceAndFee.publisherMarketOrderFee))
|
||||||
|
.add(new Decimal(orderPriceAndFee.providerFee.providerFeeAmount))
|
||||||
|
.toString()
|
||||||
|
return orderPriceAndFee
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} chain
|
||||||
|
* @param {string} datatokenAddress
|
||||||
|
* @param {number=} timeout timout of the service, this is needed to return order details
|
||||||
|
* @param {string=} account account that wants to buy, is needed to return order details
|
||||||
|
* @param {bool=} includeOrderPriceAndFees if false price will be spot price (pool) and rate (fre), if true you will get the order price including fees !! fees not yet done
|
||||||
|
* @returns {Promise<AccessDetails>}
|
||||||
*/
|
*/
|
||||||
export async function getAccessDetails(
|
export async function getAccessDetails(
|
||||||
chain: number,
|
chainId: number,
|
||||||
datatokenAddress: string,
|
datatokenAddress: string,
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
account = ''
|
account = ''
|
||||||
): Promise<AccessDetails> {
|
): Promise<AccessDetails> {
|
||||||
const queryContext = getQueryContext(Number(chain))
|
const queryContext = getQueryContext(Number(chainId))
|
||||||
const tokenQueryResult: OperationResult<
|
const tokenQueryResult: OperationResult<
|
||||||
TokenPriceQuery,
|
TokenPriceQuery,
|
||||||
{ datatokenId: string; account: string }
|
{ datatokenId: string; account: string }
|
||||||
@ -229,7 +316,7 @@ export async function getAccessDetails(
|
|||||||
TokenPriceQuery,
|
TokenPriceQuery,
|
||||||
{
|
{
|
||||||
datatokenId: datatokenAddress.toLowerCase(),
|
datatokenId: datatokenAddress.toLowerCase(),
|
||||||
account: account.toLowerCase()
|
account: account?.toLowerCase()
|
||||||
},
|
},
|
||||||
queryContext
|
queryContext
|
||||||
)
|
)
|
||||||
@ -247,15 +334,14 @@ export async function getAccessDetailsForAssets(
|
|||||||
const chainAssetLists: { [key: number]: string[] } = {}
|
const chainAssetLists: { [key: number]: string[] } = {}
|
||||||
|
|
||||||
for (const asset of assets) {
|
for (const asset of assets) {
|
||||||
// harcoded until we have chainId on assets
|
|
||||||
if (chainAssetLists[asset.chainId]) {
|
if (chainAssetLists[asset.chainId]) {
|
||||||
chainAssetLists[asset.chainId].push(
|
chainAssetLists[asset.chainId].push(
|
||||||
asset?.services[0].datatokenAddress.toLowerCase()
|
asset.services[0].datatokenAddress.toLowerCase()
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
chainAssetLists[asset.chainId] = []
|
chainAssetLists[asset.chainId] = []
|
||||||
chainAssetLists[asset.chainId].push(
|
chainAssetLists[asset.chainId].push(
|
||||||
asset?.services[0].datatokenAddress.toLowerCase()
|
asset.services[0].datatokenAddress.toLowerCase()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -264,20 +350,24 @@ export async function getAccessDetailsForAssets(
|
|||||||
const queryContext = getQueryContext(Number(chainKey))
|
const queryContext = getQueryContext(Number(chainKey))
|
||||||
const tokenQueryResult: OperationResult<
|
const tokenQueryResult: OperationResult<
|
||||||
TokensPriceQuery,
|
TokensPriceQuery,
|
||||||
{ datatokenId: string; account: string }
|
{ datatokenIds: [string]; account: string }
|
||||||
> = await fetchData(
|
> = await fetchData(
|
||||||
TokensPriceQuery,
|
TokensPriceQuery,
|
||||||
{
|
{
|
||||||
datatokenIds: chainAssetLists[chainKey],
|
datatokenIds: chainAssetLists[chainKey],
|
||||||
account: account.toLowerCase()
|
account: account?.toLowerCase()
|
||||||
},
|
},
|
||||||
queryContext
|
queryContext
|
||||||
)
|
)
|
||||||
tokenQueryResult.data?.tokens.forEach((token) => {
|
tokenQueryResult.data?.tokens.forEach((token) => {
|
||||||
const accessDetails = getAccessDetailsFromTokenPrice(token)
|
|
||||||
const currentAsset = assetsExtended.find(
|
const currentAsset = assetsExtended.find(
|
||||||
(asset) => asset.services[0].datatokenAddress.toLowerCase() === token.id
|
(asset) => asset.services[0].datatokenAddress.toLowerCase() === token.id
|
||||||
)
|
)
|
||||||
|
const accessDetails = getAccessDetailsFromTokenPrice(
|
||||||
|
token,
|
||||||
|
currentAsset?.services[0]?.timeout
|
||||||
|
)
|
||||||
|
|
||||||
currentAsset.accessDetails = accessDetails
|
currentAsset.accessDetails = accessDetails
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ export function transformQueryResult(
|
|||||||
result.results = (queryResult.hits.hits || []).map(
|
result.results = (queryResult.hits.hits || []).map(
|
||||||
(hit) => hit._source as Asset
|
(hit) => hit._source as Asset
|
||||||
)
|
)
|
||||||
result.totalResults = queryResult.hits.total
|
result.totalResults = queryResult.hits.total.value
|
||||||
result.totalPages =
|
result.totalPages =
|
||||||
result.totalResults / size < 1
|
result.totalResults / size < 1
|
||||||
? Math.floor(result.totalResults / size)
|
? Math.floor(result.totalResults / size)
|
||||||
@ -163,7 +163,7 @@ export async function getAssetsFromDidList(
|
|||||||
|
|
||||||
const baseParams = {
|
const baseParams = {
|
||||||
chainIds: chainIds,
|
chainIds: chainIds,
|
||||||
filters: [getFilterTerm('id', didList)],
|
filters: [getFilterTerm('_id', didList)],
|
||||||
ignorePurgatory: true
|
ignorePurgatory: true
|
||||||
} as BaseQueryParams
|
} as BaseQueryParams
|
||||||
const query = generateBaseQuery(baseParams)
|
const query = generateBaseQuery(baseParams)
|
||||||
@ -180,19 +180,21 @@ export async function retrieveDDOListByDIDs(
|
|||||||
chainIds: number[],
|
chainIds: number[],
|
||||||
cancelToken: CancelToken
|
cancelToken: CancelToken
|
||||||
): Promise<Asset[]> {
|
): Promise<Asset[]> {
|
||||||
|
if (didList?.length === 0 || chainIds?.length === 0) return []
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (didList?.length === 0 || chainIds?.length === 0) return []
|
|
||||||
const orderedDDOListByDIDList: Asset[] = []
|
const orderedDDOListByDIDList: Asset[] = []
|
||||||
const baseQueryparams = {
|
const baseQueryparams = {
|
||||||
chainIds,
|
chainIds,
|
||||||
filters: [getFilterTerm('id', didList)],
|
filters: [getFilterTerm('_id', didList)],
|
||||||
ignorePurgatory: true
|
ignorePurgatory: true
|
||||||
} as BaseQueryParams
|
} as BaseQueryParams
|
||||||
const query = generateBaseQuery(baseQueryparams)
|
const query = generateBaseQuery(baseQueryparams)
|
||||||
const result = await queryMetadata(query, cancelToken)
|
const result = await queryMetadata(query, cancelToken)
|
||||||
|
|
||||||
didList.forEach((did: string) => {
|
didList.forEach((did: string) => {
|
||||||
const ddo = result.results.find((ddo: Asset) => ddo.id === did)
|
const ddo = result.results.find((ddo: Asset) => ddo.id === did)
|
||||||
orderedDDOListByDIDList.push(ddo)
|
if (ddo) orderedDDOListByDIDList.push(ddo)
|
||||||
})
|
})
|
||||||
return orderedDDOListByDIDList
|
return orderedDDOListByDIDList
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -330,22 +332,21 @@ export async function getPublishedAssets(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getDownloadAssets(
|
export async function getDownloadAssets(
|
||||||
didList: string[],
|
dtList: string[],
|
||||||
tokenOrders: OrdersData[],
|
tokenOrders: OrdersData[],
|
||||||
chainIds: number[],
|
chainIds: number[],
|
||||||
cancelToken: CancelToken
|
cancelToken: CancelToken
|
||||||
): Promise<DownloadedAsset[]> {
|
): Promise<DownloadedAsset[]> {
|
||||||
|
const baseQueryparams = {
|
||||||
|
chainIds,
|
||||||
|
filters: [
|
||||||
|
getFilterTerm('services.datatokenAddress', dtList),
|
||||||
|
getFilterTerm('services.type', 'access')
|
||||||
|
]
|
||||||
|
} as BaseQueryParams
|
||||||
|
const query = generateBaseQuery(baseQueryparams)
|
||||||
try {
|
try {
|
||||||
const baseQueryparams = {
|
|
||||||
chainIds,
|
|
||||||
filters: [
|
|
||||||
getFilterTerm('id', didList),
|
|
||||||
getFilterTerm('service.type', 'access')
|
|
||||||
]
|
|
||||||
} as BaseQueryParams
|
|
||||||
const query = generateBaseQuery(baseQueryparams)
|
|
||||||
const result = await queryMetadata(query, cancelToken)
|
const result = await queryMetadata(query, cancelToken)
|
||||||
|
|
||||||
const downloadedAssets: DownloadedAsset[] = result.results
|
const downloadedAssets: DownloadedAsset[] = result.results
|
||||||
.map((asset) => {
|
.map((asset) => {
|
||||||
const order = tokenOrders.find(
|
const order = tokenOrders.find(
|
||||||
@ -365,6 +366,10 @@ export async function getDownloadAssets(
|
|||||||
|
|
||||||
return downloadedAssets
|
return downloadedAssets
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
LoggerInstance.error(error.message)
|
if (axios.isCancel(error)) {
|
||||||
|
LoggerInstance.log(error.message)
|
||||||
|
} else {
|
||||||
|
LoggerInstance.error(error.message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
63
src/@utils/bezier-spline.ts
Normal file
63
src/@utils/bezier-spline.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/* bezier-spline.js
|
||||||
|
*
|
||||||
|
* computes cubic bezier coefficients to generate a smooth
|
||||||
|
* line through specified points. couples with SVG graphics
|
||||||
|
* for interactive processing.
|
||||||
|
*
|
||||||
|
* For more info see:
|
||||||
|
* http://www.particleincell.com/2012/bezier-splines/
|
||||||
|
*
|
||||||
|
* Lubos Brieda, Particle In Cell Consulting LLC, 2012
|
||||||
|
* you may freely use this algorithm in your codes however where feasible
|
||||||
|
* please include a link/reference to the source article
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* computes control points given knots K, this is the brain of the operation */
|
||||||
|
export function computeControlPoints(K: number[]) {
|
||||||
|
const p1 = []
|
||||||
|
const p2 = []
|
||||||
|
const n = K.length - 1
|
||||||
|
|
||||||
|
/* rhs vector */
|
||||||
|
const a = []
|
||||||
|
const b = []
|
||||||
|
const c = []
|
||||||
|
const r = []
|
||||||
|
|
||||||
|
/* left most segment */
|
||||||
|
a[0] = 0
|
||||||
|
b[0] = 2
|
||||||
|
c[0] = 1
|
||||||
|
r[0] = K[0] + 2 * K[1]
|
||||||
|
|
||||||
|
/* internal segments */
|
||||||
|
for (let i = 1; i < n - 1; i++) {
|
||||||
|
a[i] = 1
|
||||||
|
b[i] = 4
|
||||||
|
c[i] = 1
|
||||||
|
r[i] = 4 * K[i] + 2 * K[i + 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
/* right segment */
|
||||||
|
a[n - 1] = 2
|
||||||
|
b[n - 1] = 7
|
||||||
|
c[n - 1] = 0
|
||||||
|
r[n - 1] = 8 * K[n - 1] + K[n]
|
||||||
|
|
||||||
|
/* solves Ax=b with the Thomas algorithm (from Wikipedia) */
|
||||||
|
for (let i = 1; i < n; i++) {
|
||||||
|
const m: number = a[i] / b[i - 1]
|
||||||
|
b[i] = b[i] - m * c[i - 1]
|
||||||
|
r[i] = r[i] - m * r[i - 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
p1[n - 1] = r[n - 1] / b[n - 1]
|
||||||
|
for (let i = n - 2; i >= 0; --i) p1[i] = (r[i] - c[i] * p1[i + 1]) / b[i]
|
||||||
|
|
||||||
|
/* we have p1, now compute p2 */
|
||||||
|
for (let i = 0; i < n - 1; i++) p2[i] = 2 * K[i + 1] - p1[i + 1]
|
||||||
|
|
||||||
|
p2[n - 1] = 0.5 * (K[n] + p1[n - 1])
|
||||||
|
|
||||||
|
return { p1: p1.map((p) => Math.floor(p)), p2: p2.map((p) => Math.floor(p)) }
|
||||||
|
}
|
@ -1,89 +1,19 @@
|
|||||||
export const feedback: { [key in number]: string } = {
|
// TODO: can be better
|
||||||
99: 'Decrypting file URL...',
|
export function getOrderFeedback(
|
||||||
0: '1/3 Looking for data token. Buying if none found...',
|
baseTokenSymbol: string,
|
||||||
1: '2/3 Transfering data token.',
|
datatokenSymbol: string
|
||||||
2: '3/3 Payment confirmed. Requesting access...'
|
): { [key in number]: string } {
|
||||||
}
|
return {
|
||||||
|
0: `Approving and buying one ${datatokenSymbol} from pool`,
|
||||||
export const publishFeedback: { [key in number]: string } = {
|
1: `Ordering asset`,
|
||||||
0: '1/5 Creating datatoken ...',
|
2: `Approving ${baseTokenSymbol} and ordering asset`,
|
||||||
2: '2/5 Encrypting files ...',
|
3: 'Generating signature to access download url'
|
||||||
4: '3/5 Storing ddo ...',
|
}
|
||||||
6: '4/5 Minting tokens ...',
|
|
||||||
8: '5/5 Asset published succesfully'
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: do something with this object,
|
|
||||||
// consumeStep should probably return one of those strings
|
|
||||||
// instead of just a number
|
|
||||||
export const consumeFeedback: { [key in number]: string } = {
|
|
||||||
...feedback,
|
|
||||||
3: '3/3 Access granted. Consuming file...'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: customize for compute
|
// TODO: customize for compute
|
||||||
export const computeFeedback: { [key in number]: string } = {
|
export const computeFeedback: { [key in number]: string } = {
|
||||||
0: '1/3 Ordering asset...',
|
0: 'Ordering asset...',
|
||||||
1: '2/3 Transfering data token.',
|
1: 'Transfering datatoken.',
|
||||||
2: '3/3 Access granted. Starting job...'
|
2: 'Access granted. Starting job...'
|
||||||
}
|
|
||||||
|
|
||||||
export function getCreatePricingPoolFeedback(dtSymbol: string): {
|
|
||||||
[key: number]: string
|
|
||||||
} {
|
|
||||||
return {
|
|
||||||
99: `Minting ${dtSymbol} ...`,
|
|
||||||
0: 'Creating pool ...',
|
|
||||||
1: `Approving ${dtSymbol} ...`,
|
|
||||||
2: 'Approving OCEAN ...',
|
|
||||||
3: 'Setup pool ...',
|
|
||||||
4: 'Pool created.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCreatePricingExchangeFeedback(dtSymbol: string): {
|
|
||||||
[key: number]: string
|
|
||||||
} {
|
|
||||||
return {
|
|
||||||
99: `Minting ${dtSymbol} ...`,
|
|
||||||
0: 'Creating exchange ...',
|
|
||||||
1: `Approving ${dtSymbol} ...`,
|
|
||||||
2: 'Fixed exchange created.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCreateFreePricingFeedback(dtSymbol: string): {
|
|
||||||
[key: number]: string
|
|
||||||
} {
|
|
||||||
return {
|
|
||||||
99: `Creating ${dtSymbol} faucet...`,
|
|
||||||
0: 'Setting faucet as minter ...',
|
|
||||||
1: 'Approving minter...',
|
|
||||||
2: 'Faucet created.'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getBuyDTFeedback(dtSymbol: string): { [key: number]: string } {
|
|
||||||
return {
|
|
||||||
1: '1/3 Approving OCEAN ...',
|
|
||||||
2: `2/3 Buying ${dtSymbol} ...`,
|
|
||||||
3: `3/3 ${dtSymbol} bought.`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getSellDTFeedback(dtSymbol: string): { [key: number]: string } {
|
|
||||||
return {
|
|
||||||
1: '1/3 Approving OCEAN ...',
|
|
||||||
2: `2/3 Selling ${dtSymbol} ...`,
|
|
||||||
3: `3/3 ${dtSymbol} sold.`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getDispenseFeedback(dtSymbol: string): {
|
|
||||||
[key: number]: string
|
|
||||||
} {
|
|
||||||
return {
|
|
||||||
1: `1/2 Requesting ${dtSymbol}...`,
|
|
||||||
2: `2/2 Received ${dtSymbol}.`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
34
src/@utils/fixedRateExchange.ts
Normal file
34
src/@utils/fixedRateExchange.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { FixedRateExchange, PriceAndFees } from '@oceanprotocol/lib'
|
||||||
|
import { AccessDetails } from 'src/@types/Price'
|
||||||
|
import Web3 from 'web3'
|
||||||
|
import { getOceanConfig } from './ocean'
|
||||||
|
import { getDummyWeb3 } from './web3'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used to calculate the price to buy one datatoken from a fixed rate exchange. You need to pass either a web3 object or a chainId. If you pass a chainId a dummy web3 object will be created
|
||||||
|
* @param {AccessDetails} accessDetails
|
||||||
|
* @param {number} chainId
|
||||||
|
* @param {Web3?} web3
|
||||||
|
* @return {Promise<PriceAndFees>}
|
||||||
|
*/
|
||||||
|
export async function getFixedBuyPrice(
|
||||||
|
accessDetails: AccessDetails,
|
||||||
|
chainId?: number,
|
||||||
|
web3?: Web3
|
||||||
|
): Promise<PriceAndFees> {
|
||||||
|
if (!web3 && !chainId)
|
||||||
|
throw new Error("web3 and chainId can't be undefined at the same time!")
|
||||||
|
|
||||||
|
if (!web3) {
|
||||||
|
web3 = await getDummyWeb3(chainId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = getOceanConfig(chainId)
|
||||||
|
|
||||||
|
const fixed = new FixedRateExchange(web3, config.fixedRateExchangeAddress)
|
||||||
|
const estimatedPrice = await fixed.calcBaseInGivenOutDT(
|
||||||
|
accessDetails.addressOrId,
|
||||||
|
'1'
|
||||||
|
)
|
||||||
|
return estimatedPrice
|
||||||
|
}
|
@ -6,8 +6,8 @@ import matter from 'gray-matter'
|
|||||||
// Next.js specifics to be used in getStaticProps / getStaticPaths
|
// Next.js specifics to be used in getStaticProps / getStaticPaths
|
||||||
// to automatically generate pages from Markdown files in `src/pages/[slug].tsx`.
|
// to automatically generate pages from Markdown files in `src/pages/[slug].tsx`.
|
||||||
//
|
//
|
||||||
const pagesDirectory = join(process.cwd(), 'content', 'pages')
|
// const pagesDirectory = join(process.cwd(), 'content', 'pages')
|
||||||
|
const pagesDirectory = './content/pages'
|
||||||
export interface PageData {
|
export interface PageData {
|
||||||
slug: string
|
slug: string
|
||||||
frontmatter: { [key: string]: any }
|
frontmatter: { [key: string]: any }
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { renderStaticWaves } from './oceanWaves'
|
import { SvgWaves } from './SvgWaves'
|
||||||
|
|
||||||
// https://docs.opensea.io/docs/metadata-standards
|
// https://docs.opensea.io/docs/metadata-standards
|
||||||
export interface NftMetadata {
|
export interface NftMetadata {
|
||||||
@ -21,6 +21,7 @@ function encodeSvg(svgString: string): string {
|
|||||||
? '<svg'
|
? '<svg'
|
||||||
: '<svg xmlns="http://www.w3.org/2000/svg"'
|
: '<svg xmlns="http://www.w3.org/2000/svg"'
|
||||||
)
|
)
|
||||||
|
.replace('></path>', '/>')
|
||||||
.replace(/"/g, "'")
|
.replace(/"/g, "'")
|
||||||
.replace(/%/g, '%25')
|
.replace(/%/g, '%25')
|
||||||
.replace(/#/g, '%23')
|
.replace(/#/g, '%23')
|
||||||
@ -32,41 +33,40 @@ function encodeSvg(svgString: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function generateNftMetadata(): NftMetadata {
|
export function generateNftMetadata(): NftMetadata {
|
||||||
// TODO: crop image properly in the end as generated SVG waves are a super-wide image,
|
const waves = new SvgWaves()
|
||||||
// and add a filled background deciding on either black or white.
|
const svg = waves.generateSvg()
|
||||||
const image = renderStaticWaves()
|
|
||||||
// const image = new XMLSerializer().serializeToString(waves)
|
// TODO: figure out if also image URI needs base64 encoding
|
||||||
// const image = `<svg><path d="M0 10.4304L16.3396 10.4304L8.88727 17.6833L10.2401 19L20 9.5L10.2401 0L8.88727 1.31491L16.3396 8.56959L0 8.56959V10.4304Z" /></svg>`
|
// e.g. 'data:image/svg+xml;base64,'
|
||||||
|
// generated SVG embedded as 'data:image/svg+xml' and encoded characters
|
||||||
|
const imageData = `data:image/svg+xml,${encodeSvg(svg.outerHTML)}`
|
||||||
|
|
||||||
const newNft: NftMetadata = {
|
const newNft: NftMetadata = {
|
||||||
name: 'Ocean Asset v4 NFT',
|
name: 'Ocean Asset NFT',
|
||||||
symbol: 'OCEAN-NFT',
|
symbol: 'OCEAN-NFT',
|
||||||
description: `This NFT represents an asset in the Ocean Protocol v4 ecosystem.`,
|
description: `This NFT represents an asset in the Ocean Protocol v4 ecosystem.`,
|
||||||
// TODO: ideally this includes the final DID
|
// TODO: ideally this includes the final DID
|
||||||
external_url: 'https://market.oceanprotocol.com',
|
external_url: 'https://market.oceanprotocol.com',
|
||||||
background_color: '141414', // dark background
|
background_color: '141414', // dark background
|
||||||
// TODO: figure out if also image URI needs base64 encoding
|
image_data: imageData
|
||||||
// generated SVG embedded as 'data:image/svg+xml' and encoded characters
|
|
||||||
image_data: `data:image/svg+xml,${encodeSvg(image)}`
|
|
||||||
// generated SVG embedded as 'data:image/svg+xml;base64'
|
|
||||||
// image: `data:image/svg+xml;base64,${window?.btoa(image)}`
|
|
||||||
// image: `data:image/svg+xml;base64,${Buffer.from(image).toString('base64')}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return newNft
|
return newNft
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateNftCreateData(nftMetadata: NftMetadata): any {
|
export function generateNftCreateData(nftMetadata: NftMetadata): any {
|
||||||
|
// TODO: figure out if Buffer.from method is working in browser in final build
|
||||||
|
// as BTOA is deprecated.
|
||||||
|
// tokenURI: window?.btoa(JSON.stringify(nftMetadata))
|
||||||
|
const encodedMetadata = Buffer.from(JSON.stringify(nftMetadata)).toString(
|
||||||
|
'base64'
|
||||||
|
)
|
||||||
|
|
||||||
const nftCreateData = {
|
const nftCreateData = {
|
||||||
name: nftMetadata.name,
|
name: nftMetadata.name,
|
||||||
symbol: nftMetadata.symbol,
|
symbol: nftMetadata.symbol,
|
||||||
templateIndex: 1,
|
templateIndex: 1,
|
||||||
// Gas estimation fails if we add our huge tokenURI
|
tokenURI: `data:application/json;base64,${encodedMetadata}`
|
||||||
tokenURI: '{url:"https://coolImage.com, name: "just for test"}'
|
|
||||||
// TODO: figure out if Buffer.from method is working in browser in final build
|
|
||||||
// as BTOA is deprecated.
|
|
||||||
// tokenURI: window?.btoa(JSON.stringify(nftMetadata))
|
|
||||||
// tokenURI: Buffer.from(JSON.stringify(nftMetadata)).toString('base64') // should end up as data:application/json;base64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nftCreateData
|
return nftCreateData
|
||||||
|
@ -24,3 +24,9 @@ export function compareAsBN(balance: string, price: string): boolean {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Random number from interval
|
||||||
|
export function randomIntFromInterval(min: number, max: number): number {
|
||||||
|
// min and max are included
|
||||||
|
return Math.floor(Math.random() * (max - min + 1) + min)
|
||||||
|
}
|
||||||
|
@ -1,49 +0,0 @@
|
|||||||
import * as d3 from 'd3'
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Ocean Protocol D3 waves
|
|
||||||
* https://oceanprotocol.com/art
|
|
||||||
* Based off of Bostock's Circle Wave
|
|
||||||
* https://bl.ocks.org/mbostock/2d466ec3417722e3568cd83fc35338e3
|
|
||||||
*/
|
|
||||||
export function renderStaticWaves(): string {
|
|
||||||
const svg = d3.create('svg')
|
|
||||||
const width = 1000
|
|
||||||
const height = 250
|
|
||||||
const x = d3.scaleLinear().range([0, width])
|
|
||||||
const angles = d3.range(Math.random(), 4 * Math.PI, Math.PI / 20)
|
|
||||||
|
|
||||||
const path = svg
|
|
||||||
// .append('rect')
|
|
||||||
// .attr('fill', '#fff')
|
|
||||||
// .attr('width', '100%')
|
|
||||||
// .attr('height', '100%')
|
|
||||||
.append('g')
|
|
||||||
.attr('transform', `translate(${width / -4}, ${height / 2})`)
|
|
||||||
.attr('fill', 'none')
|
|
||||||
.attr('stroke-width', 2)
|
|
||||||
.selectAll('path')
|
|
||||||
.data(['#FF4092', '#E000CF', '#8B98A9', '#E2E2E2'])
|
|
||||||
.enter()
|
|
||||||
.append('path')
|
|
||||||
.attr('stroke', (d) => d)
|
|
||||||
.style('mix-blend-mode', 'darken')
|
|
||||||
.datum((d, i) => {
|
|
||||||
return d3
|
|
||||||
.line()
|
|
||||||
.curve(d3.curveBasisOpen)
|
|
||||||
.x((angles: any) => x(angles / 4))
|
|
||||||
.y((angles: any) => {
|
|
||||||
const t = d3.now() / 3000
|
|
||||||
return (
|
|
||||||
Math.cos(angles * 8 - (i * 2 * Math.PI) / 10 + t) *
|
|
||||||
Math.pow((2 + Math.cos(angles - t)) / 2, 4) *
|
|
||||||
15
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
path.attr('d', (d) => d(angles as any))
|
|
||||||
|
|
||||||
return `<svg>${svg.node().innerHTML}</svg>`
|
|
||||||
}
|
|
101
src/@utils/order.ts
Normal file
101
src/@utils/order.ts
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import {
|
||||||
|
approve,
|
||||||
|
Datatoken,
|
||||||
|
FreOrderParams,
|
||||||
|
OrderParams,
|
||||||
|
ProviderInstance
|
||||||
|
} from '@oceanprotocol/lib'
|
||||||
|
import { AssetExtended } from 'src/@types/AssetExtended'
|
||||||
|
import Web3 from 'web3'
|
||||||
|
import { getOceanConfig } from './ocean'
|
||||||
|
import { TransactionReceipt } from 'web3-eth'
|
||||||
|
import { getSiteMetadata } from './siteConfig'
|
||||||
|
import { OrderPriceAndFees } from 'src/@types/Price'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For pool you need to buy the datatoken beforehand, this always assumes you want to order the first service
|
||||||
|
* @param web3
|
||||||
|
* @param asset
|
||||||
|
* @param accountId
|
||||||
|
* @returns {TransactionReceipt} receipt of the order
|
||||||
|
*/
|
||||||
|
export async function order(
|
||||||
|
web3: Web3,
|
||||||
|
asset: AssetExtended,
|
||||||
|
orderPriceAndFees: OrderPriceAndFees,
|
||||||
|
accountId: string
|
||||||
|
): Promise<TransactionReceipt> {
|
||||||
|
const datatoken = new Datatoken(web3)
|
||||||
|
const config = getOceanConfig(asset.chainId)
|
||||||
|
const { appConfig } = getSiteMetadata()
|
||||||
|
|
||||||
|
const initializeData = await ProviderInstance.initialize(
|
||||||
|
asset.id,
|
||||||
|
asset.services[0].id,
|
||||||
|
0,
|
||||||
|
accountId,
|
||||||
|
asset.services[0].serviceEndpoint
|
||||||
|
)
|
||||||
|
|
||||||
|
const orderParams = {
|
||||||
|
consumer: accountId,
|
||||||
|
serviceIndex: 0,
|
||||||
|
_providerFee: initializeData.providerFee,
|
||||||
|
_consumeMarketFee: {
|
||||||
|
consumeMarketFeeAddress: appConfig.marketFeeAddress,
|
||||||
|
consumeMarketFeeAmount: appConfig.consumeMarketOrderFee,
|
||||||
|
consumeMarketFeeToken: config.oceanTokenAddress
|
||||||
|
}
|
||||||
|
} as OrderParams
|
||||||
|
|
||||||
|
// TODO: we need to approve provider fee
|
||||||
|
switch (asset.accessDetails?.type) {
|
||||||
|
case 'fixed': {
|
||||||
|
// this assumes all fees are in ocean
|
||||||
|
const txApprove = await approve(
|
||||||
|
web3,
|
||||||
|
accountId,
|
||||||
|
asset.accessDetails.baseToken.address,
|
||||||
|
asset.accessDetails.datatoken.address,
|
||||||
|
orderPriceAndFees.price,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
const freParams = {
|
||||||
|
exchangeContract: config.fixedRateExchangeAddress,
|
||||||
|
exchangeId: asset.accessDetails.addressOrId,
|
||||||
|
maxBaseTokenAmount: orderPriceAndFees.price,
|
||||||
|
swapMarketFee: appConfig.consumeMarketFixedSwapFee,
|
||||||
|
marketFeeAddress: appConfig.marketFeeAddress
|
||||||
|
} as FreOrderParams
|
||||||
|
const tx = await datatoken.buyFromFreAndOrder(
|
||||||
|
asset.accessDetails.datatoken.address,
|
||||||
|
accountId,
|
||||||
|
orderParams,
|
||||||
|
freParams
|
||||||
|
)
|
||||||
|
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
case 'dynamic': {
|
||||||
|
const tx = await datatoken.startOrder(
|
||||||
|
asset.accessDetails.datatoken.address,
|
||||||
|
accountId,
|
||||||
|
accountId,
|
||||||
|
0,
|
||||||
|
initializeData.providerFee
|
||||||
|
)
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'free': {
|
||||||
|
const tx = await datatoken.buyFromDispenserAndOrder(
|
||||||
|
asset.services[0].datatokenAddress,
|
||||||
|
accountId,
|
||||||
|
orderParams,
|
||||||
|
config.dispenserAddress
|
||||||
|
)
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
75
src/@utils/pool.ts
Normal file
75
src/@utils/pool.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { approve, Pool, PoolPriceAndFees } from '@oceanprotocol/lib'
|
||||||
|
import Web3 from 'web3'
|
||||||
|
import { getSiteMetadata } from './siteConfig'
|
||||||
|
import { getDummyWeb3 } from './web3'
|
||||||
|
import { TransactionReceipt } from 'web3-eth'
|
||||||
|
import Decimal from 'decimal.js'
|
||||||
|
import { AccessDetails } from 'src/@types/Price'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used to calculate the price to buy one datatoken from a pool, that is different from spot price. You need to pass either a web3 object or a chainId. If you pass a chainId a dummy web3 object will be created
|
||||||
|
* @param {AccessDetails} accessDetails
|
||||||
|
* @param {Web3?} [web3]
|
||||||
|
* @param {number?} [chainId]
|
||||||
|
* @return {Promise<PoolPriceAndFees>}
|
||||||
|
*/
|
||||||
|
export async function calculateBuyPrice(
|
||||||
|
accessDetails: AccessDetails,
|
||||||
|
chainId?: number,
|
||||||
|
web3?: Web3
|
||||||
|
): Promise<PoolPriceAndFees> {
|
||||||
|
if (!web3 && !chainId)
|
||||||
|
throw new Error("web3 and chainId can't be undefined at the same time!")
|
||||||
|
|
||||||
|
if (!web3) {
|
||||||
|
web3 = await getDummyWeb3(chainId)
|
||||||
|
}
|
||||||
|
|
||||||
|
const pool = new Pool(web3)
|
||||||
|
const { appConfig } = getSiteMetadata()
|
||||||
|
const estimatedPrice = await pool.getAmountInExactOut(
|
||||||
|
accessDetails.addressOrId,
|
||||||
|
accessDetails.baseToken.address,
|
||||||
|
accessDetails.datatoken.address,
|
||||||
|
'1',
|
||||||
|
appConfig.consumeMarketPoolSwapFee
|
||||||
|
)
|
||||||
|
|
||||||
|
return estimatedPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function buyDtFromPool(
|
||||||
|
accessDetails: AccessDetails,
|
||||||
|
accountId: string,
|
||||||
|
web3: Web3
|
||||||
|
): Promise<TransactionReceipt> {
|
||||||
|
const pool = new Pool(web3)
|
||||||
|
const { appConfig } = getSiteMetadata()
|
||||||
|
// we need to calculate the actual price to buy one datatoken
|
||||||
|
const dtPrice = await calculateBuyPrice(accessDetails, null, web3)
|
||||||
|
const approveTx = await approve(
|
||||||
|
web3,
|
||||||
|
accountId,
|
||||||
|
accessDetails.baseToken.address,
|
||||||
|
accessDetails.addressOrId,
|
||||||
|
dtPrice.tokenAmount,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
const result = await pool.swapExactAmountOut(
|
||||||
|
accountId,
|
||||||
|
accessDetails.addressOrId,
|
||||||
|
{
|
||||||
|
marketFeeAddress: appConfig.marketFeeAddress,
|
||||||
|
tokenIn: accessDetails.baseToken.address,
|
||||||
|
tokenOut: accessDetails.datatoken.address
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// this is just to be safe
|
||||||
|
maxAmountIn: new Decimal(dtPrice.tokenAmount).mul(10).toString(),
|
||||||
|
swapMarketFee: appConfig.consumeMarketPoolSwapFee,
|
||||||
|
tokenAmountOut: '1'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
@ -1,10 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
|
downloadFileBrowser,
|
||||||
FileMetadata,
|
FileMetadata,
|
||||||
LoggerInstance,
|
LoggerInstance,
|
||||||
ProviderInstance
|
ProviderInstance
|
||||||
} from '@oceanprotocol/lib'
|
} from '@oceanprotocol/lib'
|
||||||
|
import { AssetExtended } from 'src/@types/AssetExtended'
|
||||||
|
import Web3 from 'web3'
|
||||||
|
|
||||||
// TODO: Why do we have these functions ?!?!?!
|
// TODO: Why do we have these one line functions ?!?!?!
|
||||||
export async function getEncryptedFiles(
|
export async function getEncryptedFiles(
|
||||||
files: FileMetadata[],
|
files: FileMetadata[],
|
||||||
providerUrl: string
|
providerUrl: string
|
||||||
@ -46,3 +49,21 @@ export async function getFileUrlInfo(
|
|||||||
LoggerInstance.error(error.message)
|
LoggerInstance.error(error.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function downloadFile(
|
||||||
|
web3: Web3,
|
||||||
|
asset: AssetExtended,
|
||||||
|
accountId: string,
|
||||||
|
validOrderTx?: string
|
||||||
|
) {
|
||||||
|
const downloadUrl = await ProviderInstance.getDownloadUrl(
|
||||||
|
asset.id,
|
||||||
|
accountId,
|
||||||
|
asset.services[0].id,
|
||||||
|
0,
|
||||||
|
validOrderTx || asset.accessDetails.validOrderTx,
|
||||||
|
asset.services[0].serviceEndpoint,
|
||||||
|
web3
|
||||||
|
)
|
||||||
|
await downloadFileBrowser(downloadUrl)
|
||||||
|
}
|
||||||
|
12
src/@utils/siteConfig.ts
Normal file
12
src/@utils/siteConfig.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { UseSiteMetadata } from '@hooks/useSiteMetadata/types'
|
||||||
|
import siteContent from '../../content/site.json'
|
||||||
|
import appConfig from '../../app.config'
|
||||||
|
|
||||||
|
export function getSiteMetadata(): UseSiteMetadata {
|
||||||
|
const siteMeta: UseSiteMetadata = {
|
||||||
|
...siteContent,
|
||||||
|
appConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
return siteMeta
|
||||||
|
}
|
@ -12,7 +12,10 @@ import {
|
|||||||
PoolShares as PoolSharesList,
|
PoolShares as PoolSharesList,
|
||||||
PoolShares_poolShares as PoolShare
|
PoolShares_poolShares as PoolShare
|
||||||
} from '../@types/subgraph/PoolShares'
|
} from '../@types/subgraph/PoolShares'
|
||||||
import { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData'
|
import {
|
||||||
|
OrdersData_orders as OrdersData,
|
||||||
|
OrdersData_orders_datatoken as OrdersDatatoken
|
||||||
|
} from '../@types/subgraph/OrdersData'
|
||||||
import { UserSalesQuery as UsersSalesList } from '../@types/subgraph/UserSalesQuery'
|
import { UserSalesQuery as UsersSalesList } from '../@types/subgraph/UserSalesQuery'
|
||||||
|
|
||||||
export interface UserLiquidity {
|
export interface UserLiquidity {
|
||||||
@ -71,6 +74,7 @@ const HighestLiquidityAssets = gql`
|
|||||||
symbol
|
symbol
|
||||||
}
|
}
|
||||||
baseTokenLiquidity
|
baseTokenLiquidity
|
||||||
|
datatokenLiquidity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@ -140,7 +144,11 @@ const UserTokenOrders = gql`
|
|||||||
orderDirection: desc
|
orderDirection: desc
|
||||||
where: { consumer: $user }
|
where: { consumer: $user }
|
||||||
) {
|
) {
|
||||||
|
consumer {
|
||||||
|
id
|
||||||
|
}
|
||||||
datatoken {
|
datatoken {
|
||||||
|
id
|
||||||
address
|
address
|
||||||
symbol
|
symbol
|
||||||
}
|
}
|
||||||
@ -154,14 +162,11 @@ const UserTokenOrders = gql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
// TODO: counting orders might be enough here to get sales for a user
|
|
||||||
const UserSalesQuery = gql`
|
const UserSalesQuery = gql`
|
||||||
query UserSalesQuery($userSalesId: ID) {
|
query UserSalesQuery($user: String!) {
|
||||||
users(where: { id: $userSalesId }) {
|
users(where: { id: $user }) {
|
||||||
id
|
id
|
||||||
orders(first: 10000) {
|
totalSales
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
@ -294,6 +299,7 @@ export async function getHighestLiquidityDatatokens(
|
|||||||
highestLiquidityAssets.sort(
|
highestLiquidityAssets.sort(
|
||||||
(a, b) => b.baseTokenLiquidity - a.baseTokenLiquidity
|
(a, b) => b.baseTokenLiquidity - a.baseTokenLiquidity
|
||||||
)
|
)
|
||||||
|
|
||||||
for (let i = 0; i < highestLiquidityAssets.length; i++) {
|
for (let i = 0; i < highestLiquidityAssets.length; i++) {
|
||||||
if (!highestLiquidityAssets[i]?.datatoken?.address) continue
|
if (!highestLiquidityAssets[i]?.datatoken?.address) continue
|
||||||
dtList.push(highestLiquidityAssets[i].datatoken.address)
|
dtList.push(highestLiquidityAssets[i].datatoken.address)
|
||||||
@ -376,9 +382,8 @@ export async function getUserTokenOrders(
|
|||||||
variables,
|
variables,
|
||||||
chainIds
|
chainIds
|
||||||
)
|
)
|
||||||
|
|
||||||
for (let i = 0; i < tokenOrders?.length; i++) {
|
for (let i = 0; i < tokenOrders?.length; i++) {
|
||||||
tokenOrders[i].tokenOrders.forEach((tokenOrder: OrdersData) => {
|
tokenOrders[i].orders.forEach((tokenOrder: OrdersData) => {
|
||||||
data.push(tokenOrder)
|
data.push(tokenOrder)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -393,7 +398,7 @@ export async function getUserSales(
|
|||||||
accountId: string,
|
accountId: string,
|
||||||
chainIds: number[]
|
chainIds: number[]
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const variables = { userSalesId: accountId?.toLowerCase() }
|
const variables = { user: accountId?.toLowerCase() }
|
||||||
try {
|
try {
|
||||||
const userSales = await fetchDataForMultipleChains(
|
const userSales = await fetchDataForMultipleChains(
|
||||||
UserSalesQuery,
|
UserSalesQuery,
|
||||||
@ -403,7 +408,7 @@ export async function getUserSales(
|
|||||||
let salesSum = 0
|
let salesSum = 0
|
||||||
for (let i = 0; i < userSales.length; i++) {
|
for (let i = 0; i < userSales.length; i++) {
|
||||||
if (userSales[i].users.length > 0) {
|
if (userSales[i].users.length > 0) {
|
||||||
salesSum += userSales[i].users[0].nrSales
|
salesSum += parseInt(userSales[i].users[0].totalSales)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return salesSum
|
return salesSum
|
||||||
@ -432,7 +437,7 @@ export async function getTopAssetsPublishers(
|
|||||||
if (publishersIndex === -1) {
|
if (publishersIndex === -1) {
|
||||||
const publisher: AccountTeaserVM = {
|
const publisher: AccountTeaserVM = {
|
||||||
address: fetchedUsers.data.users[i].id,
|
address: fetchedUsers.data.users[i].id,
|
||||||
nrSales: fetchedUsers.data.users[i].orders.length
|
nrSales: fetchedUsers.data.users[i].totalSales
|
||||||
}
|
}
|
||||||
publisherSales.push(publisher)
|
publisherSales.push(publisher)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { getNetworkDisplayName } from '@hooks/useNetworkMetadata'
|
import { getNetworkDisplayName } from '@hooks/useNetworkMetadata'
|
||||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||||
|
import Web3 from 'web3'
|
||||||
import { getOceanConfig } from './ocean'
|
import { getOceanConfig } from './ocean'
|
||||||
|
|
||||||
export function accountTruncate(account: string): string {
|
export function accountTruncate(account: string): string {
|
||||||
@ -8,6 +9,15 @@ export function accountTruncate(account: string): string {
|
|||||||
const truncated = account.replace(middle, '…')
|
const truncated = account.replace(middle, '…')
|
||||||
return truncated
|
return truncated
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* returns a dummy web3 instance, only usable to get info from the chain
|
||||||
|
* @param chainId
|
||||||
|
* @returns Web3 instance
|
||||||
|
*/
|
||||||
|
export async function getDummyWeb3(chainId: number): Promise<Web3> {
|
||||||
|
const config = getOceanConfig(chainId)
|
||||||
|
return new Web3(config.nodeUri)
|
||||||
|
}
|
||||||
|
|
||||||
export async function addCustomNetwork(
|
export async function addCustomNetwork(
|
||||||
web3Provider: any,
|
web3Provider: any,
|
||||||
|
@ -10,6 +10,7 @@ import { useIsMounted } from '@hooks/useIsMounted'
|
|||||||
import { AssetExtended } from 'src/@types/AssetExtended'
|
import { AssetExtended } from 'src/@types/AssetExtended'
|
||||||
import { Asset } from '@oceanprotocol/lib'
|
import { Asset } from '@oceanprotocol/lib'
|
||||||
import { getAccessDetailsForAssets } from '@utils/accessDetailsAndPricing'
|
import { getAccessDetailsForAssets } from '@utils/accessDetailsAndPricing'
|
||||||
|
import { useWeb3 } from '@context/Web3'
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
const cx = classNames.bind(styles)
|
||||||
|
|
||||||
@ -43,6 +44,7 @@ export default function AssetList({
|
|||||||
noPublisher
|
noPublisher
|
||||||
}: AssetListProps): ReactElement {
|
}: AssetListProps): ReactElement {
|
||||||
const { chainIds } = useUserPreferences()
|
const { chainIds } = useUserPreferences()
|
||||||
|
const { accountId } = useWeb3()
|
||||||
const [assetsWithPrices, setAssetsWithPrices] = useState<AssetExtended[]>()
|
const [assetsWithPrices, setAssetsWithPrices] = useState<AssetExtended[]>()
|
||||||
const [loading, setLoading] = useState<boolean>(isLoading)
|
const [loading, setLoading] = useState<boolean>(isLoading)
|
||||||
const isMounted = useIsMounted()
|
const isMounted = useIsMounted()
|
||||||
@ -51,14 +53,16 @@ export default function AssetList({
|
|||||||
if (!assets) return
|
if (!assets) return
|
||||||
setAssetsWithPrices(assets as AssetExtended[])
|
setAssetsWithPrices(assets as AssetExtended[])
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
|
||||||
async function fetchPrices() {
|
async function fetchPrices() {
|
||||||
const assetsWithPrices = await getAccessDetailsForAssets(assets)
|
const assetsWithPrices = await getAccessDetailsForAssets(
|
||||||
|
assets,
|
||||||
|
accountId || ''
|
||||||
|
)
|
||||||
if (!isMounted()) return
|
if (!isMounted()) return
|
||||||
setAssetsWithPrices([...assetsWithPrices])
|
setAssetsWithPrices([...assetsWithPrices])
|
||||||
}
|
}
|
||||||
fetchPrices()
|
fetchPrices()
|
||||||
}, [assets, isMounted])
|
}, [assets, isMounted, accountId])
|
||||||
|
|
||||||
// // This changes the page field inside the query
|
// // This changes the page field inside the query
|
||||||
function handlePageChange(selected: number) {
|
function handlePageChange(selected: number) {
|
||||||
|
@ -31,6 +31,8 @@ interface ButtonBuyProps {
|
|||||||
algorithmConsumableStatus?: number
|
algorithmConsumableStatus?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: we need to take a look at these messages
|
||||||
|
|
||||||
function getConsumeHelpText(
|
function getConsumeHelpText(
|
||||||
dtBalance: string,
|
dtBalance: string,
|
||||||
dtSymbol: string,
|
dtSymbol: string,
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
.datatoken {
|
.datatoken {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--spacer);
|
gap: var(--spacer);
|
||||||
grid-template-columns: 1fr 11fr;
|
grid-template-columns: none;
|
||||||
|
margin-top: calc(var(--spacer) / 2);
|
||||||
margin-bottom: var(--spacer);
|
margin-bottom: var(--spacer);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
@ -20,9 +21,16 @@
|
|||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
fill: var(--brand-violet);
|
fill: var(--brand-violet);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image svg {
|
.image svg {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 40rem) {
|
||||||
|
.datatoken {
|
||||||
|
grid-template-columns: 1fr 11fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
67
src/components/@shared/FormFields/Nft/TxFee.tsx
Normal file
67
src/components/@shared/FormFields/Nft/TxFee.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import React, { ReactElement, useEffect, useState } from 'react'
|
||||||
|
import { usePrices } from '@context/Prices'
|
||||||
|
import { useWeb3 } from '@context/Web3'
|
||||||
|
import useNftFactory from '@hooks/contracts/useNftFactory'
|
||||||
|
import { NftFactory } from '@oceanprotocol/lib'
|
||||||
|
import Conversion from '@shared/Price/Conversion'
|
||||||
|
import { generateNftCreateData, NftMetadata } from '@utils/nft'
|
||||||
|
|
||||||
|
const getEstGasFee = async (
|
||||||
|
address: string,
|
||||||
|
nftFactory: NftFactory,
|
||||||
|
nftMetadata: NftMetadata,
|
||||||
|
ethToOceanConversionRate: number
|
||||||
|
): Promise<string> => {
|
||||||
|
if (!address || !nftFactory || !nftMetadata || !ethToOceanConversionRate)
|
||||||
|
return
|
||||||
|
|
||||||
|
const { web3 } = nftFactory
|
||||||
|
const nft = generateNftCreateData(nftMetadata)
|
||||||
|
|
||||||
|
const gasPrice = await web3.eth.getGasPrice()
|
||||||
|
const gasLimit = await nftFactory?.estGasCreateNFT(address, nft)
|
||||||
|
const gasFeeEth = web3.utils.fromWei(
|
||||||
|
(+gasPrice * +gasLimit).toString(),
|
||||||
|
'ether'
|
||||||
|
)
|
||||||
|
const gasFeeOcean = (+gasFeeEth / +ethToOceanConversionRate).toString()
|
||||||
|
return gasFeeOcean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TxFee({
|
||||||
|
nftMetadata
|
||||||
|
}: {
|
||||||
|
nftMetadata: NftMetadata
|
||||||
|
}): ReactElement {
|
||||||
|
const { accountId } = useWeb3()
|
||||||
|
const { prices } = usePrices()
|
||||||
|
const nftFactory = useNftFactory()
|
||||||
|
const [gasFee, setGasFee] = useState('')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const calculateGasFee = async () =>
|
||||||
|
setGasFee(
|
||||||
|
await getEstGasFee(
|
||||||
|
accountId,
|
||||||
|
nftFactory,
|
||||||
|
nftMetadata,
|
||||||
|
(prices as any)?.eth
|
||||||
|
)
|
||||||
|
)
|
||||||
|
calculateGasFee()
|
||||||
|
}, [accountId, nftFactory, nftMetadata, prices])
|
||||||
|
|
||||||
|
return gasFee ? (
|
||||||
|
<p>
|
||||||
|
Gas fee estimation for this artwork
|
||||||
|
<Conversion price={gasFee} />
|
||||||
|
</p>
|
||||||
|
) : accountId ? (
|
||||||
|
<p>
|
||||||
|
An error occurred while estimating the gas fee for this artwork, please
|
||||||
|
try again.
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p>Please connect your wallet to get a gas fee estimate for this artwork</p>
|
||||||
|
)
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
.nft {
|
.nft {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--spacer);
|
gap: var(--spacer);
|
||||||
grid-template-columns: 1fr 11fr;
|
grid-template-columns: none;
|
||||||
|
margin-top: calc(var(--spacer) / 2);
|
||||||
margin-bottom: var(--spacer);
|
margin-bottom: var(--spacer);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
@ -19,16 +20,32 @@
|
|||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.refresh {
|
.actions {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: calc(var(--spacer) / 4);
|
left: 0;
|
||||||
bottom: calc(var(--spacer) / 4);
|
bottom: 0;
|
||||||
|
padding: 0 calc(var(--spacer) / 4);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions svg {
|
||||||
|
margin: 0;
|
||||||
|
fill: var(--font-color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.refresh svg {
|
.refresh svg {
|
||||||
fill: var(--brand-pink);
|
transform: scale(0.9);
|
||||||
width: var(--font-size-mini);
|
transform-origin: center;
|
||||||
height: var(--font-size-mini);
|
}
|
||||||
|
|
||||||
|
@media (min-width: 40rem) {
|
||||||
|
.nft {
|
||||||
|
grid-template-columns: 1fr 11fr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,35 +5,43 @@ import { useField } from 'formik'
|
|||||||
import React, { ReactElement, useEffect } from 'react'
|
import React, { ReactElement, useEffect } from 'react'
|
||||||
import Refresh from '@images/refresh.svg'
|
import Refresh from '@images/refresh.svg'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
|
import Tooltip from '@shared/atoms/Tooltip'
|
||||||
|
import TxFee from './TxFee'
|
||||||
|
|
||||||
export default function Nft(props: InputProps): ReactElement {
|
export default function Nft(props: InputProps): ReactElement {
|
||||||
const [field, meta, helpers] = useField(props.name)
|
const [field, meta, helpers] = useField(props.name)
|
||||||
|
|
||||||
|
const refreshNftMetadata = () => {
|
||||||
|
const nftMetadata = generateNftMetadata()
|
||||||
|
helpers.setValue({ ...nftMetadata })
|
||||||
|
}
|
||||||
|
|
||||||
// Generate on first mount
|
// Generate on first mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (field.value?.name !== '') return
|
if (field.value?.name !== '') return
|
||||||
|
|
||||||
const nftOptions = generateNftMetadata()
|
refreshNftMetadata()
|
||||||
helpers.setValue({ ...nftOptions })
|
|
||||||
}, [field.value?.name])
|
}, [field.value?.name])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.nft}>
|
<div className={styles.nft}>
|
||||||
<figure className={styles.image}>
|
<figure className={styles.image}>
|
||||||
<img src={field?.value?.image_data} width="128" height="128" />
|
<img src={field?.value?.image_data} width="128" height="128" />
|
||||||
<Button
|
<div className={styles.actions}>
|
||||||
style="text"
|
<Tooltip content={<TxFee nftMetadata={field.value} />} />
|
||||||
size="small"
|
<Button
|
||||||
className={styles.refresh}
|
style="text"
|
||||||
title="Generate new image"
|
size="small"
|
||||||
onClick={(e) => {
|
className={styles.refresh}
|
||||||
e.preventDefault()
|
title="Generate new image"
|
||||||
const nftMetadata = generateNftMetadata()
|
onClick={(e) => {
|
||||||
helpers.setValue({ ...nftMetadata })
|
e.preventDefault()
|
||||||
}}
|
refreshNftMetadata()
|
||||||
>
|
}}
|
||||||
<Refresh />
|
>
|
||||||
</Button>
|
<Refresh />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
<div className={styles.token}>
|
<div className={styles.token}>
|
||||||
|
@ -96,7 +96,6 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
font-size: var(--font-size-small);
|
font-size: var(--font-size-small);
|
||||||
color: var(--font-color-text);
|
|
||||||
padding-left: 0.5rem;
|
padding-left: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,21 +3,24 @@ import styles from './index.module.css'
|
|||||||
import Loader from '../atoms/Loader'
|
import Loader from '../atoms/Loader'
|
||||||
import Tooltip from '../atoms/Tooltip'
|
import Tooltip from '../atoms/Tooltip'
|
||||||
import PriceUnit from './PriceUnit'
|
import PriceUnit from './PriceUnit'
|
||||||
|
import { AccessDetails, OrderPriceAndFees } from 'src/@types/Price'
|
||||||
|
|
||||||
export default function Price({
|
export default function Price({
|
||||||
accessDetails,
|
accessDetails,
|
||||||
|
orderPriceAndFees,
|
||||||
className,
|
className,
|
||||||
small,
|
small,
|
||||||
conversion
|
conversion
|
||||||
}: {
|
}: {
|
||||||
accessDetails: AccessDetails
|
accessDetails: AccessDetails
|
||||||
|
orderPriceAndFees?: OrderPriceAndFees
|
||||||
className?: string
|
className?: string
|
||||||
small?: boolean
|
small?: boolean
|
||||||
conversion?: boolean
|
conversion?: boolean
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
return accessDetails?.price || accessDetails?.type === 'free' ? (
|
return accessDetails?.price || accessDetails?.type === 'free' ? (
|
||||||
<PriceUnit
|
<PriceUnit
|
||||||
price={`${accessDetails.price}`}
|
price={`${orderPriceAndFees?.price || accessDetails?.price}`}
|
||||||
symbol={accessDetails.baseToken?.symbol}
|
symbol={accessDetails.baseToken?.symbol}
|
||||||
className={className}
|
className={className}
|
||||||
small={small}
|
small={small}
|
||||||
|
@ -45,6 +45,13 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text code {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: inherit;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.action,
|
.action,
|
||||||
button.action {
|
button.action {
|
||||||
margin-top: calc(var(--spacer) / 2);
|
margin-top: calc(var(--spacer) / 2);
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
padding: calc(var(--spacer) / 2);
|
padding: calc(var(--spacer) / 2);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab {
|
.tab {
|
||||||
@ -15,7 +17,7 @@
|
|||||||
background-color: var(--background-body);
|
background-color: var(--background-body);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
margin-right: -1px;
|
margin-right: -1px;
|
||||||
min-width: 100px;
|
min-width: 90px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab:first-child {
|
.tab:first-child {
|
||||||
@ -38,6 +40,10 @@
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tab > div {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.tabContent {
|
.tabContent {
|
||||||
padding: calc(var(--spacer) / 2);
|
padding: calc(var(--spacer) / 2);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { ReactElement, ReactNode } from 'react'
|
import React, { ReactElement, ReactNode } from 'react'
|
||||||
import { Tab, Tabs as ReactTabs, TabList, TabPanel } from 'react-tabs'
|
import { Tab, Tabs as ReactTabs, TabList, TabPanel } from 'react-tabs'
|
||||||
|
import InputElement from '@shared/FormInput/InputElement'
|
||||||
import styles from './Tabs.module.css'
|
import styles from './Tabs.module.css'
|
||||||
|
|
||||||
export interface TabsItem {
|
export interface TabsItem {
|
||||||
@ -12,12 +13,14 @@ export default function Tabs({
|
|||||||
items,
|
items,
|
||||||
className,
|
className,
|
||||||
handleTabChange,
|
handleTabChange,
|
||||||
defaultIndex
|
defaultIndex,
|
||||||
|
showRadio
|
||||||
}: {
|
}: {
|
||||||
items: TabsItem[]
|
items: TabsItem[]
|
||||||
className?: string
|
className?: string
|
||||||
handleTabChange?: (tabName: string) => void
|
handleTabChange?: (tabName: string) => void
|
||||||
defaultIndex?: number
|
defaultIndex?: number
|
||||||
|
showRadio?: boolean
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
return (
|
return (
|
||||||
<ReactTabs
|
<ReactTabs
|
||||||
@ -25,14 +28,24 @@ export default function Tabs({
|
|||||||
defaultIndex={defaultIndex}
|
defaultIndex={defaultIndex}
|
||||||
>
|
>
|
||||||
<TabList className={styles.tabList}>
|
<TabList className={styles.tabList}>
|
||||||
{items.map((item) => (
|
{items.map((item, index) => (
|
||||||
<Tab
|
<Tab
|
||||||
className={styles.tab}
|
className={styles.tab}
|
||||||
key={item.title}
|
key={item.title}
|
||||||
onClick={handleTabChange ? () => handleTabChange(item.title) : null}
|
onClick={handleTabChange ? () => handleTabChange(item.title) : null}
|
||||||
disabled={item.disabled}
|
disabled={item.disabled}
|
||||||
>
|
>
|
||||||
{item.title}
|
{showRadio ? (
|
||||||
|
<InputElement
|
||||||
|
name={item.title}
|
||||||
|
type="radio"
|
||||||
|
checked={defaultIndex === index}
|
||||||
|
options={[item.title]}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
item.title
|
||||||
|
)}
|
||||||
</Tab>
|
</Tab>
|
||||||
))}
|
))}
|
||||||
</TabList>
|
</TabList>
|
||||||
|
@ -15,7 +15,9 @@ const Tag = ({ tag, noLinks }: { tag: string; noLinks?: boolean }) => {
|
|||||||
return noLinks ? (
|
return noLinks ? (
|
||||||
<span className={styles.tag}>{tag}</span>
|
<span className={styles.tag}>{tag}</span>
|
||||||
) : (
|
) : (
|
||||||
<Link href={`/search?tags=${urlEncodedTag}&sort=created&sortOrder=desc`}>
|
<Link
|
||||||
|
href={`/search?tags=${urlEncodedTag}&sort=metadata.created&sortOrder=desc`}
|
||||||
|
>
|
||||||
<a className={styles.tag} title={tag}>
|
<a className={styles.tag} title={tag}>
|
||||||
{tag}
|
{tag}
|
||||||
</a>
|
</a>
|
||||||
|
@ -6,12 +6,13 @@ import AssetComputeList from '@shared/AssetList/AssetComputeList'
|
|||||||
import { useCancelToken } from '@hooks/useCancelToken'
|
import { useCancelToken } from '@hooks/useCancelToken'
|
||||||
import { getServiceByName } from '@utils/ddo'
|
import { getServiceByName } from '@utils/ddo'
|
||||||
import { Asset } from '@oceanprotocol/lib'
|
import { Asset } from '@oceanprotocol/lib'
|
||||||
|
import { AssetExtended } from 'src/@types/AssetExtended'
|
||||||
|
|
||||||
export default function AlgorithmDatasetsListForCompute({
|
export default function AlgorithmDatasetsListForCompute({
|
||||||
ddo,
|
asset,
|
||||||
algorithmDid
|
algorithmDid
|
||||||
}: {
|
}: {
|
||||||
ddo: Asset
|
asset: AssetExtended
|
||||||
algorithmDid: string
|
algorithmDid: string
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const [datasetsForCompute, setDatasetsForCompute] =
|
const [datasetsForCompute, setDatasetsForCompute] =
|
||||||
@ -19,24 +20,24 @@ export default function AlgorithmDatasetsListForCompute({
|
|||||||
const newCancelToken = useCancelToken()
|
const newCancelToken = useCancelToken()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ddo) return
|
if (!asset) return
|
||||||
|
|
||||||
async function getDatasetsAllowedForCompute() {
|
async function getDatasetsAllowedForCompute() {
|
||||||
const isCompute = Boolean(getServiceByName(ddo, 'compute'))
|
const isCompute = Boolean(getServiceByName(asset, 'compute'))
|
||||||
const datasetComputeService = getServiceByName(
|
const datasetComputeService = getServiceByName(
|
||||||
ddo,
|
asset,
|
||||||
isCompute ? 'compute' : 'access'
|
isCompute ? 'compute' : 'access'
|
||||||
)
|
)
|
||||||
const datasets = await getAlgorithmDatasetsForCompute(
|
const datasets = await getAlgorithmDatasetsForCompute(
|
||||||
algorithmDid,
|
algorithmDid,
|
||||||
datasetComputeService?.serviceEndpoint,
|
datasetComputeService?.serviceEndpoint,
|
||||||
ddo?.chainId,
|
asset?.chainId,
|
||||||
newCancelToken()
|
newCancelToken()
|
||||||
)
|
)
|
||||||
setDatasetsForCompute(datasets)
|
setDatasetsForCompute(datasets)
|
||||||
}
|
}
|
||||||
ddo.metadata.type === 'algorithm' && getDatasetsAllowedForCompute()
|
asset.metadata.type === 'algorithm' && getDatasetsAllowedForCompute()
|
||||||
}, [ddo?.metadata?.type])
|
}, [asset?.metadata?.type])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.datasetsContainer}>
|
<div className={styles.datasetsContainer}>
|
||||||
|
@ -11,6 +11,7 @@ import { useWeb3 } from '@context/Web3'
|
|||||||
import { checkIfConsumable } from '@utils/ddo'
|
import { checkIfConsumable } from '@utils/ddo'
|
||||||
import content from '../../../../../content/pages/startComputeDataset.json'
|
import content from '../../../../../content/pages/startComputeDataset.json'
|
||||||
import { Asset } from '@oceanprotocol/lib'
|
import { Asset } from '@oceanprotocol/lib'
|
||||||
|
import { AccessDetails } from 'src/@types/Price'
|
||||||
|
|
||||||
export default function FormStartCompute({
|
export default function FormStartCompute({
|
||||||
algorithms,
|
algorithms,
|
||||||
@ -104,7 +105,7 @@ export default function FormStartCompute({
|
|||||||
? 0
|
? 0
|
||||||
: Number(algorithmConsumeDetails.price)
|
: Number(algorithmConsumeDetails.price)
|
||||||
|
|
||||||
setTotalPrice(priceDataset + priceAlgo)
|
setTotalPrice((priceDataset + priceAlgo).toString())
|
||||||
}, [
|
}, [
|
||||||
asset?.accessDetails,
|
asset?.accessDetails,
|
||||||
algorithmConsumeDetails,
|
algorithmConsumeDetails,
|
||||||
@ -143,7 +144,7 @@ export default function FormStartCompute({
|
|||||||
hasDatatokenSelectedComputeAsset={hasDatatokenSelectedComputeAsset}
|
hasDatatokenSelectedComputeAsset={hasDatatokenSelectedComputeAsset}
|
||||||
algorithmConsumeDetails={algorithmConsumeDetails}
|
algorithmConsumeDetails={algorithmConsumeDetails}
|
||||||
symbol={oceanSymbol}
|
symbol={oceanSymbol}
|
||||||
totalPrice={totalPrice}
|
totalPrice={Number.parseFloat(totalPrice)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ButtonBuy
|
<ButtonBuy
|
||||||
|
@ -3,6 +3,7 @@ import { useAsset } from '@context/Asset'
|
|||||||
import PriceUnit from '@shared/Price/PriceUnit'
|
import PriceUnit from '@shared/Price/PriceUnit'
|
||||||
import Tooltip from '@shared/atoms/Tooltip'
|
import Tooltip from '@shared/atoms/Tooltip'
|
||||||
import styles from './PriceOutput.module.css'
|
import styles from './PriceOutput.module.css'
|
||||||
|
import { AccessDetails } from 'src/@types/Price'
|
||||||
|
|
||||||
interface PriceOutputProps {
|
interface PriceOutputProps {
|
||||||
totalPrice: number
|
totalPrice: number
|
||||||
@ -74,14 +75,14 @@ export default function PriceOutput({
|
|||||||
<Row
|
<Row
|
||||||
hasPreviousOrder={hasPreviousOrder}
|
hasPreviousOrder={hasPreviousOrder}
|
||||||
hasDatatoken={hasDatatoken}
|
hasDatatoken={hasDatatoken}
|
||||||
price={asset?.accessDetails?.price}
|
price={Number.parseFloat(asset?.accessDetails?.price)}
|
||||||
timeout={assetTimeout}
|
timeout={assetTimeout}
|
||||||
symbol={symbol}
|
symbol={symbol}
|
||||||
/>
|
/>
|
||||||
<Row
|
<Row
|
||||||
hasPreviousOrder={hasPreviousOrderSelectedComputeAsset}
|
hasPreviousOrder={hasPreviousOrderSelectedComputeAsset}
|
||||||
hasDatatoken={hasDatatokenSelectedComputeAsset}
|
hasDatatoken={hasDatatokenSelectedComputeAsset}
|
||||||
price={algorithmConsumeDetails?.price}
|
price={Number.parseFloat(algorithmConsumeDetails?.price)}
|
||||||
timeout={selectedComputeAssetTimeout}
|
timeout={selectedComputeAssetTimeout}
|
||||||
symbol={symbol}
|
symbol={symbol}
|
||||||
sign="+"
|
sign="+"
|
||||||
|
@ -26,7 +26,6 @@ import FileIcon from '@shared/FileIcon'
|
|||||||
import Alert from '@shared/atoms/Alert'
|
import Alert from '@shared/atoms/Alert'
|
||||||
import { useSiteMetadata } from '@hooks/useSiteMetadata'
|
import { useSiteMetadata } from '@hooks/useSiteMetadata'
|
||||||
import { useWeb3 } from '@context/Web3'
|
import { useWeb3 } from '@context/Web3'
|
||||||
import { usePricing } from '@hooks/usePricing'
|
|
||||||
import {
|
import {
|
||||||
generateBaseQuery,
|
generateBaseQuery,
|
||||||
getFilterTerm,
|
getFilterTerm,
|
||||||
@ -59,6 +58,7 @@ import { Decimal } from 'decimal.js'
|
|||||||
import { TransactionReceipt } from 'web3-core'
|
import { TransactionReceipt } from 'web3-core'
|
||||||
import { useAbortController } from '@hooks/useAbortController'
|
import { useAbortController } from '@hooks/useAbortController'
|
||||||
import { getAccessDetails } from '@utils/accessDetailsAndPricing'
|
import { getAccessDetails } from '@utils/accessDetailsAndPricing'
|
||||||
|
import { AccessDetails } from 'src/@types/Price'
|
||||||
|
|
||||||
export default function Compute({
|
export default function Compute({
|
||||||
asset,
|
asset,
|
||||||
@ -78,8 +78,7 @@ export default function Compute({
|
|||||||
consumableFeedback?: string
|
consumableFeedback?: string
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { appConfig } = useSiteMetadata()
|
const { appConfig } = useSiteMetadata()
|
||||||
const { accountId, web3 } = useWeb3()
|
const { accountId } = useWeb3()
|
||||||
const { pricingError, pricingStepText } = usePricing()
|
|
||||||
const [isJobStarting, setIsJobStarting] = useState(false)
|
const [isJobStarting, setIsJobStarting] = useState(false)
|
||||||
const [error, setError] = useState<string>()
|
const [error, setError] = useState<string>()
|
||||||
const newAbortController = useAbortController()
|
const newAbortController = useAbortController()
|
||||||
@ -156,13 +155,13 @@ export default function Compute({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!algorithmConsumeDetails) return
|
if (!algorithmConsumeDetails) return
|
||||||
|
|
||||||
setIsAlgoConsumablePrice(algorithmConsumeDetails.isConsumable)
|
setIsAlgoConsumablePrice(algorithmConsumeDetails.isPurchasable)
|
||||||
}, [algorithmConsumeDetails])
|
}, [algorithmConsumeDetails])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!accessDetails) return
|
if (!accessDetails) return
|
||||||
|
|
||||||
setIsConsumablePrice(accessDetails.isConsumable)
|
setIsConsumablePrice(accessDetails.isPurchasable)
|
||||||
}, [accessDetails])
|
}, [accessDetails])
|
||||||
|
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
@ -217,10 +216,10 @@ export default function Compute({
|
|||||||
|
|
||||||
// Output errors in toast UI
|
// Output errors in toast UI
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newError = error || pricingError
|
const newError = error
|
||||||
if (!newError) return
|
if (!newError) return
|
||||||
toast.error(newError)
|
toast.error(newError)
|
||||||
}, [error, pricingError])
|
}, [error])
|
||||||
|
|
||||||
async function startJob(algorithmId: string): Promise<string> {
|
async function startJob(algorithmId: string): Promise<string> {
|
||||||
try {
|
try {
|
||||||
@ -646,7 +645,7 @@ export default function Compute({
|
|||||||
/>
|
/>
|
||||||
<AlgorithmDatasetsListForCompute
|
<AlgorithmDatasetsListForCompute
|
||||||
algorithmDid={asset.id}
|
algorithmDid={asset.id}
|
||||||
ddo={asset}
|
asset={asset}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@ -680,7 +679,8 @@ export default function Compute({
|
|||||||
selectedComputeAssetLowPoolLiquidity={!isAlgoConsumablePrice}
|
selectedComputeAssetLowPoolLiquidity={!isAlgoConsumablePrice}
|
||||||
selectedComputeAssetType="algorithm"
|
selectedComputeAssetType="algorithm"
|
||||||
selectedComputeAssetTimeout={algorithmTimeout}
|
selectedComputeAssetTimeout={algorithmTimeout}
|
||||||
stepText={pricingStepText || 'Starting Compute Job...'}
|
// lazy comment when removing pricingStepText
|
||||||
|
stepText={'pricingStepText' || 'Starting Compute Job...'}
|
||||||
algorithmConsumeDetails={algorithmConsumeDetails}
|
algorithmConsumeDetails={algorithmConsumeDetails}
|
||||||
isConsumable={isConsumable}
|
isConsumable={isConsumable}
|
||||||
consumableFeedback={consumableFeedback}
|
consumableFeedback={consumableFeedback}
|
||||||
|
@ -1,154 +0,0 @@
|
|||||||
import React, { ReactElement, useEffect, useState } from 'react'
|
|
||||||
import { toast } from 'react-toastify'
|
|
||||||
import FileIcon from '@shared/FileIcon'
|
|
||||||
import Price from '@shared/Price'
|
|
||||||
import { useSiteMetadata } from '@hooks/useSiteMetadata'
|
|
||||||
import { useAsset } from '@context/Asset'
|
|
||||||
import { useWeb3 } from '@context/Web3'
|
|
||||||
import { usePricing } from '@hooks/usePricing'
|
|
||||||
import { useConsume } from '@hooks/useConsume'
|
|
||||||
import ButtonBuy from '@shared/ButtonBuy'
|
|
||||||
import { secondsToString } from '@utils/ddo'
|
|
||||||
import AlgorithmDatasetsListForCompute from './Compute/AlgorithmDatasetsListForCompute'
|
|
||||||
import styles from './Consume.module.css'
|
|
||||||
import { useIsMounted } from '@hooks/useIsMounted'
|
|
||||||
import { Asset, FileMetadata } from '@oceanprotocol/lib'
|
|
||||||
|
|
||||||
export default function Consume({
|
|
||||||
ddo,
|
|
||||||
accessDetails,
|
|
||||||
file,
|
|
||||||
isBalanceSufficient,
|
|
||||||
dtBalance,
|
|
||||||
fileIsLoading,
|
|
||||||
isConsumable,
|
|
||||||
consumableFeedback
|
|
||||||
}: {
|
|
||||||
ddo: Asset
|
|
||||||
accessDetails: AccessDetails
|
|
||||||
file: FileMetadata
|
|
||||||
isBalanceSufficient: boolean
|
|
||||||
dtBalance: string
|
|
||||||
fileIsLoading?: boolean
|
|
||||||
isConsumable?: boolean
|
|
||||||
consumableFeedback?: string
|
|
||||||
}): ReactElement {
|
|
||||||
const { accountId } = useWeb3()
|
|
||||||
const { appConfig } = useSiteMetadata()
|
|
||||||
const [hasPreviousOrder, setHasPreviousOrder] = useState(false)
|
|
||||||
const [previousOrderId, setPreviousOrderId] = useState<string>()
|
|
||||||
const { isInPurgatory, isAssetNetwork } = useAsset()
|
|
||||||
const { buyDT, pricingStepText, pricingError, pricingIsLoading } =
|
|
||||||
usePricing()
|
|
||||||
const { consumeStepText, consume, consumeError, isLoading } = useConsume()
|
|
||||||
const [isDisabled, setIsDisabled] = useState(true)
|
|
||||||
const [hasDatatoken, setHasDatatoken] = useState(false)
|
|
||||||
const [isConsumablePrice, setIsConsumablePrice] = useState(true)
|
|
||||||
const [assetTimeout, setAssetTimeout] = useState('')
|
|
||||||
const isMounted = useIsMounted()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!ddo) return
|
|
||||||
|
|
||||||
const { timeout } = ddo.services[0]
|
|
||||||
setAssetTimeout(`${timeout}`)
|
|
||||||
}, [ddo])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!accessDetails) return
|
|
||||||
|
|
||||||
setIsConsumablePrice(accessDetails.isConsumable)
|
|
||||||
setHasPreviousOrder(accessDetails.owned)
|
|
||||||
setPreviousOrderId(accessDetails.validOrderTx)
|
|
||||||
}, [accessDetails])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setHasDatatoken(Number(dtBalance) >= 1)
|
|
||||||
}, [dtBalance])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!accountId) return
|
|
||||||
setIsDisabled(
|
|
||||||
!isConsumable ||
|
|
||||||
((!isBalanceSufficient ||
|
|
||||||
!isAssetNetwork ||
|
|
||||||
typeof consumeStepText !== 'undefined' ||
|
|
||||||
pricingIsLoading ||
|
|
||||||
!isConsumablePrice) &&
|
|
||||||
!hasPreviousOrder &&
|
|
||||||
!hasDatatoken)
|
|
||||||
)
|
|
||||||
}, [
|
|
||||||
hasPreviousOrder,
|
|
||||||
isBalanceSufficient,
|
|
||||||
isAssetNetwork,
|
|
||||||
consumeStepText,
|
|
||||||
pricingIsLoading,
|
|
||||||
isConsumablePrice,
|
|
||||||
hasDatatoken,
|
|
||||||
isConsumable,
|
|
||||||
accountId
|
|
||||||
])
|
|
||||||
|
|
||||||
async function handleConsume() {
|
|
||||||
// if (!hasPreviousOrder && !hasDatatoken) {
|
|
||||||
// const tx = await buyDT('1', price, ddo)
|
|
||||||
// if (tx === undefined) return
|
|
||||||
// }
|
|
||||||
const error = await consume(
|
|
||||||
ddo.id,
|
|
||||||
ddo.services[0].datatokenAddress,
|
|
||||||
'access',
|
|
||||||
appConfig.marketFeeAddress,
|
|
||||||
previousOrderId
|
|
||||||
)
|
|
||||||
error || setHasPreviousOrder(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output errors in UI
|
|
||||||
useEffect(() => {
|
|
||||||
consumeError && toast.error(consumeError)
|
|
||||||
}, [consumeError])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
pricingError && toast.error(pricingError)
|
|
||||||
}, [pricingError])
|
|
||||||
|
|
||||||
const PurchaseButton = () => (
|
|
||||||
<ButtonBuy
|
|
||||||
action="download"
|
|
||||||
disabled={isDisabled}
|
|
||||||
hasPreviousOrder={hasPreviousOrder}
|
|
||||||
hasDatatoken={hasDatatoken}
|
|
||||||
dtSymbol={ddo?.datatokens[0]?.symbol}
|
|
||||||
dtBalance={dtBalance}
|
|
||||||
datasetLowPoolLiquidity={!isConsumablePrice}
|
|
||||||
onClick={handleConsume}
|
|
||||||
assetTimeout={secondsToString(parseInt(assetTimeout))}
|
|
||||||
assetType={ddo?.metadata?.type}
|
|
||||||
stepText={consumeStepText || pricingStepText}
|
|
||||||
isLoading={pricingIsLoading || isLoading}
|
|
||||||
priceType={accessDetails?.type}
|
|
||||||
isConsumable={isConsumable}
|
|
||||||
isBalanceSufficient={isBalanceSufficient}
|
|
||||||
consumableFeedback={consumableFeedback}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<aside className={styles.consume}>
|
|
||||||
<div className={styles.info}>
|
|
||||||
<div className={styles.filewrapper}>
|
|
||||||
<FileIcon file={file} isLoading={fileIsLoading} />
|
|
||||||
</div>
|
|
||||||
<div className={styles.pricewrapper}>
|
|
||||||
<Price accessDetails={accessDetails} conversion />
|
|
||||||
{!isInPurgatory && <PurchaseButton />}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{ddo?.metadata?.type === 'algorithm' && (
|
|
||||||
<AlgorithmDatasetsListForCompute algorithmDid={ddo.id} ddo={ddo} />
|
|
||||||
)}
|
|
||||||
</aside>
|
|
||||||
)
|
|
||||||
}
|
|
175
src/components/Asset/AssetActions/Download.tsx
Normal file
175
src/components/Asset/AssetActions/Download.tsx
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
import React, { ReactElement, useEffect, useState } from 'react'
|
||||||
|
import FileIcon from '@shared/FileIcon'
|
||||||
|
import Price from '@shared/Price'
|
||||||
|
import { useAsset } from '@context/Asset'
|
||||||
|
import { useWeb3 } from '@context/Web3'
|
||||||
|
import ButtonBuy from '@shared/ButtonBuy'
|
||||||
|
import { secondsToString } from '@utils/ddo'
|
||||||
|
import AlgorithmDatasetsListForCompute from './Compute/AlgorithmDatasetsListForCompute'
|
||||||
|
import styles from './Download.module.css'
|
||||||
|
import { FileMetadata, LoggerInstance, ZERO_ADDRESS } from '@oceanprotocol/lib'
|
||||||
|
import { order } from '@utils/order'
|
||||||
|
import { AssetExtended } from 'src/@types/AssetExtended'
|
||||||
|
import { buyDtFromPool, calculateBuyPrice } from '@utils/pool'
|
||||||
|
import { downloadFile } from '@utils/provider'
|
||||||
|
import { getOrderFeedback } from '@utils/feedback'
|
||||||
|
import { getOrderPriceAndFees } from '@utils/accessDetailsAndPricing'
|
||||||
|
import { OrderPriceAndFees } from 'src/@types/Price'
|
||||||
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
|
export default function Download({
|
||||||
|
asset,
|
||||||
|
file,
|
||||||
|
isBalanceSufficient,
|
||||||
|
dtBalance,
|
||||||
|
fileIsLoading,
|
||||||
|
consumableFeedback
|
||||||
|
}: {
|
||||||
|
asset: AssetExtended
|
||||||
|
file: FileMetadata
|
||||||
|
isBalanceSufficient: boolean
|
||||||
|
dtBalance: string
|
||||||
|
fileIsLoading?: boolean
|
||||||
|
consumableFeedback?: string
|
||||||
|
}): ReactElement {
|
||||||
|
const { accountId, web3 } = useWeb3()
|
||||||
|
const { isInPurgatory, isAssetNetwork } = useAsset()
|
||||||
|
const [isDisabled, setIsDisabled] = useState(true)
|
||||||
|
const [hasDatatoken, setHasDatatoken] = useState(false)
|
||||||
|
const [statusText, setStatusText] = useState('')
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [isOwned, setIsOwned] = useState(false)
|
||||||
|
const [validOrderTx, setValidOrderTx] = useState('')
|
||||||
|
const [orderPriceAndFees, setOrderPriceAndFees] =
|
||||||
|
useState<OrderPriceAndFees>()
|
||||||
|
useEffect(() => {
|
||||||
|
if (!asset?.accessDetails) return
|
||||||
|
|
||||||
|
setIsOwned(asset?.accessDetails?.isOwned)
|
||||||
|
setValidOrderTx(asset?.accessDetails?.validOrderTx)
|
||||||
|
// get full price and fees
|
||||||
|
async function init() {
|
||||||
|
if (asset?.accessDetails?.addressOrId === ZERO_ADDRESS) return
|
||||||
|
setIsLoading(true)
|
||||||
|
setStatusText('Calculating price including fees.')
|
||||||
|
const orderPriceAndFees = await getOrderPriceAndFees(asset, ZERO_ADDRESS)
|
||||||
|
setOrderPriceAndFees(orderPriceAndFees)
|
||||||
|
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
init()
|
||||||
|
}, [asset, accountId])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setHasDatatoken(Number(dtBalance) >= 1)
|
||||||
|
}, [dtBalance])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!accountId || !asset?.accessDetails) return
|
||||||
|
setIsDisabled(
|
||||||
|
!asset?.accessDetails.isPurchasable ||
|
||||||
|
((!isBalanceSufficient || !isAssetNetwork) && !isOwned && !hasDatatoken)
|
||||||
|
)
|
||||||
|
}, [
|
||||||
|
asset?.accessDetails,
|
||||||
|
isBalanceSufficient,
|
||||||
|
isAssetNetwork,
|
||||||
|
hasDatatoken,
|
||||||
|
accountId,
|
||||||
|
isOwned
|
||||||
|
])
|
||||||
|
|
||||||
|
async function handleOrderOrDownload() {
|
||||||
|
setIsLoading(true)
|
||||||
|
if (isOwned) {
|
||||||
|
setStatusText(
|
||||||
|
getOrderFeedback(
|
||||||
|
asset.accessDetails?.baseToken?.symbol,
|
||||||
|
asset.accessDetails?.datatoken?.symbol
|
||||||
|
)[3]
|
||||||
|
)
|
||||||
|
await downloadFile(web3, asset, accountId, validOrderTx)
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
if (!hasDatatoken && asset.accessDetails.type === 'dynamic') {
|
||||||
|
setStatusText(
|
||||||
|
getOrderFeedback(
|
||||||
|
asset.accessDetails.baseToken?.symbol,
|
||||||
|
asset.accessDetails.datatoken?.symbol
|
||||||
|
)[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
const tx = await buyDtFromPool(asset.accessDetails, accountId, web3)
|
||||||
|
|
||||||
|
if (!tx) {
|
||||||
|
toast.error('Failed to buy datatoken from pool!')
|
||||||
|
setIsLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setStatusText(
|
||||||
|
getOrderFeedback(
|
||||||
|
asset.accessDetails.baseToken?.symbol,
|
||||||
|
asset.accessDetails.datatoken?.symbol
|
||||||
|
)[asset.accessDetails?.type === 'fixed' ? 2 : 1]
|
||||||
|
)
|
||||||
|
const orderTx = await order(web3, asset, orderPriceAndFees, accountId)
|
||||||
|
|
||||||
|
setIsOwned(true)
|
||||||
|
setValidOrderTx(orderTx.transactionHash)
|
||||||
|
} catch (ex) {
|
||||||
|
LoggerInstance.log(ex.message)
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const PurchaseButton = () => (
|
||||||
|
<ButtonBuy
|
||||||
|
action="download"
|
||||||
|
disabled={isDisabled}
|
||||||
|
hasPreviousOrder={isOwned}
|
||||||
|
hasDatatoken={hasDatatoken}
|
||||||
|
dtSymbol={asset?.datatokens[0]?.symbol}
|
||||||
|
dtBalance={dtBalance}
|
||||||
|
datasetLowPoolLiquidity={!asset.accessDetails?.isPurchasable}
|
||||||
|
onClick={handleOrderOrDownload}
|
||||||
|
assetTimeout={secondsToString(asset.services[0].timeout)}
|
||||||
|
assetType={asset?.metadata?.type}
|
||||||
|
stepText={statusText}
|
||||||
|
// isLoading={pricingIsLoading || isLoading}
|
||||||
|
isLoading={isLoading}
|
||||||
|
priceType={asset.accessDetails?.type}
|
||||||
|
isConsumable={asset.accessDetails?.isPurchasable}
|
||||||
|
isBalanceSufficient={isBalanceSufficient}
|
||||||
|
consumableFeedback={consumableFeedback}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<aside className={styles.consume}>
|
||||||
|
<div className={styles.info}>
|
||||||
|
<div className={styles.filewrapper}>
|
||||||
|
<FileIcon file={file} isLoading={fileIsLoading} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.pricewrapper}>
|
||||||
|
<Price
|
||||||
|
accessDetails={asset.accessDetails}
|
||||||
|
orderPriceAndFees={orderPriceAndFees}
|
||||||
|
conversion
|
||||||
|
/>
|
||||||
|
{!isInPurgatory && <PurchaseButton />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{asset?.metadata?.type === 'algorithm' && (
|
||||||
|
<AlgorithmDatasetsListForCompute
|
||||||
|
algorithmDid={asset.id}
|
||||||
|
asset={asset}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</aside>
|
||||||
|
)
|
||||||
|
}
|
@ -106,7 +106,6 @@ export default function Add({
|
|||||||
const result = await poolInstance.joinswapExternAmountIn(
|
const result = await poolInstance.joinswapExternAmountIn(
|
||||||
accountId,
|
accountId,
|
||||||
poolAddress,
|
poolAddress,
|
||||||
tokenInAddress,
|
|
||||||
amount,
|
amount,
|
||||||
minPoolAmountOut
|
minPoolAmountOut
|
||||||
)
|
)
|
||||||
|
@ -64,7 +64,6 @@ export default function Remove({
|
|||||||
const result = await poolInstance.exitswapPoolAmountIn(
|
const result = await poolInstance.exitswapPoolAmountIn(
|
||||||
accountId,
|
accountId,
|
||||||
poolAddress,
|
poolAddress,
|
||||||
tokenOutAddress,
|
|
||||||
amountPoolShares,
|
amountPoolShares,
|
||||||
minOceanAmount
|
minOceanAmount
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { ReactElement, useState, useEffect } from 'react'
|
import React, { ReactElement, useState, useEffect } from 'react'
|
||||||
import Compute from './Compute'
|
import Compute from './Compute'
|
||||||
import Consume from './Consume'
|
import Consume from './Download'
|
||||||
import { FileMetadata, LoggerInstance, Datatoken } from '@oceanprotocol/lib'
|
import { FileMetadata, LoggerInstance, Datatoken } from '@oceanprotocol/lib'
|
||||||
import Tabs, { TabsItem } from '@shared/atoms/Tabs'
|
import Tabs, { TabsItem } from '@shared/atoms/Tabs'
|
||||||
import { compareAsBN } from '@utils/numbers'
|
import { compareAsBN } from '@utils/numbers'
|
||||||
@ -141,13 +141,11 @@ export default function AssetActions({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Consume
|
<Consume
|
||||||
ddo={asset}
|
asset={asset}
|
||||||
accessDetails={asset?.accessDetails}
|
|
||||||
dtBalance={dtBalance}
|
dtBalance={dtBalance}
|
||||||
isBalanceSufficient={isBalanceSufficient}
|
isBalanceSufficient={isBalanceSufficient}
|
||||||
file={fileMetadata}
|
file={fileMetadata}
|
||||||
fileIsLoading={fileIsLoading}
|
fileIsLoading={fileIsLoading}
|
||||||
isConsumable={isConsumable}
|
|
||||||
consumableFeedback={consumableFeedback}
|
consumableFeedback={consumableFeedback}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -1,199 +0,0 @@
|
|||||||
import React, { ReactElement, useEffect, useState } from 'react'
|
|
||||||
import { gql, OperationContext } from 'urql'
|
|
||||||
import Conversion from '@shared/Price/Conversion'
|
|
||||||
import PriceUnit from '@shared/Price/PriceUnit'
|
|
||||||
import Tooltip from '@shared/atoms/Tooltip'
|
|
||||||
import NetworkName from '@shared/NetworkName'
|
|
||||||
import { fetchData, getSubgraphUri } from '@utils/subgraph'
|
|
||||||
import { useSiteMetadata } from '@hooks/useSiteMetadata'
|
|
||||||
import useNetworkMetadata, {
|
|
||||||
filterNetworksByType
|
|
||||||
} from '@hooks/useNetworkMetadata'
|
|
||||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
|
||||||
import styles from './MarketStats.module.css'
|
|
||||||
import { FooterStatsValues_globalStats_totalLiquidity_token as LiquidityToken } from 'src/@types/subgraph/FooterStatsValues'
|
|
||||||
|
|
||||||
const getGlobalStatsValues = gql`
|
|
||||||
query FooterStatsValues {
|
|
||||||
globalStats {
|
|
||||||
poolCount
|
|
||||||
nftCount
|
|
||||||
datatokenCount
|
|
||||||
orderCount
|
|
||||||
totalLiquidity {
|
|
||||||
token {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
symbol
|
|
||||||
}
|
|
||||||
value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
interface Value {
|
|
||||||
[chainId: number]: string
|
|
||||||
}
|
|
||||||
|
|
||||||
function MarketNetworkStats({
|
|
||||||
totalValueLocked,
|
|
||||||
poolCount,
|
|
||||||
totalOceanLiquidity
|
|
||||||
}: {
|
|
||||||
totalValueLocked: string
|
|
||||||
poolCount: string
|
|
||||||
totalOceanLiquidity: string
|
|
||||||
}): ReactElement {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Conversion price={totalValueLocked} hideApproximateSymbol />{' '}
|
|
||||||
<abbr title="Total Value Locked">TVL</abbr> across{' '}
|
|
||||||
<strong>{poolCount}</strong> asset pools that contain{' '}
|
|
||||||
<PriceUnit price={totalOceanLiquidity} small className={styles.total} />,
|
|
||||||
plus datatokens for each pool.
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function MarketNetworkStatsTooltip({
|
|
||||||
totalValueLocked,
|
|
||||||
poolCount,
|
|
||||||
totalOceanLiquidity,
|
|
||||||
mainChainIds
|
|
||||||
}: {
|
|
||||||
totalValueLocked: Value
|
|
||||||
poolCount: Value
|
|
||||||
totalOceanLiquidity: Value
|
|
||||||
mainChainIds: number[]
|
|
||||||
}): ReactElement {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ul className={styles.statsList}>
|
|
||||||
{totalValueLocked &&
|
|
||||||
totalOceanLiquidity &&
|
|
||||||
poolCount &&
|
|
||||||
mainChainIds?.map((chainId, key) => (
|
|
||||||
<li className={styles.tooltipStats} key={key}>
|
|
||||||
<NetworkName networkId={chainId} className={styles.network} />
|
|
||||||
<br />
|
|
||||||
<Conversion
|
|
||||||
price={totalValueLocked[chainId] || '0'}
|
|
||||||
hideApproximateSymbol
|
|
||||||
/>{' '}
|
|
||||||
<abbr title="Total Value Locked">TVL</abbr>
|
|
||||||
{' | '}
|
|
||||||
<strong>{poolCount[chainId] || '0'}</strong> pools
|
|
||||||
{' | '}
|
|
||||||
<PriceUnit price={totalOceanLiquidity[chainId] || '0'} small />
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
<p className={styles.note}>
|
|
||||||
Counted on-chain from our pool factory. Does not filter out assets in{' '}
|
|
||||||
<a href="https://github.com/oceanprotocol/list-purgatory">
|
|
||||||
list-purgatory
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function MarketStats(): ReactElement {
|
|
||||||
const [totalValueLocked, setTotalValueLocked] = useState<Value>()
|
|
||||||
const [totalOceanLiquidity, setTotalOceanLiquidity] = useState<Value>()
|
|
||||||
const [poolCount, setPoolCount] = useState<Value>()
|
|
||||||
const [totalValueLockedSum, setTotalValueLockedSum] = useState<string>()
|
|
||||||
const [totalOceanLiquiditySum, setTotalOceanLiquiditySum] = useState<string>()
|
|
||||||
const [poolCountSum, setPoolCountSum] = useState<string>()
|
|
||||||
const [mainChainIds, setMainChainIds] = useState<number[]>()
|
|
||||||
const { appConfig } = useSiteMetadata()
|
|
||||||
const { networksList } = useNetworkMetadata()
|
|
||||||
|
|
||||||
async function getMarketStats() {
|
|
||||||
const mainChainIdsList = await filterNetworksByType(
|
|
||||||
'mainnet',
|
|
||||||
appConfig.chainIdsSupported,
|
|
||||||
networksList
|
|
||||||
)
|
|
||||||
setMainChainIds(mainChainIdsList)
|
|
||||||
|
|
||||||
let newTotalValueLockedSum = 0
|
|
||||||
const newTotalOceanLiquiditySum = 0
|
|
||||||
let newPoolCountSum = 0
|
|
||||||
|
|
||||||
for (const chainId of mainChainIdsList) {
|
|
||||||
const context: OperationContext = {
|
|
||||||
url: `${getSubgraphUri(
|
|
||||||
chainId
|
|
||||||
)}/subgraphs/name/oceanprotocol/ocean-subgraph`,
|
|
||||||
requestPolicy: 'network-only'
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetchData(getGlobalStatsValues, null, context)
|
|
||||||
if (!response) continue
|
|
||||||
|
|
||||||
const {
|
|
||||||
poolCount,
|
|
||||||
nftCount,
|
|
||||||
datatokenCount,
|
|
||||||
orderCount,
|
|
||||||
totalLiquidity
|
|
||||||
} = response?.data?.globalStats[0]
|
|
||||||
|
|
||||||
await setTotalValueLocked((prevState) => ({
|
|
||||||
...prevState,
|
|
||||||
[chainId]: totalLiquidity.value
|
|
||||||
}))
|
|
||||||
// TODO: how to get total OCEAN liquidity? Does this work?
|
|
||||||
await setTotalOceanLiquidity((prevState) => ({
|
|
||||||
...prevState,
|
|
||||||
[chainId]: totalLiquidity.filter(
|
|
||||||
(token: LiquidityToken) => token.symbol === 'OCEAN'
|
|
||||||
)[0]
|
|
||||||
}))
|
|
||||||
await setPoolCount((prevState) => ({
|
|
||||||
...prevState,
|
|
||||||
[chainId]: poolCount
|
|
||||||
}))
|
|
||||||
|
|
||||||
newTotalValueLockedSum += parseInt(totalLiquidity.value)
|
|
||||||
// newTotalOceanLiquiditySum += parseInt(totalOceanLiquidity.value)
|
|
||||||
newPoolCountSum += parseInt(poolCount)
|
|
||||||
} catch (error) {
|
|
||||||
LoggerInstance.error('Error fetchData: ', error.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setTotalValueLockedSum(`${newTotalValueLockedSum}`)
|
|
||||||
setTotalOceanLiquiditySum(`${newTotalOceanLiquiditySum}`)
|
|
||||||
setPoolCountSum(`${newPoolCountSum}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getMarketStats()
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.stats}>
|
|
||||||
<>
|
|
||||||
<MarketNetworkStats
|
|
||||||
totalValueLocked={totalValueLockedSum || '0'}
|
|
||||||
totalOceanLiquidity={totalOceanLiquiditySum || '0'}
|
|
||||||
poolCount={poolCountSum || '0'}
|
|
||||||
/>{' '}
|
|
||||||
<Tooltip
|
|
||||||
className={styles.info}
|
|
||||||
content={
|
|
||||||
<MarketNetworkStatsTooltip
|
|
||||||
totalValueLocked={totalValueLocked}
|
|
||||||
poolCount={poolCount}
|
|
||||||
totalOceanLiquidity={totalOceanLiquidity}
|
|
||||||
mainChainIds={mainChainIds}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,28 +1,12 @@
|
|||||||
.stats {
|
|
||||||
margin-bottom: calc(var(--spacer) * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* specificity sledgehammer override without !important */
|
|
||||||
.stats,
|
|
||||||
.stats *,
|
|
||||||
.statsList * {
|
|
||||||
font-size: var(--font-size-small);
|
|
||||||
color: var(--color-secondary);
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltipStats {
|
.tooltipStats {
|
||||||
margin-bottom: calc(var(--spacer) / 3);
|
margin-bottom: calc(var(--spacer) / 3);
|
||||||
padding-bottom: calc(var(--spacer) / 3);
|
padding-bottom: calc(var(--spacer) / 3);
|
||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.network {
|
.statsList {
|
||||||
font-weight: var(--font-weight-bold);
|
composes: statsList from './index.module.css';
|
||||||
}
|
padding-bottom: 0;
|
||||||
|
|
||||||
.info {
|
|
||||||
width: 0.85rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.statsList,
|
.statsList,
|
||||||
@ -30,13 +14,13 @@
|
|||||||
padding: calc(var(--spacer) / 4);
|
padding: calc(var(--spacer) / 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.statsList {
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note {
|
.note {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
font-size: var(--font-size-mini);
|
font-size: var(--font-size-mini);
|
||||||
color: var(--color-secondary);
|
color: var(--color-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.network {
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
}
|
51
src/components/Footer/MarketStats/Tooltip.tsx
Normal file
51
src/components/Footer/MarketStats/Tooltip.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import React, { ReactElement } from 'react'
|
||||||
|
import Conversion from '@shared/Price/Conversion'
|
||||||
|
import PriceUnit from '@shared/Price/PriceUnit'
|
||||||
|
import NetworkName from '@shared/NetworkName'
|
||||||
|
import styles from './Tooltip.module.css'
|
||||||
|
import { StatsValue } from './_types'
|
||||||
|
|
||||||
|
export default function MarketStatsTooltip({
|
||||||
|
totalValueLockedInOcean,
|
||||||
|
poolCount,
|
||||||
|
totalOceanLiquidity,
|
||||||
|
mainChainIds
|
||||||
|
}: {
|
||||||
|
totalValueLockedInOcean: StatsValue
|
||||||
|
poolCount: StatsValue
|
||||||
|
totalOceanLiquidity: StatsValue
|
||||||
|
mainChainIds: number[]
|
||||||
|
}): ReactElement {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ul className={styles.statsList}>
|
||||||
|
{mainChainIds?.map((chainId, key) => (
|
||||||
|
<li className={styles.tooltipStats} key={key}>
|
||||||
|
<NetworkName networkId={chainId} className={styles.network} />
|
||||||
|
<br />
|
||||||
|
<Conversion
|
||||||
|
price={totalValueLockedInOcean?.[chainId] || '0'}
|
||||||
|
hideApproximateSymbol
|
||||||
|
/>{' '}
|
||||||
|
<abbr title="Total Value Locked">TVL</abbr>
|
||||||
|
{' | '}
|
||||||
|
<strong>{poolCount?.[chainId] || '0'}</strong> pools
|
||||||
|
{' | '}
|
||||||
|
<PriceUnit
|
||||||
|
price={totalOceanLiquidity?.[chainId] || '0'}
|
||||||
|
symbol="OCEAN"
|
||||||
|
small
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<p className={styles.note}>
|
||||||
|
Counted on-chain from our NFT and pool factories. Does not filter out
|
||||||
|
assets in{' '}
|
||||||
|
<a href="https://github.com/oceanprotocol/list-purgatory">
|
||||||
|
list-purgatory
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
28
src/components/Footer/MarketStats/Total.tsx
Normal file
28
src/components/Footer/MarketStats/Total.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import React, { ReactElement } from 'react'
|
||||||
|
import Conversion from '@shared/Price/Conversion'
|
||||||
|
import PriceUnit from '@shared/Price/PriceUnit'
|
||||||
|
import { StatsTotal } from './_types'
|
||||||
|
|
||||||
|
export default function MarketStatsTotal({
|
||||||
|
total
|
||||||
|
}: {
|
||||||
|
total: StatsTotal
|
||||||
|
}): ReactElement {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
<strong>{total.orders}</strong> orders across{' '}
|
||||||
|
<strong>{total.nfts}</strong> assets with{' '}
|
||||||
|
<strong>{total.datatokens}</strong> different datatokens.
|
||||||
|
</p>
|
||||||
|
<Conversion
|
||||||
|
price={`${total.totalValueLockedInOcean}`}
|
||||||
|
hideApproximateSymbol
|
||||||
|
/>{' '}
|
||||||
|
<abbr title="Total Value Locked">TVL</abbr> across{' '}
|
||||||
|
<strong>{total.pools}</strong> asset pools that contain{' '}
|
||||||
|
<PriceUnit price={`${total.totalOceanLiquidity}`} symbol="OCEAN" small />,
|
||||||
|
plus datatokens for each pool.
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
20
src/components/Footer/MarketStats/_queries.ts
Normal file
20
src/components/Footer/MarketStats/_queries.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { gql } from 'urql'
|
||||||
|
|
||||||
|
export const queryGlobalStatistics = gql`
|
||||||
|
query FooterStatsValues {
|
||||||
|
globalStatistics {
|
||||||
|
poolCount
|
||||||
|
nftCount
|
||||||
|
datatokenCount
|
||||||
|
orderCount
|
||||||
|
totalLiquidity {
|
||||||
|
value
|
||||||
|
token {
|
||||||
|
address
|
||||||
|
name
|
||||||
|
symbol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
12
src/components/Footer/MarketStats/_types.ts
Normal file
12
src/components/Footer/MarketStats/_types.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export interface StatsValue {
|
||||||
|
[chainId: number]: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StatsTotal {
|
||||||
|
totalValueLockedInOcean: number
|
||||||
|
totalOceanLiquidity: number
|
||||||
|
pools: number
|
||||||
|
nfts: number
|
||||||
|
datatokens: number
|
||||||
|
orders: number
|
||||||
|
}
|
21
src/components/Footer/MarketStats/index.module.css
Normal file
21
src/components/Footer/MarketStats/index.module.css
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
.stats {
|
||||||
|
margin-bottom: calc(var(--spacer) * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* specificity sledgehammer override without !important */
|
||||||
|
.stats,
|
||||||
|
.stats *,
|
||||||
|
.statsList * {
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
color: var(--color-secondary);
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
width: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats p {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
166
src/components/Footer/MarketStats/index.tsx
Normal file
166
src/components/Footer/MarketStats/index.tsx
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
import React, { ReactElement, useCallback, useEffect, useState } from 'react'
|
||||||
|
import { OperationContext } from 'urql'
|
||||||
|
import Tooltip from '@shared/atoms/Tooltip'
|
||||||
|
import { fetchData, getSubgraphUri } from '@utils/subgraph'
|
||||||
|
import { useSiteMetadata } from '@hooks/useSiteMetadata'
|
||||||
|
import useNetworkMetadata, {
|
||||||
|
filterNetworksByType
|
||||||
|
} from '@hooks/useNetworkMetadata'
|
||||||
|
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||||
|
import styles from './index.module.css'
|
||||||
|
import { FooterStatsValues_globalStatistics as FooterStatsValuesGlobalStatistics } from 'src/@types/subgraph/FooterStatsValues'
|
||||||
|
import MarketStatsTooltip from './Tooltip'
|
||||||
|
import MarketStatsTotal from './Total'
|
||||||
|
import { queryGlobalStatistics } from './_queries'
|
||||||
|
import { usePrices } from '@context/Prices'
|
||||||
|
import { useUserPreferences } from '@context/UserPreferences'
|
||||||
|
import Decimal from 'decimal.js'
|
||||||
|
import { StatsTotal, StatsValue } from './_types'
|
||||||
|
|
||||||
|
const initialTotal: StatsTotal = {
|
||||||
|
totalValueLockedInOcean: 0,
|
||||||
|
totalOceanLiquidity: 0,
|
||||||
|
pools: 0,
|
||||||
|
nfts: 0,
|
||||||
|
datatokens: 0,
|
||||||
|
orders: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MarketStats(): ReactElement {
|
||||||
|
const { appConfig } = useSiteMetadata()
|
||||||
|
const { networksList } = useNetworkMetadata()
|
||||||
|
const { currency } = useUserPreferences()
|
||||||
|
const { prices } = usePrices()
|
||||||
|
|
||||||
|
const [mainChainIds, setMainChainIds] = useState<number[]>()
|
||||||
|
const [data, setData] =
|
||||||
|
useState<{ [chainId: number]: FooterStatsValuesGlobalStatistics }>()
|
||||||
|
const [totalValueLockedInOcean, setTotalValueLockedInOcean] =
|
||||||
|
useState<StatsValue>()
|
||||||
|
const [totalOceanLiquidity, setTotalOceanLiquidity] = useState<StatsValue>()
|
||||||
|
const [poolCount, setPoolCount] = useState<StatsValue>()
|
||||||
|
const [total, setTotal] = useState(initialTotal)
|
||||||
|
|
||||||
|
//
|
||||||
|
// Set the main chain ids we want to display stats for
|
||||||
|
//
|
||||||
|
useEffect(() => {
|
||||||
|
if (!networksList) return
|
||||||
|
|
||||||
|
const mainChainIdsList = filterNetworksByType(
|
||||||
|
'mainnet',
|
||||||
|
appConfig.chainIdsSupported,
|
||||||
|
networksList
|
||||||
|
)
|
||||||
|
setMainChainIds(mainChainIdsList)
|
||||||
|
}, [appConfig.chainIdsSupported, networksList])
|
||||||
|
|
||||||
|
//
|
||||||
|
// Helper: fetch data from subgraph
|
||||||
|
//
|
||||||
|
const getMarketStats = useCallback(async () => {
|
||||||
|
if (!mainChainIds?.length) return
|
||||||
|
|
||||||
|
for (const chainId of mainChainIds) {
|
||||||
|
const context: OperationContext = {
|
||||||
|
url: `${getSubgraphUri(
|
||||||
|
chainId
|
||||||
|
)}/subgraphs/name/oceanprotocol/ocean-subgraph`,
|
||||||
|
requestPolicy: 'network-only'
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetchData(queryGlobalStatistics, null, context)
|
||||||
|
if (!response?.data?.globalStatistics) return
|
||||||
|
|
||||||
|
setData((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
[chainId]: response.data.globalStatistics[0]
|
||||||
|
}))
|
||||||
|
} catch (error) {
|
||||||
|
LoggerInstance.error('Error fetching global stats: ', error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [mainChainIds])
|
||||||
|
|
||||||
|
//
|
||||||
|
// 1. Fetch Data
|
||||||
|
//
|
||||||
|
useEffect(() => {
|
||||||
|
getMarketStats()
|
||||||
|
}, [getMarketStats])
|
||||||
|
|
||||||
|
//
|
||||||
|
// 2. Data Manipulation
|
||||||
|
//
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data || !mainChainIds?.length) return
|
||||||
|
|
||||||
|
const newTotal: StatsTotal = {
|
||||||
|
...initialTotal // always start calculating beginning from initial 0 values
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const chainId of mainChainIds) {
|
||||||
|
const baseTokenValue = data[chainId]?.totalLiquidity[0]?.value
|
||||||
|
|
||||||
|
try {
|
||||||
|
const totalValueLockedInOcean = baseTokenValue
|
||||||
|
? new Decimal(baseTokenValue).mul(2)
|
||||||
|
: new Decimal(0)
|
||||||
|
|
||||||
|
setTotalValueLockedInOcean((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
[chainId]: `${totalValueLockedInOcean}`
|
||||||
|
}))
|
||||||
|
|
||||||
|
const totalOceanLiquidity = Number(baseTokenValue) || 0
|
||||||
|
|
||||||
|
setTotalOceanLiquidity((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
[chainId]: `${totalOceanLiquidity}`
|
||||||
|
}))
|
||||||
|
|
||||||
|
const poolCount = data[chainId]?.poolCount || 0
|
||||||
|
|
||||||
|
setPoolCount((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
[chainId]: `${poolCount}`
|
||||||
|
}))
|
||||||
|
|
||||||
|
const nftCount = data[chainId]?.nftCount || 0
|
||||||
|
const datatokenCount = data[chainId]?.datatokenCount || 0
|
||||||
|
const orderCount = data[chainId]?.orderCount || 0
|
||||||
|
|
||||||
|
newTotal.totalValueLockedInOcean += totalValueLockedInOcean.toNumber()
|
||||||
|
newTotal.totalOceanLiquidity += totalOceanLiquidity
|
||||||
|
newTotal.pools += poolCount
|
||||||
|
newTotal.nfts += nftCount
|
||||||
|
newTotal.datatokens += datatokenCount
|
||||||
|
newTotal.orders += orderCount
|
||||||
|
} catch (error) {
|
||||||
|
LoggerInstance.error('Error data manipulation: ', error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTotal(newTotal)
|
||||||
|
}, [data, mainChainIds, prices, currency])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.stats}>
|
||||||
|
<>
|
||||||
|
<MarketStatsTotal total={total} />{' '}
|
||||||
|
<Tooltip
|
||||||
|
className={styles.info}
|
||||||
|
content={
|
||||||
|
<MarketStatsTooltip
|
||||||
|
totalValueLockedInOcean={totalValueLockedInOcean}
|
||||||
|
poolCount={poolCount}
|
||||||
|
totalOceanLiquidity={totalOceanLiquidity}
|
||||||
|
mainChainIds={mainChainIds}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,12 +1,11 @@
|
|||||||
import { useUserPreferences } from '@context/UserPreferences'
|
import { useUserPreferences } from '@context/UserPreferences'
|
||||||
import React, { ReactElement, useEffect, useState, useCallback } from 'react'
|
import React, { ReactElement, useEffect, useState } from 'react'
|
||||||
import Table from '@shared/atoms/Table'
|
import Table from '@shared/atoms/Table'
|
||||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||||
import Price from '@shared/Price'
|
import Price from '@shared/Price'
|
||||||
import Tooltip from '@shared/atoms/Tooltip'
|
import Tooltip from '@shared/atoms/Tooltip'
|
||||||
import AssetTitle from '@shared/AssetList/AssetListTitle'
|
import AssetTitle from '@shared/AssetList/AssetListTitle'
|
||||||
import { retrieveDDOListByDIDs } from '@utils/aquarius'
|
import { retrieveDDOListByDIDs } from '@utils/aquarius'
|
||||||
import { CancelToken } from 'axios'
|
|
||||||
import { useSiteMetadata } from '@hooks/useSiteMetadata'
|
import { useSiteMetadata } from '@hooks/useSiteMetadata'
|
||||||
import { useCancelToken } from '@hooks/useCancelToken'
|
import { useCancelToken } from '@hooks/useCancelToken'
|
||||||
import { AssetExtended } from 'src/@types/AssetExtended'
|
import { AssetExtended } from 'src/@types/AssetExtended'
|
||||||
@ -53,26 +52,6 @@ export default function Bookmarks(): ReactElement {
|
|||||||
const { chainIds } = useUserPreferences()
|
const { chainIds } = useUserPreferences()
|
||||||
const newCancelToken = useCancelToken()
|
const newCancelToken = useCancelToken()
|
||||||
|
|
||||||
const getAssetsBookmarked = useCallback(
|
|
||||||
async (
|
|
||||||
bookmarks: string[],
|
|
||||||
chainIds: number[],
|
|
||||||
cancelToken: CancelToken
|
|
||||||
) => {
|
|
||||||
try {
|
|
||||||
const result = await retrieveDDOListByDIDs(
|
|
||||||
bookmarks,
|
|
||||||
chainIds,
|
|
||||||
cancelToken
|
|
||||||
)
|
|
||||||
return result
|
|
||||||
} catch (error) {
|
|
||||||
LoggerInstance.error(error.message)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!appConfig?.metadataCacheUri || bookmarks === []) return
|
if (!appConfig?.metadataCacheUri || bookmarks === []) return
|
||||||
|
|
||||||
@ -85,21 +64,23 @@ export default function Bookmarks(): ReactElement {
|
|||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resultPinned = await getAssetsBookmarked(
|
const result = await retrieveDDOListByDIDs(
|
||||||
bookmarks,
|
bookmarks,
|
||||||
chainIds,
|
chainIds,
|
||||||
newCancelToken()
|
newCancelToken()
|
||||||
)
|
)
|
||||||
|
if (!result?.length) return
|
||||||
|
|
||||||
const pinnedAssets: AssetExtended[] = await getAccessDetailsForAssets(
|
const pinnedAssets: AssetExtended[] = await getAccessDetailsForAssets(
|
||||||
resultPinned,
|
result,
|
||||||
accountId
|
accountId
|
||||||
)
|
)
|
||||||
setPinned(pinnedAssets)
|
setPinned(pinnedAssets)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
LoggerInstance.error(error.message)
|
LoggerInstance.error(`Bookmarks error:`, error.message)
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(false)
|
|
||||||
}
|
}
|
||||||
init()
|
init()
|
||||||
}, [
|
}, [
|
||||||
@ -107,7 +88,6 @@ export default function Bookmarks(): ReactElement {
|
|||||||
bookmarks,
|
bookmarks,
|
||||||
chainIds,
|
chainIds,
|
||||||
accountId,
|
accountId,
|
||||||
getAssetsBookmarked,
|
|
||||||
newCancelToken
|
newCancelToken
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -24,10 +24,9 @@ async function getQueryHighest(
|
|||||||
esPaginationOptions: {
|
esPaginationOptions: {
|
||||||
size: dtList.length > 0 ? dtList.length : 1
|
size: dtList.length > 0 ? dtList.length : 1
|
||||||
},
|
},
|
||||||
filters: [getFilterTerm('dataToken', dtList)]
|
filters: [getFilterTerm('services.datatokenAddress', dtList)]
|
||||||
} as BaseQueryParams
|
} as BaseQueryParams
|
||||||
const queryHighest = generateBaseQuery(baseQueryParams)
|
const queryHighest = generateBaseQuery(baseQueryParams)
|
||||||
|
|
||||||
return [queryHighest, dtList]
|
return [queryHighest, dtList]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@ async function getPoolSharesLiquidity(
|
|||||||
poolShares: PoolShare[]
|
poolShares: PoolShare[]
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
let totalLiquidity = 0
|
let totalLiquidity = 0
|
||||||
|
|
||||||
for (const poolShare of poolShares) {
|
for (const poolShare of poolShares) {
|
||||||
const poolLiquidity = calculateUserLiquidity(poolShare)
|
const poolLiquidity = calculateUserLiquidity(poolShare)
|
||||||
totalLiquidity += poolLiquidity
|
totalLiquidity += poolLiquidity
|
||||||
|
@ -4,37 +4,36 @@ import { FormPublishData } from '../_types'
|
|||||||
import { useFormikContext } from 'formik'
|
import { useFormikContext } from 'formik'
|
||||||
import AssetContent from 'src/components/Asset/AssetContent'
|
import AssetContent from 'src/components/Asset/AssetContent'
|
||||||
import { transformPublishFormToDdo } from '../_utils'
|
import { transformPublishFormToDdo } from '../_utils'
|
||||||
import { Asset } from '@oceanprotocol/lib'
|
import { AssetExtended } from 'src/@types/AssetExtended'
|
||||||
|
import { ZERO_ADDRESS } from '@oceanprotocol/lib'
|
||||||
|
|
||||||
export default function Preview(): ReactElement {
|
export default function Preview(): ReactElement {
|
||||||
const [asset, setAsset] = useState<Asset>()
|
const [asset, setAsset] = useState<AssetExtended>()
|
||||||
const [accessDetails, setAccessDetails] = useState<AccessDetails>()
|
|
||||||
const { values } = useFormikContext<FormPublishData>()
|
const { values } = useFormikContext<FormPublishData>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function makeDdo() {
|
async function makeDdo() {
|
||||||
const asset = await transformPublishFormToDdo(values)
|
const asset = (await transformPublishFormToDdo(values)) as AssetExtended
|
||||||
setAsset(asset as Asset)
|
|
||||||
|
|
||||||
// dummy BestPrice to trigger certain AssetActions
|
// dummy BestPrice to trigger certain AssetActions
|
||||||
const accessDetails: AccessDetails = {
|
asset.accessDetails = {
|
||||||
type: values.pricing.type,
|
type: values.pricing.type,
|
||||||
addressOrId: '0x...',
|
addressOrId: ZERO_ADDRESS,
|
||||||
price: values.pricing.price,
|
price: values.pricing.price,
|
||||||
baseToken: {
|
baseToken: {
|
||||||
address: '0x..',
|
address: ZERO_ADDRESS,
|
||||||
name: '',
|
name: 'OCEAN',
|
||||||
symbol: ''
|
symbol: 'OCEAN'
|
||||||
},
|
},
|
||||||
datatoken: {
|
datatoken: {
|
||||||
address: '0x..',
|
address: ZERO_ADDRESS,
|
||||||
name: '',
|
name: '',
|
||||||
symbol: ''
|
symbol: ''
|
||||||
},
|
},
|
||||||
owned: false,
|
isPurchasable: true,
|
||||||
|
isOwned: false,
|
||||||
validOrderTx: ''
|
validOrderTx: ''
|
||||||
}
|
}
|
||||||
setAccessDetails(accessDetails)
|
setAsset(asset)
|
||||||
}
|
}
|
||||||
makeDdo()
|
makeDdo()
|
||||||
}, [values])
|
}, [values])
|
||||||
@ -44,7 +43,7 @@ export default function Preview(): ReactElement {
|
|||||||
<h2 className={styles.previewTitle}>Preview</h2>
|
<h2 className={styles.previewTitle}>Preview</h2>
|
||||||
|
|
||||||
<h3 className={styles.assetTitle}>{values.metadata.name}</h3>
|
<h3 className={styles.assetTitle}>{values.metadata.name}</h3>
|
||||||
<AssetContent asset={asset} />
|
{asset && <AssetContent asset={asset} />}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
.token {
|
.token {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: flex-start;
|
||||||
margin-bottom: calc(var(--spacer) / 2);
|
margin-bottom: calc(var(--spacer) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,8 +60,7 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
max-width: 12rem;
|
margin: auto 0;
|
||||||
margin: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.max {
|
.max {
|
||||||
@ -73,3 +72,13 @@
|
|||||||
.weight strong {
|
.weight strong {
|
||||||
color: var(--font-color-text);
|
color: var(--font-color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 41rem) {
|
||||||
|
.token {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.data {
|
||||||
|
margin: auto;
|
||||||
|
max-width: 12rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
.fees {
|
.fees {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--spacer);
|
gap: var(--spacer);
|
||||||
grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
|
||||||
padding: var(--spacer);
|
padding: var(--spacer) 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fees > div {
|
.fees > div {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
justify-self: center;
|
justify-self: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fees label {
|
.fees label {
|
||||||
@ -18,3 +18,10 @@
|
|||||||
.fees input {
|
.fees input {
|
||||||
max-width: 5rem;
|
max-width: 5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 25rem) {
|
||||||
|
.fees {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
|
||||||
|
padding: var(--spacer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,14 +1,31 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement, useEffect } from 'react'
|
||||||
|
import { useFormikContext } from 'formik'
|
||||||
|
import { FormPublishData } from '../_types'
|
||||||
import FormHelp from '@shared/FormInput/Help'
|
import FormHelp from '@shared/FormInput/Help'
|
||||||
import Price from './Price'
|
import Price from './Price'
|
||||||
import styles from './Dynamic.module.css'
|
import styles from './Dynamic.module.css'
|
||||||
|
|
||||||
export default function Free({ content }: { content: any }): ReactElement {
|
export default function Free({ content }: { content: any }): ReactElement {
|
||||||
|
// connect with Form state, use for conditional field rendering
|
||||||
|
const { values, setFieldValue } = useFormikContext<FormPublishData>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// if the user has agreed, then set pricing to continue
|
||||||
|
if (values.pricing.freeAgreement) {
|
||||||
|
setFieldValue('pricing.price', 1)
|
||||||
|
setFieldValue('pricing.amountDataToken', 1000)
|
||||||
|
} else {
|
||||||
|
// disabled continue button if the user hasn't agree to the "free agreement"
|
||||||
|
setFieldValue('pricing.price', 0)
|
||||||
|
setFieldValue('pricing.amountDataToken', 0)
|
||||||
|
}
|
||||||
|
}, [setFieldValue, values])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormHelp>{content.info}</FormHelp>
|
<FormHelp>{content.info}</FormHelp>
|
||||||
<h4 className={styles.title}>Price</h4>
|
<h4 className={styles.title}>Price</h4>
|
||||||
<Price />
|
<Price content={content} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.free {
|
.free {
|
||||||
text-align: center;
|
text-align: left;
|
||||||
margin: calc(var(--spacer) / 2) 0;
|
margin: calc(var(--spacer) / 2);
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.free [class*='FormInput_field'] {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
import Conversion from '@shared/Price/Conversion'
|
import Conversion from '@shared/Price/Conversion'
|
||||||
import { useField, useFormikContext } from 'formik'
|
import { Field, useField, useFormikContext } from 'formik'
|
||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import Input from '@shared/FormInput'
|
import Input from '@shared/FormInput'
|
||||||
import Error from './Error'
|
import Error from './Error'
|
||||||
import PriceUnit from '@shared/Price/PriceUnit'
|
import PriceUnit from '@shared/Price/PriceUnit'
|
||||||
import styles from './Price.module.css'
|
import styles from './Price.module.css'
|
||||||
import { FormPublishData } from '../_types'
|
import { FormPublishData } from '../_types'
|
||||||
|
import { getFieldContent } from '../_utils'
|
||||||
|
|
||||||
export default function Price({
|
export default function Price({
|
||||||
firstPrice
|
firstPrice,
|
||||||
|
content
|
||||||
}: {
|
}: {
|
||||||
firstPrice?: string
|
firstPrice?: string
|
||||||
|
content?: any
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const [field, meta] = useField('pricing.price')
|
const [field, meta] = useField('pricing.price')
|
||||||
|
|
||||||
@ -20,7 +23,13 @@ export default function Price({
|
|||||||
return (
|
return (
|
||||||
<div className={styles.price}>
|
<div className={styles.price}>
|
||||||
{values.pricing.type === 'free' ? (
|
{values.pricing.type === 'free' ? (
|
||||||
<h4 className={styles.free}>Free</h4>
|
<div className={styles.free}>
|
||||||
|
<Field
|
||||||
|
{...getFieldContent('freeAgreement', content.fields)}
|
||||||
|
component={Input}
|
||||||
|
name="pricing.freeAgreement"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pricing ul > li[class*='Tabs_tab'] {
|
||||||
|
padding: calc(var(--spacer) / 4) var(--spacer);
|
||||||
|
}
|
||||||
|
|
||||||
.pricing [class*='Tabs_tabContent'] {
|
.pricing [class*='Tabs_tabContent'] {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
|
@ -23,7 +23,9 @@ export default function PricingFields(): ReactElement {
|
|||||||
function handleTabChange(tabName: string) {
|
function handleTabChange(tabName: string) {
|
||||||
const type = tabName.toLowerCase()
|
const type = tabName.toLowerCase()
|
||||||
setFieldValue('pricing.type', type)
|
setFieldValue('pricing.type', type)
|
||||||
type === 'dynamic' && setFieldValue('pricing.amountDataToken', 1000)
|
setFieldValue('pricing.price', 0)
|
||||||
|
setFieldValue('pricing.freeAgreement', false)
|
||||||
|
type !== 'free' && setFieldValue('pricing.amountDataToken', 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always update everything when price value changes
|
// Always update everything when price value changes
|
||||||
@ -42,7 +44,14 @@ export default function PricingFields(): ReactElement {
|
|||||||
: 0
|
: 0
|
||||||
|
|
||||||
setFieldValue('pricing.amountDataToken', amountDataToken)
|
setFieldValue('pricing.amountDataToken', amountDataToken)
|
||||||
}, [price, amountOcean, weightOnOcean, weightOnDataToken, type])
|
}, [
|
||||||
|
price,
|
||||||
|
amountOcean,
|
||||||
|
weightOnOcean,
|
||||||
|
weightOnDataToken,
|
||||||
|
type,
|
||||||
|
setFieldValue
|
||||||
|
])
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
appConfig.allowFixedPricing === 'true'
|
appConfig.allowFixedPricing === 'true'
|
||||||
@ -71,6 +80,7 @@ export default function PricingFields(): ReactElement {
|
|||||||
handleTabChange={handleTabChange}
|
handleTabChange={handleTabChange}
|
||||||
defaultIndex={type === 'dynamic' ? 1 : type === 'free' ? 2 : 0}
|
defaultIndex={type === 'dynamic' ? 1 : type === 'free' ? 2 : 0}
|
||||||
className={styles.pricing}
|
className={styles.pricing}
|
||||||
|
showRadio
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,15 @@
|
|||||||
.title {
|
.title {
|
||||||
font-size: var(--font-size-large);
|
font-size: var(--font-size-large);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.txs {
|
||||||
|
display: block;
|
||||||
|
margin-left: 1.5rem;
|
||||||
margin-bottom: calc(var(--spacer) / 4);
|
margin-bottom: calc(var(--spacer) / 4);
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
@ -78,3 +86,18 @@
|
|||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 30rem) {
|
||||||
|
.txs {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 40rem) {
|
||||||
|
.title {
|
||||||
|
margin-bottom: calc(var(--spacer) / 2);
|
||||||
|
}
|
||||||
|
.txs {
|
||||||
|
margin-left: calc(var(--spacer) / 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -9,8 +9,8 @@ export function Feedback(): ReactElement {
|
|||||||
|
|
||||||
const items = Object.entries(values.feedback).map(([key, value], index) => (
|
const items = Object.entries(values.feedback).map(([key, value], index) => (
|
||||||
<li key={index} className={styles[value.status]}>
|
<li key={index} className={styles[value.status]}>
|
||||||
<h3 className={styles.title}>
|
<h3 className={styles.title}>{value.name}</h3>
|
||||||
{value.name}
|
<div className={styles.txs}>
|
||||||
{value.txCount > 0 && (
|
{value.txCount > 0 && (
|
||||||
<TransactionCount
|
<TransactionCount
|
||||||
txCount={value.txCount}
|
txCount={value.txCount}
|
||||||
@ -18,7 +18,7 @@ export function Feedback(): ReactElement {
|
|||||||
txHash={value.txHash}
|
txHash={value.txHash}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</h3>
|
</div>
|
||||||
<p className={styles.description}>{value.description}</p>
|
<p className={styles.description}>{value.description}</p>
|
||||||
{value.errorMessage && (
|
{value.errorMessage && (
|
||||||
<span className={styles.errorMessage}>{value.errorMessage}</span>
|
<span className={styles.errorMessage}>{value.errorMessage}</span>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
.txHash {
|
.txHash {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: calc(var(--spacer) / 2);
|
margin: calc(var(--spacer) / 4) calc(var(--spacer) / 4);
|
||||||
margin-top: calc(var(--spacer) / 2);
|
|
||||||
font-size: var(--font-size-small);
|
font-size: var(--font-size-small);
|
||||||
font-family: var(--font-family-base);
|
font-family: var(--font-family-base);
|
||||||
font-weight: var(--font-weight-base);
|
font-weight: var(--font-weight-base);
|
||||||
|
@ -96,7 +96,8 @@ export const initialValues: FormPublishData = {
|
|||||||
amountOcean: 50,
|
amountOcean: 50,
|
||||||
weightOnOcean: '5', // 50% on OCEAN
|
weightOnOcean: '5', // 50% on OCEAN
|
||||||
weightOnDataToken: '5', // 50% on datatoken
|
weightOnDataToken: '5', // 50% on datatoken
|
||||||
swapFee: 0.1 // in %
|
swapFee: 0.1, // in %
|
||||||
|
freeAgreement: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { ServiceComputeOptions } from '@oceanprotocol/lib'
|
import { ServiceComputeOptions } from '@oceanprotocol/lib'
|
||||||
import { NftMetadata } from '@utils/nft'
|
import { NftMetadata } from '@utils/nft'
|
||||||
import { ReactElement } from 'react'
|
import { ReactElement } from 'react'
|
||||||
|
import { PriceOptions } from 'src/@types/Price'
|
||||||
|
|
||||||
interface FileMetadata {
|
interface FileMetadata {
|
||||||
url: string
|
url: string
|
||||||
|
@ -18,6 +18,8 @@ import {
|
|||||||
import { mapTimeoutStringToSeconds } from '@utils/ddo'
|
import { mapTimeoutStringToSeconds } from '@utils/ddo'
|
||||||
import { generateNftCreateData } from '@utils/nft'
|
import { generateNftCreateData } from '@utils/nft'
|
||||||
import { getEncryptedFiles } from '@utils/provider'
|
import { getEncryptedFiles } from '@utils/provider'
|
||||||
|
import { getSiteMetadata } from '@utils/siteConfig'
|
||||||
|
import Decimal from 'decimal.js'
|
||||||
import slugify from 'slugify'
|
import slugify from 'slugify'
|
||||||
import Web3 from 'web3'
|
import Web3 from 'web3'
|
||||||
import {
|
import {
|
||||||
@ -191,7 +193,6 @@ export async function transformPublishFormToDdo(
|
|||||||
export async function createTokensAndPricing(
|
export async function createTokensAndPricing(
|
||||||
values: FormPublishData,
|
values: FormPublishData,
|
||||||
accountId: string,
|
accountId: string,
|
||||||
marketFeeAddress: string,
|
|
||||||
config: Config,
|
config: Config,
|
||||||
nftFactory: NftFactory,
|
nftFactory: NftFactory,
|
||||||
web3: Web3
|
web3: Web3
|
||||||
@ -199,19 +200,17 @@ export async function createTokensAndPricing(
|
|||||||
const nftCreateData: NftCreateData = generateNftCreateData(
|
const nftCreateData: NftCreateData = generateNftCreateData(
|
||||||
values.metadata.nft
|
values.metadata.nft
|
||||||
)
|
)
|
||||||
|
const { appConfig } = getSiteMetadata()
|
||||||
LoggerInstance.log('[publish] Creating NFT with metadata', nftCreateData)
|
LoggerInstance.log('[publish] Creating NFT with metadata', nftCreateData)
|
||||||
|
|
||||||
// TODO: cap is hardcoded for now to 1000, this needs to be discussed at some point
|
// TODO: cap is hardcoded for now to 1000, this needs to be discussed at some point
|
||||||
// fee is default 0 for now
|
|
||||||
// TODO: templateIndex is hardcoded for now but this is incorrect, in the future it should be something like 1 for pools, and 2 for fre and free
|
|
||||||
const ercParams: Erc20CreateParams = {
|
const ercParams: Erc20CreateParams = {
|
||||||
templateIndex: values.pricing.type === 'dynamic' ? 1 : 2,
|
templateIndex: values.pricing.type === 'dynamic' ? 1 : 2,
|
||||||
minter: accountId,
|
minter: accountId,
|
||||||
feeManager: accountId,
|
feeManager: accountId,
|
||||||
mpFeeAddress: marketFeeAddress,
|
mpFeeAddress: appConfig.marketFeeAddress,
|
||||||
feeToken: config.oceanTokenAddress,
|
feeToken: config.oceanTokenAddress,
|
||||||
feeAmount: `0`,
|
feeAmount: appConfig.publisherMarketOrderFee,
|
||||||
cap: '1000',
|
cap: '1000',
|
||||||
name: values.services[0].dataTokenOptions.name,
|
name: values.services[0].dataTokenOptions.name,
|
||||||
symbol: values.services[0].dataTokenOptions.symbol
|
symbol: values.services[0].dataTokenOptions.symbol
|
||||||
@ -226,21 +225,22 @@ export async function createTokensAndPricing(
|
|||||||
case 'dynamic': {
|
case 'dynamic': {
|
||||||
// no vesting in market by default, maybe at a later time , vestingAmount and vestedBlocks are hardcoded
|
// no vesting in market by default, maybe at a later time , vestingAmount and vestedBlocks are hardcoded
|
||||||
// we use only ocean as basetoken
|
// we use only ocean as basetoken
|
||||||
// TODO: discuss swapFeeLiquidityProvider, swapFeeMarketPlaceRunner
|
// swapFeeLiquidityProvider is the swap fee of the liquidity providers
|
||||||
|
// swapFeeMarketRunner is the swap fee of the market where the swap occurs
|
||||||
const poolParams: PoolCreationParams = {
|
const poolParams: PoolCreationParams = {
|
||||||
ssContract: config.sideStakingAddress,
|
ssContract: config.sideStakingAddress,
|
||||||
baseTokenAddress: config.oceanTokenAddress,
|
baseTokenAddress: config.oceanTokenAddress,
|
||||||
baseTokenSender: config.erc721FactoryAddress,
|
baseTokenSender: config.erc721FactoryAddress,
|
||||||
publisherAddress: accountId,
|
publisherAddress: accountId,
|
||||||
marketFeeCollector: marketFeeAddress,
|
marketFeeCollector: appConfig.marketFeeAddress,
|
||||||
poolTemplateAddress: config.poolTemplateAddress,
|
poolTemplateAddress: config.poolTemplateAddress,
|
||||||
rate: values.pricing.price.toString(),
|
rate: new Decimal(1).div(values.pricing.price).toString(),
|
||||||
baseTokenDecimals: 18,
|
baseTokenDecimals: 18,
|
||||||
vestingAmount: '0',
|
vestingAmount: '0',
|
||||||
vestedBlocks: 2726000,
|
vestedBlocks: 2726000,
|
||||||
initialBaseTokenLiquidity: values.pricing.amountOcean.toString(),
|
initialBaseTokenLiquidity: values.pricing.amountOcean.toString(),
|
||||||
swapFeeLiquidityProvider: 1e15,
|
swapFeeLiquidityProvider: (values.pricing.swapFee / 100).toString(),
|
||||||
swapFeeMarketRunner: 1e15
|
swapFeeMarketRunner: appConfig.publisherMarketPoolSwapFee
|
||||||
}
|
}
|
||||||
|
|
||||||
LoggerInstance.log(
|
LoggerInstance.log(
|
||||||
@ -249,18 +249,17 @@ export async function createTokensAndPricing(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// the spender in this case is the erc721Factory because we are delegating
|
// the spender in this case is the erc721Factory because we are delegating
|
||||||
const pool = new Pool(web3)
|
|
||||||
const txApprove = await approve(
|
const txApprove = await approve(
|
||||||
web3,
|
web3,
|
||||||
accountId,
|
accountId,
|
||||||
config.oceanTokenAddress,
|
config.oceanTokenAddress,
|
||||||
config.erc721FactoryAddress,
|
config.erc721FactoryAddress,
|
||||||
'200',
|
values.pricing.amountOcean.toString(),
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
LoggerInstance.log('[publish] pool.approve tx', txApprove)
|
LoggerInstance.log('[publish] pool.approve tx', txApprove, nftFactory)
|
||||||
|
|
||||||
const result = await nftFactory.createNftErcWithPool(
|
const result = await nftFactory.createNftErc20WithPool(
|
||||||
accountId,
|
accountId,
|
||||||
nftCreateData,
|
nftCreateData,
|
||||||
ercParams,
|
ercParams,
|
||||||
@ -279,11 +278,11 @@ export async function createTokensAndPricing(
|
|||||||
fixedRateAddress: config.fixedRateExchangeAddress,
|
fixedRateAddress: config.fixedRateExchangeAddress,
|
||||||
baseTokenAddress: config.oceanTokenAddress,
|
baseTokenAddress: config.oceanTokenAddress,
|
||||||
owner: accountId,
|
owner: accountId,
|
||||||
marketFeeCollector: marketFeeAddress,
|
marketFeeCollector: appConfig.marketFeeAddress,
|
||||||
baseTokenDecimals: 18,
|
baseTokenDecimals: 18,
|
||||||
datatokenDecimals: 18,
|
datatokenDecimals: 18,
|
||||||
fixedRate: values.pricing.price.toString(),
|
fixedRate: values.pricing.price.toString(),
|
||||||
marketFee: 1e15,
|
marketFee: appConfig.publisherMarketFixedSwapFee,
|
||||||
withMint: true
|
withMint: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,7 +291,7 @@ export async function createTokensAndPricing(
|
|||||||
freParams
|
freParams
|
||||||
)
|
)
|
||||||
|
|
||||||
const result = await nftFactory.createNftErcWithFixedRate(
|
const result = await nftFactory.createNftErc20WithFixedRate(
|
||||||
accountId,
|
accountId,
|
||||||
nftCreateData,
|
nftCreateData,
|
||||||
ercParams,
|
ercParams,
|
||||||
@ -324,7 +323,7 @@ export async function createTokensAndPricing(
|
|||||||
dispenserParams
|
dispenserParams
|
||||||
)
|
)
|
||||||
|
|
||||||
const result = await nftFactory.createNftErcWithDispenser(
|
const result = await nftFactory.createNftErc20WithDispenser(
|
||||||
accountId,
|
accountId,
|
||||||
nftCreateData,
|
nftCreateData,
|
||||||
ercParams,
|
ercParams,
|
||||||
|
@ -21,7 +21,6 @@ import {
|
|||||||
LoggerInstance,
|
LoggerInstance,
|
||||||
DDO
|
DDO
|
||||||
} from '@oceanprotocol/lib'
|
} from '@oceanprotocol/lib'
|
||||||
import { useSiteMetadata } from '@hooks/useSiteMetadata'
|
|
||||||
import { getOceanConfig } from '@utils/ocean'
|
import { getOceanConfig } from '@utils/ocean'
|
||||||
import { validationSchema } from './_validation'
|
import { validationSchema } from './_validation'
|
||||||
import { useAbortController } from '@hooks/useAbortController'
|
import { useAbortController } from '@hooks/useAbortController'
|
||||||
@ -38,7 +37,6 @@ export default function PublishPage({
|
|||||||
const { accountId, web3, chainId } = useWeb3()
|
const { accountId, web3, chainId } = useWeb3()
|
||||||
const { isInPurgatory, purgatoryData } = useAccountPurgatory(accountId)
|
const { isInPurgatory, purgatoryData } = useAccountPurgatory(accountId)
|
||||||
const scrollToRef = useRef()
|
const scrollToRef = useRef()
|
||||||
const { appConfig } = useSiteMetadata()
|
|
||||||
const nftFactory = useNftFactory()
|
const nftFactory = useNftFactory()
|
||||||
const newAbortController = useAbortController()
|
const newAbortController = useAbortController()
|
||||||
|
|
||||||
@ -75,7 +73,6 @@ export default function PublishPage({
|
|||||||
await createTokensAndPricing(
|
await createTokensAndPricing(
|
||||||
values,
|
values,
|
||||||
accountId,
|
accountId,
|
||||||
appConfig.marketFeeAddress,
|
|
||||||
config,
|
config,
|
||||||
nftFactory,
|
nftFactory,
|
||||||
web3
|
web3
|
||||||
|
@ -61,8 +61,9 @@ export default function SearchPage({
|
|||||||
setTotalResults(undefined)
|
setTotalResults(undefined)
|
||||||
const queryResult = await getResults(parsed, chainIds, newCancelToken())
|
const queryResult = await getResults(parsed, chainIds, newCancelToken())
|
||||||
setQueryResult(queryResult)
|
setQueryResult(queryResult)
|
||||||
setTotalResults(queryResult.totalResults)
|
|
||||||
setTotalPagesNumber(queryResult.totalPages)
|
setTotalResults(queryResult?.totalResults || 0)
|
||||||
|
setTotalPagesNumber(queryResult?.totalPages || 0)
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
},
|
},
|
||||||
[newCancelToken, setTotalPagesNumber, setTotalResults]
|
[newCancelToken, setTotalPagesNumber, setTotalResults]
|
||||||
|
@ -123,7 +123,7 @@ export function getSearchQuery(
|
|||||||
|
|
||||||
const filters: FilterTerm[] = []
|
const filters: FilterTerm[] = []
|
||||||
accessType !== undefined &&
|
accessType !== undefined &&
|
||||||
filters.push(getFilterTerm('nft.type', accessType))
|
filters.push(getFilterTerm('services.type', accessType))
|
||||||
serviceType !== undefined &&
|
serviceType !== undefined &&
|
||||||
filters.push(getFilterTerm('metadata.type', serviceType))
|
filters.push(getFilterTerm('metadata.type', serviceType))
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user