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_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"
|
||||
|
||||
|
||||
|
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:
|
||||
branches:
|
||||
- main
|
||||
- v4
|
||||
tags:
|
||||
- '**'
|
||||
pull_request:
|
||||
|
4
.github/workflows/codeql-analysis.yml
vendored
4
.github/workflows/codeql-analysis.yml
vendored
@ -13,10 +13,10 @@ name: 'CodeQL'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
branches: [main, v4]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [main]
|
||||
branches: [main, v4]
|
||||
schedule:
|
||||
- cron: '21 8 * * 4'
|
||||
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -11,6 +11,7 @@ coverage
|
||||
repo-metadata.json
|
||||
networks-metadata.json
|
||||
src/@types/subgraph
|
||||
src/@types/apollo/
|
||||
graphql.schema.json
|
||||
src/@types/graph.types.ts
|
||||
tsconfig.tsbuildinfo
|
||||
|
@ -7,7 +7,7 @@ module.exports = {
|
||||
// return appConfig.metadataCacheUri
|
||||
metadataCacheUri:
|
||||
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.
|
||||
// This preselects the Chains user preferences.
|
||||
@ -22,6 +22,25 @@ module.exports = {
|
||||
marketFeeAddress:
|
||||
process.env.NEXT_PUBLIC_MARKET_FEE_ADDRESS ||
|
||||
'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
|
||||
// see: https://api.coingecko.com/api/v3/simple/supported_vs_currencies
|
||||
|
@ -28,7 +28,16 @@
|
||||
},
|
||||
"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": {
|
||||
|
@ -6,7 +6,7 @@
|
||||
"name": "nft",
|
||||
"label": "Data 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
|
||||
},
|
||||
{
|
||||
|
@ -17,24 +17,35 @@ module.exports = (phase, { defaultConfig }) => {
|
||||
type: 'asset/resource'
|
||||
}
|
||||
)
|
||||
|
||||
// for old ocean.js, most likely can be removed later on
|
||||
config.plugins.push(
|
||||
new options.webpack.IgnorePlugin({
|
||||
resourceRegExp: /^electron$/
|
||||
})
|
||||
)
|
||||
|
||||
config.resolve.fallback = {
|
||||
const 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,
|
||||
crypto: false,
|
||||
os: false,
|
||||
stream: false,
|
||||
http: false,
|
||||
https: 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'
|
||||
? defaultConfig.webpack(config, options)
|
||||
: 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",
|
||||
"deploy:s3": "bash scripts/deploy-s3.sh",
|
||||
"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": {
|
||||
"@coingecko/cryptoformat": "^0.4.4",
|
||||
"@loadable/component": "^5.15.2",
|
||||
"@oceanprotocol/art": "^3.2.0",
|
||||
"@oceanprotocol/lib": "^1.0.0-next.11",
|
||||
"@oceanprotocol/lib": "^1.0.0-next.21",
|
||||
"@oceanprotocol/typographies": "^0.1.0",
|
||||
"@portis/web3": "^4.0.6",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
@ -30,7 +30,6 @@
|
||||
"bignumber.js": "^9.0.2",
|
||||
"chart.js": "^3.7.0",
|
||||
"classnames": "^2.3.1",
|
||||
"d3": "^7.3.0",
|
||||
"date-fns": "^2.28.0",
|
||||
"decimal.js": "^10.3.1",
|
||||
"dom-confetti": "^0.2.2",
|
||||
@ -44,7 +43,7 @@
|
||||
"jwt-decode": "^3.1.2",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.omit": "^4.5.0",
|
||||
"next": "^12.0.9",
|
||||
"next": "^12.1.0",
|
||||
"query-string": "^7.1.0",
|
||||
"react": "^17.0.2",
|
||||
"react-chartjs-2": "^4.0.1",
|
||||
@ -95,10 +94,13 @@
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"https-browserify": "^1.0.0",
|
||||
"husky": "^7.0.4",
|
||||
"prettier": "^2.5.1",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"process": "^0.11.10",
|
||||
"serve": "^13.0.2",
|
||||
"stream-http": "^2.8.3",
|
||||
"typescript": "^4.5.4"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -69,7 +69,8 @@ function AssetProvider({
|
||||
|
||||
if (!asset) {
|
||||
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)
|
||||
} else {
|
||||
@ -95,7 +96,6 @@ function AssetProvider({
|
||||
// -----------------------------------
|
||||
const fetchAccessDetails = useCallback(async (): Promise<void> => {
|
||||
if (!asset?.chainId || !asset?.services) return
|
||||
|
||||
const accessDetails = await getAccessDetails(
|
||||
asset.chainId,
|
||||
asset.services[0].datatokenAddress,
|
||||
|
@ -10,9 +10,9 @@ export const poolDataQuery = gql`
|
||||
poolData: pool(id: $pool) {
|
||||
id
|
||||
totalShares
|
||||
poolFee
|
||||
opfFee
|
||||
marketFee
|
||||
liquidityProviderFee
|
||||
opcFee
|
||||
marketSwapFee
|
||||
spotPrice
|
||||
baseToken {
|
||||
address
|
||||
|
@ -30,7 +30,8 @@ const initialPoolInfo: Partial<PoolInfo> = {
|
||||
}
|
||||
|
||||
const initialPoolInfoUser: Partial<PoolInfoUser> = {
|
||||
liquidity: new Decimal(0)
|
||||
liquidity: new Decimal(0),
|
||||
poolShares: '0'
|
||||
}
|
||||
|
||||
const initialPoolInfoCreator: Partial<PoolInfoUser> = initialPoolInfoUser
|
||||
@ -68,7 +69,7 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
|
||||
setPoolData(response.poolData)
|
||||
setPoolInfoUser((prevState) => ({
|
||||
...prevState,
|
||||
poolShares: response.poolDataUser?.shares[0]?.shares
|
||||
poolShares: response.poolDataUser?.shares[0]?.shares || '0'
|
||||
}))
|
||||
setPoolSnapshots(response.poolSnapshots)
|
||||
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])
|
||||
|
||||
// Helper: start interval fetching
|
||||
// Having `accountId` as dependency is important for interval to
|
||||
// change after user account switch.
|
||||
const initFetchInterval = useCallback(() => {
|
||||
if (fetchInterval) return
|
||||
|
||||
@ -89,6 +88,10 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
|
||||
)
|
||||
}, refreshInterval)
|
||||
setFetchInterval(newInterval)
|
||||
|
||||
// Having `accountId` as dependency is important for interval to
|
||||
// change after user account switch.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [fetchInterval, fetchAllData, accountId])
|
||||
|
||||
useEffect(() => {
|
||||
@ -115,10 +118,10 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
|
||||
useEffect(() => {
|
||||
if (!poolData) return
|
||||
|
||||
// Fees
|
||||
const poolFee = getFee(poolData.poolFee)
|
||||
const marketFee = getFee(poolData.marketFee)
|
||||
const opfFee = getFee(poolData.opfFee)
|
||||
// Fees - this will be renamed again in subgraph
|
||||
const poolFee = getFee(poolData.liquidityProviderFee)
|
||||
const marketFee = getFee(poolData.marketSwapFee)
|
||||
const opfFee = getFee(poolData.opcFee)
|
||||
|
||||
// Total Liquidity
|
||||
const totalLiquidityInOcean = isValidNumber(poolData.spotPrice)
|
||||
@ -191,13 +194,13 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
|
||||
!poolData ||
|
||||
!poolInfo?.totalPoolTokens ||
|
||||
!asset?.chainId ||
|
||||
!accountId
|
||||
!accountId ||
|
||||
!poolInfoUser
|
||||
)
|
||||
return
|
||||
|
||||
// Staking bot receives half the pool shares so for display purposes
|
||||
// we can multiply by 2 as we have a hardcoded 50/50 pool weight.
|
||||
const userPoolShares = new Decimal(poolInfoUser.poolShares)
|
||||
const userPoolShares = new Decimal(poolInfoUser.poolShares || 0)
|
||||
.mul(2)
|
||||
.toString()
|
||||
|
||||
@ -235,6 +238,8 @@ function PoolProvider({ children }: { children: ReactNode }): ReactElement {
|
||||
poolShares: userPoolShares,
|
||||
...newPoolInfoUser
|
||||
})
|
||||
// poolInfoUser was not added on purpose, we use setPoolInfoUser so it will just loop
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
poolData,
|
||||
poolInfoUser?.poolShares,
|
||||
|
@ -220,18 +220,14 @@ function ProfileProvider({
|
||||
async (cancelToken: CancelToken) => {
|
||||
if (!accountId || !chainIds) return
|
||||
|
||||
const didList: string[] = []
|
||||
const dtList: string[] = []
|
||||
const tokenOrders = await getUserTokenOrders(accountId, chainIds)
|
||||
|
||||
for (let i = 0; i < tokenOrders?.length; i++) {
|
||||
const did = web3.utils
|
||||
.toChecksumAddress(tokenOrders[i].datatoken.address)
|
||||
.replace('0x', 'did:op:')
|
||||
didList.push(did)
|
||||
dtList.push(tokenOrders[i].datatoken.address)
|
||||
}
|
||||
|
||||
const downloads = await getDownloadAssets(
|
||||
didList,
|
||||
dtList,
|
||||
tokenOrders,
|
||||
chainIds,
|
||||
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 siteContent from '../../../content/site.json'
|
||||
import appConfig from '../../../app.config'
|
||||
import { getSiteMetadata } from '@utils/siteConfig'
|
||||
|
||||
export function useSiteMetadata(): UseSiteMetadata {
|
||||
const siteMeta: UseSiteMetadata = {
|
||||
...siteContent,
|
||||
appConfig
|
||||
}
|
||||
|
||||
return siteMeta
|
||||
return getSiteMetadata()
|
||||
}
|
||||
|
@ -23,6 +23,12 @@ export interface UseSiteMetadata {
|
||||
chainIds: number[]
|
||||
chainIdsSupported: number[]
|
||||
marketFeeAddress: string
|
||||
publisherMarketOrderFee: string
|
||||
publisherMarketPoolSwapFee: string
|
||||
publisherMarketFixedSwapFee: string
|
||||
consumeMarketOrderFee: string
|
||||
consumeMarketPoolSwapFee: string
|
||||
consumeMarketFixedSwapFee: string
|
||||
currencies: string[]
|
||||
portisId: 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 {
|
||||
type: 'dynamic' | 'fixed' | 'free' | ''
|
||||
price: number
|
||||
// if type is dynamic this is the pool address, for fixed/free this is an id.
|
||||
price: string
|
||||
addressOrId: string
|
||||
baseToken: TokenInfo
|
||||
datatoken: TokenInfo
|
||||
isConsumable?: boolean
|
||||
// if there are valid orders for this
|
||||
owned: bool
|
||||
isPurchasable?: boolean
|
||||
isOwned: bool
|
||||
validOrderTx: string
|
||||
publisherMarketOrderFee: string
|
||||
}
|
||||
|
||||
interface PriceOptions {
|
||||
@ -20,4 +60,5 @@ interface PriceOptions {
|
||||
weightOnOcean: string
|
||||
// easier to keep this as number for Yup input validation
|
||||
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
|
||||
_shards: ShardsResponse
|
||||
hits: {
|
||||
total: number
|
||||
total: {
|
||||
relation: string
|
||||
value: number
|
||||
}
|
||||
max_score: number
|
||||
hits: Array<{
|
||||
_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_tokens as TokensPrice
|
||||
} from '../@types/subgraph/TokensPriceQuery'
|
||||
import { Asset } from '@oceanprotocol/lib'
|
||||
import { Asset, ProviderInstance } from '@oceanprotocol/lib'
|
||||
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`
|
||||
query TokensPriceQuery($datatokenIds: [ID!], $account: String) {
|
||||
@ -39,6 +44,7 @@ const TokensPriceQuery = gql`
|
||||
}
|
||||
fixedRateExchanges {
|
||||
id
|
||||
exchangeId
|
||||
price
|
||||
baseToken {
|
||||
symbol
|
||||
@ -99,6 +105,7 @@ const TokenPriceQuery = gql`
|
||||
}
|
||||
fixedRateExchanges {
|
||||
id
|
||||
exchangeId
|
||||
price
|
||||
baseToken {
|
||||
symbol
|
||||
@ -132,25 +139,33 @@ const TokenPriceQuery = gql`
|
||||
}
|
||||
`
|
||||
|
||||
// TODO: fill in fees after subgraph update
|
||||
function getAccessDetailsFromTokenPrice(
|
||||
tokenPrice: TokenPrice | TokensPrice,
|
||||
timeout?: number
|
||||
): AccessDetails {
|
||||
const accessDetails = {} as AccessDetails
|
||||
|
||||
if (!timeout && !tokenPrice.orders && tokenPrice.orders.length > 0) {
|
||||
if (
|
||||
tokenPrice &&
|
||||
timeout &&
|
||||
tokenPrice.orders &&
|
||||
tokenPrice.orders.length > 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
|
||||
}
|
||||
|
||||
// TODO: fetch order fee from sub query
|
||||
accessDetails.publisherMarketOrderFee = '0'
|
||||
|
||||
// free is always the best price
|
||||
if (tokenPrice.dispensers && tokenPrice.dispensers.length > 0) {
|
||||
const dispenser = tokenPrice.dispensers[0]
|
||||
accessDetails.type = 'free'
|
||||
accessDetails.addressOrId = dispenser.id
|
||||
accessDetails.price = 0
|
||||
accessDetails.isConsumable = dispenser.active
|
||||
accessDetails.addressOrId = dispenser.token.id
|
||||
accessDetails.price = '0'
|
||||
accessDetails.isPurchasable = dispenser.active
|
||||
accessDetails.datatoken = {
|
||||
address: dispenser.token.id,
|
||||
name: dispenser.token.name,
|
||||
@ -164,21 +179,21 @@ function getAccessDetailsFromTokenPrice(
|
||||
tokenPrice.fixedRateExchanges &&
|
||||
tokenPrice.fixedRateExchanges.length > 0
|
||||
) {
|
||||
const fre = tokenPrice.fixedRateExchanges[0]
|
||||
const fixed = tokenPrice.fixedRateExchanges[0]
|
||||
accessDetails.type = 'fixed'
|
||||
accessDetails.addressOrId = fre.id
|
||||
accessDetails.price = fre.price
|
||||
accessDetails.addressOrId = fixed.exchangeId
|
||||
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.
|
||||
accessDetails.isConsumable = fre.active
|
||||
accessDetails.isPurchasable = fixed.active
|
||||
accessDetails.baseToken = {
|
||||
address: fre.baseToken.address,
|
||||
name: fre.baseToken.name,
|
||||
symbol: fre.baseToken.symbol
|
||||
address: fixed.baseToken.address,
|
||||
name: fixed.baseToken.name,
|
||||
symbol: fixed.baseToken.symbol
|
||||
}
|
||||
accessDetails.datatoken = {
|
||||
address: fre.datatoken.address,
|
||||
name: fre.datatoken.name,
|
||||
symbol: fre.datatoken.symbol
|
||||
address: fixed.datatoken.address,
|
||||
name: fixed.datatoken.name,
|
||||
symbol: fixed.datatoken.symbol
|
||||
}
|
||||
return accessDetails
|
||||
}
|
||||
@ -188,10 +203,10 @@ function getAccessDetailsFromTokenPrice(
|
||||
const pool = tokenPrice.pools[0]
|
||||
accessDetails.type = 'dynamic'
|
||||
accessDetails.addressOrId = pool.id
|
||||
// TODO: this needs to be consumePrice
|
||||
accessDetails.price = pool.spotPrice
|
||||
// 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 = {
|
||||
address: pool.baseToken.address,
|
||||
name: pool.baseToken.name,
|
||||
@ -208,20 +223,92 @@ function getAccessDetailsFromTokenPrice(
|
||||
}
|
||||
|
||||
/**
|
||||
* returns various consume details for the desired datatoken
|
||||
* @param chain chain on which the datatoken is preset
|
||||
* @param datatokenAddress address of the datatoken
|
||||
* @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
|
||||
* @returns AccessDetails
|
||||
* This will be used to get price including feed before ordering
|
||||
* @param {AssetExtended} asset
|
||||
* @return {Promise<OrdePriceAndFee>}
|
||||
*/
|
||||
export async function getOrderPriceAndFees(
|
||||
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(
|
||||
chain: number,
|
||||
chainId: number,
|
||||
datatokenAddress: string,
|
||||
timeout?: number,
|
||||
account = ''
|
||||
): Promise<AccessDetails> {
|
||||
const queryContext = getQueryContext(Number(chain))
|
||||
const queryContext = getQueryContext(Number(chainId))
|
||||
const tokenQueryResult: OperationResult<
|
||||
TokenPriceQuery,
|
||||
{ datatokenId: string; account: string }
|
||||
@ -229,7 +316,7 @@ export async function getAccessDetails(
|
||||
TokenPriceQuery,
|
||||
{
|
||||
datatokenId: datatokenAddress.toLowerCase(),
|
||||
account: account.toLowerCase()
|
||||
account: account?.toLowerCase()
|
||||
},
|
||||
queryContext
|
||||
)
|
||||
@ -247,15 +334,14 @@ export async function getAccessDetailsForAssets(
|
||||
const chainAssetLists: { [key: number]: string[] } = {}
|
||||
|
||||
for (const asset of assets) {
|
||||
// harcoded until we have chainId on assets
|
||||
if (chainAssetLists[asset.chainId]) {
|
||||
chainAssetLists[asset.chainId].push(
|
||||
asset?.services[0].datatokenAddress.toLowerCase()
|
||||
asset.services[0].datatokenAddress.toLowerCase()
|
||||
)
|
||||
} else {
|
||||
chainAssetLists[asset.chainId] = []
|
||||
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 tokenQueryResult: OperationResult<
|
||||
TokensPriceQuery,
|
||||
{ datatokenId: string; account: string }
|
||||
{ datatokenIds: [string]; account: string }
|
||||
> = await fetchData(
|
||||
TokensPriceQuery,
|
||||
{
|
||||
datatokenIds: chainAssetLists[chainKey],
|
||||
account: account.toLowerCase()
|
||||
account: account?.toLowerCase()
|
||||
},
|
||||
queryContext
|
||||
)
|
||||
tokenQueryResult.data?.tokens.forEach((token) => {
|
||||
const accessDetails = getAccessDetailsFromTokenPrice(token)
|
||||
const currentAsset = assetsExtended.find(
|
||||
(asset) => asset.services[0].datatokenAddress.toLowerCase() === token.id
|
||||
)
|
||||
const accessDetails = getAccessDetailsFromTokenPrice(
|
||||
token,
|
||||
currentAsset?.services[0]?.timeout
|
||||
)
|
||||
|
||||
currentAsset.accessDetails = accessDetails
|
||||
})
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ export function transformQueryResult(
|
||||
result.results = (queryResult.hits.hits || []).map(
|
||||
(hit) => hit._source as Asset
|
||||
)
|
||||
result.totalResults = queryResult.hits.total
|
||||
result.totalResults = queryResult.hits.total.value
|
||||
result.totalPages =
|
||||
result.totalResults / size < 1
|
||||
? Math.floor(result.totalResults / size)
|
||||
@ -163,7 +163,7 @@ export async function getAssetsFromDidList(
|
||||
|
||||
const baseParams = {
|
||||
chainIds: chainIds,
|
||||
filters: [getFilterTerm('id', didList)],
|
||||
filters: [getFilterTerm('_id', didList)],
|
||||
ignorePurgatory: true
|
||||
} as BaseQueryParams
|
||||
const query = generateBaseQuery(baseParams)
|
||||
@ -180,19 +180,21 @@ export async function retrieveDDOListByDIDs(
|
||||
chainIds: number[],
|
||||
cancelToken: CancelToken
|
||||
): Promise<Asset[]> {
|
||||
if (didList?.length === 0 || chainIds?.length === 0) return []
|
||||
|
||||
try {
|
||||
if (didList?.length === 0 || chainIds?.length === 0) return []
|
||||
const orderedDDOListByDIDList: Asset[] = []
|
||||
const baseQueryparams = {
|
||||
chainIds,
|
||||
filters: [getFilterTerm('id', didList)],
|
||||
filters: [getFilterTerm('_id', didList)],
|
||||
ignorePurgatory: true
|
||||
} as BaseQueryParams
|
||||
const query = generateBaseQuery(baseQueryparams)
|
||||
const result = await queryMetadata(query, cancelToken)
|
||||
|
||||
didList.forEach((did: string) => {
|
||||
const ddo = result.results.find((ddo: Asset) => ddo.id === did)
|
||||
orderedDDOListByDIDList.push(ddo)
|
||||
if (ddo) orderedDDOListByDIDList.push(ddo)
|
||||
})
|
||||
return orderedDDOListByDIDList
|
||||
} catch (error) {
|
||||
@ -330,22 +332,21 @@ export async function getPublishedAssets(
|
||||
}
|
||||
|
||||
export async function getDownloadAssets(
|
||||
didList: string[],
|
||||
dtList: string[],
|
||||
tokenOrders: OrdersData[],
|
||||
chainIds: number[],
|
||||
cancelToken: CancelToken
|
||||
): Promise<DownloadedAsset[]> {
|
||||
const baseQueryparams = {
|
||||
chainIds,
|
||||
filters: [
|
||||
getFilterTerm('services.datatokenAddress', dtList),
|
||||
getFilterTerm('services.type', 'access')
|
||||
]
|
||||
} as BaseQueryParams
|
||||
const query = generateBaseQuery(baseQueryparams)
|
||||
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 downloadedAssets: DownloadedAsset[] = result.results
|
||||
.map((asset) => {
|
||||
const order = tokenOrders.find(
|
||||
@ -365,6 +366,10 @@ export async function getDownloadAssets(
|
||||
|
||||
return downloadedAssets
|
||||
} 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 } = {
|
||||
99: 'Decrypting file URL...',
|
||||
0: '1/3 Looking for data token. Buying if none found...',
|
||||
1: '2/3 Transfering data token.',
|
||||
2: '3/3 Payment confirmed. Requesting access...'
|
||||
}
|
||||
|
||||
export const publishFeedback: { [key in number]: string } = {
|
||||
0: '1/5 Creating datatoken ...',
|
||||
2: '2/5 Encrypting files ...',
|
||||
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: can be better
|
||||
export function getOrderFeedback(
|
||||
baseTokenSymbol: string,
|
||||
datatokenSymbol: string
|
||||
): { [key in number]: string } {
|
||||
return {
|
||||
0: `Approving and buying one ${datatokenSymbol} from pool`,
|
||||
1: `Ordering asset`,
|
||||
2: `Approving ${baseTokenSymbol} and ordering asset`,
|
||||
3: 'Generating signature to access download url'
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: customize for compute
|
||||
export const computeFeedback: { [key in number]: string } = {
|
||||
0: '1/3 Ordering asset...',
|
||||
1: '2/3 Transfering data token.',
|
||||
2: '3/3 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}.`
|
||||
}
|
||||
0: 'Ordering asset...',
|
||||
1: 'Transfering datatoken.',
|
||||
2: 'Access granted. Starting job...'
|
||||
}
|
||||
|
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
|
||||
// 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 {
|
||||
slug: string
|
||||
frontmatter: { [key: string]: any }
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { renderStaticWaves } from './oceanWaves'
|
||||
import { SvgWaves } from './SvgWaves'
|
||||
|
||||
// https://docs.opensea.io/docs/metadata-standards
|
||||
export interface NftMetadata {
|
||||
@ -21,6 +21,7 @@ function encodeSvg(svgString: string): string {
|
||||
? '<svg'
|
||||
: '<svg xmlns="http://www.w3.org/2000/svg"'
|
||||
)
|
||||
.replace('></path>', '/>')
|
||||
.replace(/"/g, "'")
|
||||
.replace(/%/g, '%25')
|
||||
.replace(/#/g, '%23')
|
||||
@ -32,41 +33,40 @@ function encodeSvg(svgString: string): string {
|
||||
}
|
||||
|
||||
export function generateNftMetadata(): NftMetadata {
|
||||
// TODO: crop image properly in the end as generated SVG waves are a super-wide image,
|
||||
// and add a filled background deciding on either black or white.
|
||||
const image = renderStaticWaves()
|
||||
// const image = new XMLSerializer().serializeToString(waves)
|
||||
// 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>`
|
||||
const waves = new SvgWaves()
|
||||
const svg = waves.generateSvg()
|
||||
|
||||
// TODO: figure out if also image URI needs base64 encoding
|
||||
// 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 = {
|
||||
name: 'Ocean Asset v4 NFT',
|
||||
name: 'Ocean Asset NFT',
|
||||
symbol: 'OCEAN-NFT',
|
||||
description: `This NFT represents an asset in the Ocean Protocol v4 ecosystem.`,
|
||||
// TODO: ideally this includes the final DID
|
||||
external_url: 'https://market.oceanprotocol.com',
|
||||
background_color: '141414', // dark background
|
||||
// TODO: figure out if also image URI needs base64 encoding
|
||||
// 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')}`
|
||||
image_data: imageData
|
||||
}
|
||||
|
||||
return newNft
|
||||
}
|
||||
|
||||
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 = {
|
||||
name: nftMetadata.name,
|
||||
symbol: nftMetadata.symbol,
|
||||
templateIndex: 1,
|
||||
// Gas estimation fails if we add our huge tokenURI
|
||||
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
|
||||
tokenURI: `data:application/json;base64,${encodedMetadata}`
|
||||
}
|
||||
|
||||
return nftCreateData
|
||||
|
@ -24,3 +24,9 @@ export function compareAsBN(balance: string, price: string): boolean {
|
||||
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 {
|
||||
downloadFileBrowser,
|
||||
FileMetadata,
|
||||
LoggerInstance,
|
||||
ProviderInstance
|
||||
} 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(
|
||||
files: FileMetadata[],
|
||||
providerUrl: string
|
||||
@ -46,3 +49,21 @@ export async function getFileUrlInfo(
|
||||
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_poolShares as PoolShare
|
||||
} 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'
|
||||
|
||||
export interface UserLiquidity {
|
||||
@ -71,6 +74,7 @@ const HighestLiquidityAssets = gql`
|
||||
symbol
|
||||
}
|
||||
baseTokenLiquidity
|
||||
datatokenLiquidity
|
||||
}
|
||||
}
|
||||
`
|
||||
@ -140,7 +144,11 @@ const UserTokenOrders = gql`
|
||||
orderDirection: desc
|
||||
where: { consumer: $user }
|
||||
) {
|
||||
consumer {
|
||||
id
|
||||
}
|
||||
datatoken {
|
||||
id
|
||||
address
|
||||
symbol
|
||||
}
|
||||
@ -154,14 +162,11 @@ const UserTokenOrders = gql`
|
||||
}
|
||||
`
|
||||
|
||||
// TODO: counting orders might be enough here to get sales for a user
|
||||
const UserSalesQuery = gql`
|
||||
query UserSalesQuery($userSalesId: ID) {
|
||||
users(where: { id: $userSalesId }) {
|
||||
query UserSalesQuery($user: String!) {
|
||||
users(where: { id: $user }) {
|
||||
id
|
||||
orders(first: 10000) {
|
||||
id
|
||||
}
|
||||
totalSales
|
||||
}
|
||||
}
|
||||
`
|
||||
@ -294,6 +299,7 @@ export async function getHighestLiquidityDatatokens(
|
||||
highestLiquidityAssets.sort(
|
||||
(a, b) => b.baseTokenLiquidity - a.baseTokenLiquidity
|
||||
)
|
||||
|
||||
for (let i = 0; i < highestLiquidityAssets.length; i++) {
|
||||
if (!highestLiquidityAssets[i]?.datatoken?.address) continue
|
||||
dtList.push(highestLiquidityAssets[i].datatoken.address)
|
||||
@ -376,9 +382,8 @@ export async function getUserTokenOrders(
|
||||
variables,
|
||||
chainIds
|
||||
)
|
||||
|
||||
for (let i = 0; i < tokenOrders?.length; i++) {
|
||||
tokenOrders[i].tokenOrders.forEach((tokenOrder: OrdersData) => {
|
||||
tokenOrders[i].orders.forEach((tokenOrder: OrdersData) => {
|
||||
data.push(tokenOrder)
|
||||
})
|
||||
}
|
||||
@ -393,7 +398,7 @@ export async function getUserSales(
|
||||
accountId: string,
|
||||
chainIds: number[]
|
||||
): Promise<number> {
|
||||
const variables = { userSalesId: accountId?.toLowerCase() }
|
||||
const variables = { user: accountId?.toLowerCase() }
|
||||
try {
|
||||
const userSales = await fetchDataForMultipleChains(
|
||||
UserSalesQuery,
|
||||
@ -403,7 +408,7 @@ export async function getUserSales(
|
||||
let salesSum = 0
|
||||
for (let i = 0; i < userSales.length; i++) {
|
||||
if (userSales[i].users.length > 0) {
|
||||
salesSum += userSales[i].users[0].nrSales
|
||||
salesSum += parseInt(userSales[i].users[0].totalSales)
|
||||
}
|
||||
}
|
||||
return salesSum
|
||||
@ -432,7 +437,7 @@ export async function getTopAssetsPublishers(
|
||||
if (publishersIndex === -1) {
|
||||
const publisher: AccountTeaserVM = {
|
||||
address: fetchedUsers.data.users[i].id,
|
||||
nrSales: fetchedUsers.data.users[i].orders.length
|
||||
nrSales: fetchedUsers.data.users[i].totalSales
|
||||
}
|
||||
publisherSales.push(publisher)
|
||||
} else {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { getNetworkDisplayName } from '@hooks/useNetworkMetadata'
|
||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||
import Web3 from 'web3'
|
||||
import { getOceanConfig } from './ocean'
|
||||
|
||||
export function accountTruncate(account: string): string {
|
||||
@ -8,6 +9,15 @@ export function accountTruncate(account: string): string {
|
||||
const truncated = account.replace(middle, '…')
|
||||
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(
|
||||
web3Provider: any,
|
||||
|
@ -10,6 +10,7 @@ import { useIsMounted } from '@hooks/useIsMounted'
|
||||
import { AssetExtended } from 'src/@types/AssetExtended'
|
||||
import { Asset } from '@oceanprotocol/lib'
|
||||
import { getAccessDetailsForAssets } from '@utils/accessDetailsAndPricing'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
@ -43,6 +44,7 @@ export default function AssetList({
|
||||
noPublisher
|
||||
}: AssetListProps): ReactElement {
|
||||
const { chainIds } = useUserPreferences()
|
||||
const { accountId } = useWeb3()
|
||||
const [assetsWithPrices, setAssetsWithPrices] = useState<AssetExtended[]>()
|
||||
const [loading, setLoading] = useState<boolean>(isLoading)
|
||||
const isMounted = useIsMounted()
|
||||
@ -51,14 +53,16 @@ export default function AssetList({
|
||||
if (!assets) return
|
||||
setAssetsWithPrices(assets as AssetExtended[])
|
||||
setLoading(false)
|
||||
|
||||
async function fetchPrices() {
|
||||
const assetsWithPrices = await getAccessDetailsForAssets(assets)
|
||||
const assetsWithPrices = await getAccessDetailsForAssets(
|
||||
assets,
|
||||
accountId || ''
|
||||
)
|
||||
if (!isMounted()) return
|
||||
setAssetsWithPrices([...assetsWithPrices])
|
||||
}
|
||||
fetchPrices()
|
||||
}, [assets, isMounted])
|
||||
}, [assets, isMounted, accountId])
|
||||
|
||||
// // This changes the page field inside the query
|
||||
function handlePageChange(selected: number) {
|
||||
|
@ -31,6 +31,8 @@ interface ButtonBuyProps {
|
||||
algorithmConsumableStatus?: number
|
||||
}
|
||||
|
||||
// TODO: we need to take a look at these messages
|
||||
|
||||
function getConsumeHelpText(
|
||||
dtBalance: string,
|
||||
dtSymbol: string,
|
||||
|
@ -1,7 +1,8 @@
|
||||
.datatoken {
|
||||
display: grid;
|
||||
gap: var(--spacer);
|
||||
grid-template-columns: 1fr 11fr;
|
||||
grid-template-columns: none;
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
margin-bottom: var(--spacer);
|
||||
align-items: center;
|
||||
}
|
||||
@ -20,9 +21,16 @@
|
||||
border: 1px solid var(--border-color);
|
||||
fill: var(--brand-violet);
|
||||
border-radius: 50%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.image svg {
|
||||
width: 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 {
|
||||
display: grid;
|
||||
gap: var(--spacer);
|
||||
grid-template-columns: 1fr 11fr;
|
||||
grid-template-columns: none;
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
margin-bottom: var(--spacer);
|
||||
align-items: center;
|
||||
}
|
||||
@ -19,16 +20,32 @@
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px solid var(--border-color);
|
||||
position: relative;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.refresh {
|
||||
.actions {
|
||||
position: absolute;
|
||||
right: calc(var(--spacer) / 4);
|
||||
bottom: calc(var(--spacer) / 4);
|
||||
left: 0;
|
||||
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 {
|
||||
fill: var(--brand-pink);
|
||||
width: var(--font-size-mini);
|
||||
height: var(--font-size-mini);
|
||||
transform: scale(0.9);
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
@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 Refresh from '@images/refresh.svg'
|
||||
import styles from './index.module.css'
|
||||
import Tooltip from '@shared/atoms/Tooltip'
|
||||
import TxFee from './TxFee'
|
||||
|
||||
export default function Nft(props: InputProps): ReactElement {
|
||||
const [field, meta, helpers] = useField(props.name)
|
||||
|
||||
const refreshNftMetadata = () => {
|
||||
const nftMetadata = generateNftMetadata()
|
||||
helpers.setValue({ ...nftMetadata })
|
||||
}
|
||||
|
||||
// Generate on first mount
|
||||
useEffect(() => {
|
||||
if (field.value?.name !== '') return
|
||||
|
||||
const nftOptions = generateNftMetadata()
|
||||
helpers.setValue({ ...nftOptions })
|
||||
refreshNftMetadata()
|
||||
}, [field.value?.name])
|
||||
|
||||
return (
|
||||
<div className={styles.nft}>
|
||||
<figure className={styles.image}>
|
||||
<img src={field?.value?.image_data} width="128" height="128" />
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
className={styles.refresh}
|
||||
title="Generate new image"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
const nftMetadata = generateNftMetadata()
|
||||
helpers.setValue({ ...nftMetadata })
|
||||
}}
|
||||
>
|
||||
<Refresh />
|
||||
</Button>
|
||||
<div className={styles.actions}>
|
||||
<Tooltip content={<TxFee nftMetadata={field.value} />} />
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
className={styles.refresh}
|
||||
title="Generate new image"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
refreshNftMetadata()
|
||||
}}
|
||||
>
|
||||
<Refresh />
|
||||
</Button>
|
||||
</div>
|
||||
</figure>
|
||||
|
||||
<div className={styles.token}>
|
||||
|
@ -96,7 +96,6 @@
|
||||
padding: 0;
|
||||
font-weight: var(--font-weight-bold);
|
||||
font-size: var(--font-size-small);
|
||||
color: var(--font-color-text);
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
|
@ -3,21 +3,24 @@ import styles from './index.module.css'
|
||||
import Loader from '../atoms/Loader'
|
||||
import Tooltip from '../atoms/Tooltip'
|
||||
import PriceUnit from './PriceUnit'
|
||||
import { AccessDetails, OrderPriceAndFees } from 'src/@types/Price'
|
||||
|
||||
export default function Price({
|
||||
accessDetails,
|
||||
orderPriceAndFees,
|
||||
className,
|
||||
small,
|
||||
conversion
|
||||
}: {
|
||||
accessDetails: AccessDetails
|
||||
orderPriceAndFees?: OrderPriceAndFees
|
||||
className?: string
|
||||
small?: boolean
|
||||
conversion?: boolean
|
||||
}): ReactElement {
|
||||
return accessDetails?.price || accessDetails?.type === 'free' ? (
|
||||
<PriceUnit
|
||||
price={`${accessDetails.price}`}
|
||||
price={`${orderPriceAndFees?.price || accessDetails?.price}`}
|
||||
symbol={accessDetails.baseToken?.symbol}
|
||||
className={className}
|
||||
small={small}
|
||||
|
@ -45,6 +45,13 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.text code {
|
||||
font-size: 0.8rem;
|
||||
color: inherit;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.action,
|
||||
button.action {
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
|
@ -2,6 +2,8 @@
|
||||
text-align: center;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding: calc(var(--spacer) / 2);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tab {
|
||||
@ -15,7 +17,7 @@
|
||||
background-color: var(--background-body);
|
||||
border: 1px solid var(--border-color);
|
||||
margin-right: -1px;
|
||||
min-width: 100px;
|
||||
min-width: 90px;
|
||||
}
|
||||
|
||||
.tab:first-child {
|
||||
@ -38,6 +40,10 @@
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.tab > div {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tabContent {
|
||||
padding: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { ReactElement, ReactNode } from 'react'
|
||||
import { Tab, Tabs as ReactTabs, TabList, TabPanel } from 'react-tabs'
|
||||
import InputElement from '@shared/FormInput/InputElement'
|
||||
import styles from './Tabs.module.css'
|
||||
|
||||
export interface TabsItem {
|
||||
@ -12,12 +13,14 @@ export default function Tabs({
|
||||
items,
|
||||
className,
|
||||
handleTabChange,
|
||||
defaultIndex
|
||||
defaultIndex,
|
||||
showRadio
|
||||
}: {
|
||||
items: TabsItem[]
|
||||
className?: string
|
||||
handleTabChange?: (tabName: string) => void
|
||||
defaultIndex?: number
|
||||
showRadio?: boolean
|
||||
}): ReactElement {
|
||||
return (
|
||||
<ReactTabs
|
||||
@ -25,14 +28,24 @@ export default function Tabs({
|
||||
defaultIndex={defaultIndex}
|
||||
>
|
||||
<TabList className={styles.tabList}>
|
||||
{items.map((item) => (
|
||||
{items.map((item, index) => (
|
||||
<Tab
|
||||
className={styles.tab}
|
||||
key={item.title}
|
||||
onClick={handleTabChange ? () => handleTabChange(item.title) : null}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
{item.title}
|
||||
{showRadio ? (
|
||||
<InputElement
|
||||
name={item.title}
|
||||
type="radio"
|
||||
checked={defaultIndex === index}
|
||||
options={[item.title]}
|
||||
readOnly
|
||||
/>
|
||||
) : (
|
||||
item.title
|
||||
)}
|
||||
</Tab>
|
||||
))}
|
||||
</TabList>
|
||||
|
@ -15,7 +15,9 @@ const Tag = ({ tag, noLinks }: { tag: string; noLinks?: boolean }) => {
|
||||
return noLinks ? (
|
||||
<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}>
|
||||
{tag}
|
||||
</a>
|
||||
|
@ -6,12 +6,13 @@ import AssetComputeList from '@shared/AssetList/AssetComputeList'
|
||||
import { useCancelToken } from '@hooks/useCancelToken'
|
||||
import { getServiceByName } from '@utils/ddo'
|
||||
import { Asset } from '@oceanprotocol/lib'
|
||||
import { AssetExtended } from 'src/@types/AssetExtended'
|
||||
|
||||
export default function AlgorithmDatasetsListForCompute({
|
||||
ddo,
|
||||
asset,
|
||||
algorithmDid
|
||||
}: {
|
||||
ddo: Asset
|
||||
asset: AssetExtended
|
||||
algorithmDid: string
|
||||
}): ReactElement {
|
||||
const [datasetsForCompute, setDatasetsForCompute] =
|
||||
@ -19,24 +20,24 @@ export default function AlgorithmDatasetsListForCompute({
|
||||
const newCancelToken = useCancelToken()
|
||||
|
||||
useEffect(() => {
|
||||
if (!ddo) return
|
||||
if (!asset) return
|
||||
|
||||
async function getDatasetsAllowedForCompute() {
|
||||
const isCompute = Boolean(getServiceByName(ddo, 'compute'))
|
||||
const isCompute = Boolean(getServiceByName(asset, 'compute'))
|
||||
const datasetComputeService = getServiceByName(
|
||||
ddo,
|
||||
asset,
|
||||
isCompute ? 'compute' : 'access'
|
||||
)
|
||||
const datasets = await getAlgorithmDatasetsForCompute(
|
||||
algorithmDid,
|
||||
datasetComputeService?.serviceEndpoint,
|
||||
ddo?.chainId,
|
||||
asset?.chainId,
|
||||
newCancelToken()
|
||||
)
|
||||
setDatasetsForCompute(datasets)
|
||||
}
|
||||
ddo.metadata.type === 'algorithm' && getDatasetsAllowedForCompute()
|
||||
}, [ddo?.metadata?.type])
|
||||
asset.metadata.type === 'algorithm' && getDatasetsAllowedForCompute()
|
||||
}, [asset?.metadata?.type])
|
||||
|
||||
return (
|
||||
<div className={styles.datasetsContainer}>
|
||||
|
@ -11,6 +11,7 @@ import { useWeb3 } from '@context/Web3'
|
||||
import { checkIfConsumable } from '@utils/ddo'
|
||||
import content from '../../../../../content/pages/startComputeDataset.json'
|
||||
import { Asset } from '@oceanprotocol/lib'
|
||||
import { AccessDetails } from 'src/@types/Price'
|
||||
|
||||
export default function FormStartCompute({
|
||||
algorithms,
|
||||
@ -104,7 +105,7 @@ export default function FormStartCompute({
|
||||
? 0
|
||||
: Number(algorithmConsumeDetails.price)
|
||||
|
||||
setTotalPrice(priceDataset + priceAlgo)
|
||||
setTotalPrice((priceDataset + priceAlgo).toString())
|
||||
}, [
|
||||
asset?.accessDetails,
|
||||
algorithmConsumeDetails,
|
||||
@ -143,7 +144,7 @@ export default function FormStartCompute({
|
||||
hasDatatokenSelectedComputeAsset={hasDatatokenSelectedComputeAsset}
|
||||
algorithmConsumeDetails={algorithmConsumeDetails}
|
||||
symbol={oceanSymbol}
|
||||
totalPrice={totalPrice}
|
||||
totalPrice={Number.parseFloat(totalPrice)}
|
||||
/>
|
||||
|
||||
<ButtonBuy
|
||||
|
@ -3,6 +3,7 @@ import { useAsset } from '@context/Asset'
|
||||
import PriceUnit from '@shared/Price/PriceUnit'
|
||||
import Tooltip from '@shared/atoms/Tooltip'
|
||||
import styles from './PriceOutput.module.css'
|
||||
import { AccessDetails } from 'src/@types/Price'
|
||||
|
||||
interface PriceOutputProps {
|
||||
totalPrice: number
|
||||
@ -74,14 +75,14 @@ export default function PriceOutput({
|
||||
<Row
|
||||
hasPreviousOrder={hasPreviousOrder}
|
||||
hasDatatoken={hasDatatoken}
|
||||
price={asset?.accessDetails?.price}
|
||||
price={Number.parseFloat(asset?.accessDetails?.price)}
|
||||
timeout={assetTimeout}
|
||||
symbol={symbol}
|
||||
/>
|
||||
<Row
|
||||
hasPreviousOrder={hasPreviousOrderSelectedComputeAsset}
|
||||
hasDatatoken={hasDatatokenSelectedComputeAsset}
|
||||
price={algorithmConsumeDetails?.price}
|
||||
price={Number.parseFloat(algorithmConsumeDetails?.price)}
|
||||
timeout={selectedComputeAssetTimeout}
|
||||
symbol={symbol}
|
||||
sign="+"
|
||||
|
@ -26,7 +26,6 @@ import FileIcon from '@shared/FileIcon'
|
||||
import Alert from '@shared/atoms/Alert'
|
||||
import { useSiteMetadata } from '@hooks/useSiteMetadata'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import { usePricing } from '@hooks/usePricing'
|
||||
import {
|
||||
generateBaseQuery,
|
||||
getFilterTerm,
|
||||
@ -59,6 +58,7 @@ import { Decimal } from 'decimal.js'
|
||||
import { TransactionReceipt } from 'web3-core'
|
||||
import { useAbortController } from '@hooks/useAbortController'
|
||||
import { getAccessDetails } from '@utils/accessDetailsAndPricing'
|
||||
import { AccessDetails } from 'src/@types/Price'
|
||||
|
||||
export default function Compute({
|
||||
asset,
|
||||
@ -78,8 +78,7 @@ export default function Compute({
|
||||
consumableFeedback?: string
|
||||
}): ReactElement {
|
||||
const { appConfig } = useSiteMetadata()
|
||||
const { accountId, web3 } = useWeb3()
|
||||
const { pricingError, pricingStepText } = usePricing()
|
||||
const { accountId } = useWeb3()
|
||||
const [isJobStarting, setIsJobStarting] = useState(false)
|
||||
const [error, setError] = useState<string>()
|
||||
const newAbortController = useAbortController()
|
||||
@ -156,13 +155,13 @@ export default function Compute({
|
||||
useEffect(() => {
|
||||
if (!algorithmConsumeDetails) return
|
||||
|
||||
setIsAlgoConsumablePrice(algorithmConsumeDetails.isConsumable)
|
||||
setIsAlgoConsumablePrice(algorithmConsumeDetails.isPurchasable)
|
||||
}, [algorithmConsumeDetails])
|
||||
|
||||
useEffect(() => {
|
||||
if (!accessDetails) return
|
||||
|
||||
setIsConsumablePrice(accessDetails.isConsumable)
|
||||
setIsConsumablePrice(accessDetails.isPurchasable)
|
||||
}, [accessDetails])
|
||||
|
||||
// useEffect(() => {
|
||||
@ -217,10 +216,10 @@ export default function Compute({
|
||||
|
||||
// Output errors in toast UI
|
||||
useEffect(() => {
|
||||
const newError = error || pricingError
|
||||
const newError = error
|
||||
if (!newError) return
|
||||
toast.error(newError)
|
||||
}, [error, pricingError])
|
||||
}, [error])
|
||||
|
||||
async function startJob(algorithmId: string): Promise<string> {
|
||||
try {
|
||||
@ -646,7 +645,7 @@ export default function Compute({
|
||||
/>
|
||||
<AlgorithmDatasetsListForCompute
|
||||
algorithmDid={asset.id}
|
||||
ddo={asset}
|
||||
asset={asset}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
@ -680,7 +679,8 @@ export default function Compute({
|
||||
selectedComputeAssetLowPoolLiquidity={!isAlgoConsumablePrice}
|
||||
selectedComputeAssetType="algorithm"
|
||||
selectedComputeAssetTimeout={algorithmTimeout}
|
||||
stepText={pricingStepText || 'Starting Compute Job...'}
|
||||
// lazy comment when removing pricingStepText
|
||||
stepText={'pricingStepText' || 'Starting Compute Job...'}
|
||||
algorithmConsumeDetails={algorithmConsumeDetails}
|
||||
isConsumable={isConsumable}
|
||||
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(
|
||||
accountId,
|
||||
poolAddress,
|
||||
tokenInAddress,
|
||||
amount,
|
||||
minPoolAmountOut
|
||||
)
|
||||
|
@ -64,7 +64,6 @@ export default function Remove({
|
||||
const result = await poolInstance.exitswapPoolAmountIn(
|
||||
accountId,
|
||||
poolAddress,
|
||||
tokenOutAddress,
|
||||
amountPoolShares,
|
||||
minOceanAmount
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { ReactElement, useState, useEffect } from 'react'
|
||||
import Compute from './Compute'
|
||||
import Consume from './Consume'
|
||||
import Consume from './Download'
|
||||
import { FileMetadata, LoggerInstance, Datatoken } from '@oceanprotocol/lib'
|
||||
import Tabs, { TabsItem } from '@shared/atoms/Tabs'
|
||||
import { compareAsBN } from '@utils/numbers'
|
||||
@ -141,13 +141,11 @@ export default function AssetActions({
|
||||
/>
|
||||
) : (
|
||||
<Consume
|
||||
ddo={asset}
|
||||
accessDetails={asset?.accessDetails}
|
||||
asset={asset}
|
||||
dtBalance={dtBalance}
|
||||
isBalanceSufficient={isBalanceSufficient}
|
||||
file={fileMetadata}
|
||||
fileIsLoading={fileIsLoading}
|
||||
isConsumable={isConsumable}
|
||||
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 {
|
||||
margin-bottom: calc(var(--spacer) / 3);
|
||||
padding-bottom: calc(var(--spacer) / 3);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.network {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.info {
|
||||
width: 0.85rem;
|
||||
.statsList {
|
||||
composes: statsList from './index.module.css';
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.statsList,
|
||||
@ -30,13 +14,13 @@
|
||||
padding: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.statsList {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.note {
|
||||
margin-bottom: 0;
|
||||
padding-top: 0;
|
||||
font-size: var(--font-size-mini);
|
||||
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 React, { ReactElement, useEffect, useState, useCallback } from 'react'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import Table from '@shared/atoms/Table'
|
||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||
import Price from '@shared/Price'
|
||||
import Tooltip from '@shared/atoms/Tooltip'
|
||||
import AssetTitle from '@shared/AssetList/AssetListTitle'
|
||||
import { retrieveDDOListByDIDs } from '@utils/aquarius'
|
||||
import { CancelToken } from 'axios'
|
||||
import { useSiteMetadata } from '@hooks/useSiteMetadata'
|
||||
import { useCancelToken } from '@hooks/useCancelToken'
|
||||
import { AssetExtended } from 'src/@types/AssetExtended'
|
||||
@ -53,26 +52,6 @@ export default function Bookmarks(): ReactElement {
|
||||
const { chainIds } = useUserPreferences()
|
||||
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(() => {
|
||||
if (!appConfig?.metadataCacheUri || bookmarks === []) return
|
||||
|
||||
@ -85,21 +64,23 @@ export default function Bookmarks(): ReactElement {
|
||||
setIsLoading(true)
|
||||
|
||||
try {
|
||||
const resultPinned = await getAssetsBookmarked(
|
||||
const result = await retrieveDDOListByDIDs(
|
||||
bookmarks,
|
||||
chainIds,
|
||||
newCancelToken()
|
||||
)
|
||||
if (!result?.length) return
|
||||
|
||||
const pinnedAssets: AssetExtended[] = await getAccessDetailsForAssets(
|
||||
resultPinned,
|
||||
result,
|
||||
accountId
|
||||
)
|
||||
setPinned(pinnedAssets)
|
||||
} catch (error) {
|
||||
LoggerInstance.error(error.message)
|
||||
LoggerInstance.error(`Bookmarks error:`, error.message)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
setIsLoading(false)
|
||||
}
|
||||
init()
|
||||
}, [
|
||||
@ -107,7 +88,6 @@ export default function Bookmarks(): ReactElement {
|
||||
bookmarks,
|
||||
chainIds,
|
||||
accountId,
|
||||
getAssetsBookmarked,
|
||||
newCancelToken
|
||||
])
|
||||
|
||||
|
@ -24,10 +24,9 @@ async function getQueryHighest(
|
||||
esPaginationOptions: {
|
||||
size: dtList.length > 0 ? dtList.length : 1
|
||||
},
|
||||
filters: [getFilterTerm('dataToken', dtList)]
|
||||
filters: [getFilterTerm('services.datatokenAddress', dtList)]
|
||||
} as BaseQueryParams
|
||||
const queryHighest = generateBaseQuery(baseQueryParams)
|
||||
|
||||
return [queryHighest, dtList]
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,6 @@ async function getPoolSharesLiquidity(
|
||||
poolShares: PoolShare[]
|
||||
): Promise<number> {
|
||||
let totalLiquidity = 0
|
||||
|
||||
for (const poolShare of poolShares) {
|
||||
const poolLiquidity = calculateUserLiquidity(poolShare)
|
||||
totalLiquidity += poolLiquidity
|
||||
|
@ -4,37 +4,36 @@ import { FormPublishData } from '../_types'
|
||||
import { useFormikContext } from 'formik'
|
||||
import AssetContent from 'src/components/Asset/AssetContent'
|
||||
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 {
|
||||
const [asset, setAsset] = useState<Asset>()
|
||||
const [accessDetails, setAccessDetails] = useState<AccessDetails>()
|
||||
const [asset, setAsset] = useState<AssetExtended>()
|
||||
const { values } = useFormikContext<FormPublishData>()
|
||||
|
||||
useEffect(() => {
|
||||
async function makeDdo() {
|
||||
const asset = await transformPublishFormToDdo(values)
|
||||
setAsset(asset as Asset)
|
||||
|
||||
const asset = (await transformPublishFormToDdo(values)) as AssetExtended
|
||||
// dummy BestPrice to trigger certain AssetActions
|
||||
const accessDetails: AccessDetails = {
|
||||
asset.accessDetails = {
|
||||
type: values.pricing.type,
|
||||
addressOrId: '0x...',
|
||||
addressOrId: ZERO_ADDRESS,
|
||||
price: values.pricing.price,
|
||||
baseToken: {
|
||||
address: '0x..',
|
||||
name: '',
|
||||
symbol: ''
|
||||
address: ZERO_ADDRESS,
|
||||
name: 'OCEAN',
|
||||
symbol: 'OCEAN'
|
||||
},
|
||||
datatoken: {
|
||||
address: '0x..',
|
||||
address: ZERO_ADDRESS,
|
||||
name: '',
|
||||
symbol: ''
|
||||
},
|
||||
owned: false,
|
||||
isPurchasable: true,
|
||||
isOwned: false,
|
||||
validOrderTx: ''
|
||||
}
|
||||
setAccessDetails(accessDetails)
|
||||
setAsset(asset)
|
||||
}
|
||||
makeDdo()
|
||||
}, [values])
|
||||
@ -44,7 +43,7 @@ export default function Preview(): ReactElement {
|
||||
<h2 className={styles.previewTitle}>Preview</h2>
|
||||
|
||||
<h3 className={styles.assetTitle}>{values.metadata.name}</h3>
|
||||
<AssetContent asset={asset} />
|
||||
{asset && <AssetContent asset={asset} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
.token {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: flex-start;
|
||||
margin-bottom: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
@ -60,8 +60,7 @@
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
max-width: 12rem;
|
||||
margin: auto;
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
.max {
|
||||
@ -73,3 +72,13 @@
|
||||
.weight strong {
|
||||
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 {
|
||||
display: grid;
|
||||
gap: var(--spacer);
|
||||
grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
|
||||
padding: var(--spacer);
|
||||
grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
|
||||
padding: var(--spacer) 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fees > div {
|
||||
margin-bottom: 0;
|
||||
justify-self: center;
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.fees label {
|
||||
@ -18,3 +18,10 @@
|
||||
.fees input {
|
||||
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 Price from './Price'
|
||||
import styles from './Dynamic.module.css'
|
||||
|
||||
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 (
|
||||
<>
|
||||
<FormHelp>{content.info}</FormHelp>
|
||||
<h4 className={styles.title}>Price</h4>
|
||||
<Price />
|
||||
<Price content={content} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -62,7 +62,11 @@
|
||||
}
|
||||
|
||||
.free {
|
||||
text-align: center;
|
||||
margin: calc(var(--spacer) / 2) 0;
|
||||
text-align: left;
|
||||
margin: calc(var(--spacer) / 2);
|
||||
font-size: var(--font-size-base);
|
||||
}
|
||||
|
||||
.free [class*='FormInput_field'] {
|
||||
margin: 0;
|
||||
}
|
||||
|
@ -1,16 +1,19 @@
|
||||
import Conversion from '@shared/Price/Conversion'
|
||||
import { useField, useFormikContext } from 'formik'
|
||||
import { Field, useField, useFormikContext } from 'formik'
|
||||
import React, { ReactElement } from 'react'
|
||||
import Input from '@shared/FormInput'
|
||||
import Error from './Error'
|
||||
import PriceUnit from '@shared/Price/PriceUnit'
|
||||
import styles from './Price.module.css'
|
||||
import { FormPublishData } from '../_types'
|
||||
import { getFieldContent } from '../_utils'
|
||||
|
||||
export default function Price({
|
||||
firstPrice
|
||||
firstPrice,
|
||||
content
|
||||
}: {
|
||||
firstPrice?: string
|
||||
content?: any
|
||||
}): ReactElement {
|
||||
const [field, meta] = useField('pricing.price')
|
||||
|
||||
@ -20,7 +23,13 @@ export default function Price({
|
||||
return (
|
||||
<div className={styles.price}>
|
||||
{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}>
|
||||
|
@ -2,6 +2,10 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pricing ul > li[class*='Tabs_tab'] {
|
||||
padding: calc(var(--spacer) / 4) var(--spacer);
|
||||
}
|
||||
|
||||
.pricing [class*='Tabs_tabContent'] {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
|
@ -23,7 +23,9 @@ export default function PricingFields(): ReactElement {
|
||||
function handleTabChange(tabName: string) {
|
||||
const type = tabName.toLowerCase()
|
||||
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
|
||||
@ -42,7 +44,14 @@ export default function PricingFields(): ReactElement {
|
||||
: 0
|
||||
|
||||
setFieldValue('pricing.amountDataToken', amountDataToken)
|
||||
}, [price, amountOcean, weightOnOcean, weightOnDataToken, type])
|
||||
}, [
|
||||
price,
|
||||
amountOcean,
|
||||
weightOnOcean,
|
||||
weightOnDataToken,
|
||||
type,
|
||||
setFieldValue
|
||||
])
|
||||
|
||||
const tabs = [
|
||||
appConfig.allowFixedPricing === 'true'
|
||||
@ -71,6 +80,7 @@ export default function PricingFields(): ReactElement {
|
||||
handleTabChange={handleTabChange}
|
||||
defaultIndex={type === 'dynamic' ? 1 : type === 'free' ? 2 : 0}
|
||||
className={styles.pricing}
|
||||
showRadio
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -58,7 +58,15 @@
|
||||
.title {
|
||||
font-size: var(--font-size-large);
|
||||
display: inline-block;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.txs {
|
||||
display: block;
|
||||
margin-left: 1.5rem;
|
||||
margin-bottom: calc(var(--spacer) / 4);
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.description {
|
||||
@ -78,3 +86,18 @@
|
||||
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) => (
|
||||
<li key={index} className={styles[value.status]}>
|
||||
<h3 className={styles.title}>
|
||||
{value.name}
|
||||
<h3 className={styles.title}>{value.name}</h3>
|
||||
<div className={styles.txs}>
|
||||
{value.txCount > 0 && (
|
||||
<TransactionCount
|
||||
txCount={value.txCount}
|
||||
@ -18,7 +18,7 @@ export function Feedback(): ReactElement {
|
||||
txHash={value.txHash}
|
||||
/>
|
||||
)}
|
||||
</h3>
|
||||
</div>
|
||||
<p className={styles.description}>{value.description}</p>
|
||||
{value.errorMessage && (
|
||||
<span className={styles.errorMessage}>{value.errorMessage}</span>
|
||||
|
@ -1,7 +1,6 @@
|
||||
.txHash {
|
||||
display: inline-block;
|
||||
margin-left: calc(var(--spacer) / 2);
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
margin: calc(var(--spacer) / 4) calc(var(--spacer) / 4);
|
||||
font-size: var(--font-size-small);
|
||||
font-family: var(--font-family-base);
|
||||
font-weight: var(--font-weight-base);
|
||||
|
@ -96,7 +96,8 @@ export const initialValues: FormPublishData = {
|
||||
amountOcean: 50,
|
||||
weightOnOcean: '5', // 50% on OCEAN
|
||||
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 { NftMetadata } from '@utils/nft'
|
||||
import { ReactElement } from 'react'
|
||||
import { PriceOptions } from 'src/@types/Price'
|
||||
|
||||
interface FileMetadata {
|
||||
url: string
|
||||
|
@ -18,6 +18,8 @@ import {
|
||||
import { mapTimeoutStringToSeconds } from '@utils/ddo'
|
||||
import { generateNftCreateData } from '@utils/nft'
|
||||
import { getEncryptedFiles } from '@utils/provider'
|
||||
import { getSiteMetadata } from '@utils/siteConfig'
|
||||
import Decimal from 'decimal.js'
|
||||
import slugify from 'slugify'
|
||||
import Web3 from 'web3'
|
||||
import {
|
||||
@ -191,7 +193,6 @@ export async function transformPublishFormToDdo(
|
||||
export async function createTokensAndPricing(
|
||||
values: FormPublishData,
|
||||
accountId: string,
|
||||
marketFeeAddress: string,
|
||||
config: Config,
|
||||
nftFactory: NftFactory,
|
||||
web3: Web3
|
||||
@ -199,19 +200,17 @@ export async function createTokensAndPricing(
|
||||
const nftCreateData: NftCreateData = generateNftCreateData(
|
||||
values.metadata.nft
|
||||
)
|
||||
|
||||
const { appConfig } = getSiteMetadata()
|
||||
LoggerInstance.log('[publish] Creating NFT with metadata', nftCreateData)
|
||||
|
||||
// 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 = {
|
||||
templateIndex: values.pricing.type === 'dynamic' ? 1 : 2,
|
||||
minter: accountId,
|
||||
feeManager: accountId,
|
||||
mpFeeAddress: marketFeeAddress,
|
||||
mpFeeAddress: appConfig.marketFeeAddress,
|
||||
feeToken: config.oceanTokenAddress,
|
||||
feeAmount: `0`,
|
||||
feeAmount: appConfig.publisherMarketOrderFee,
|
||||
cap: '1000',
|
||||
name: values.services[0].dataTokenOptions.name,
|
||||
symbol: values.services[0].dataTokenOptions.symbol
|
||||
@ -226,21 +225,22 @@ export async function createTokensAndPricing(
|
||||
case 'dynamic': {
|
||||
// no vesting in market by default, maybe at a later time , vestingAmount and vestedBlocks are hardcoded
|
||||
// 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 = {
|
||||
ssContract: config.sideStakingAddress,
|
||||
baseTokenAddress: config.oceanTokenAddress,
|
||||
baseTokenSender: config.erc721FactoryAddress,
|
||||
publisherAddress: accountId,
|
||||
marketFeeCollector: marketFeeAddress,
|
||||
marketFeeCollector: appConfig.marketFeeAddress,
|
||||
poolTemplateAddress: config.poolTemplateAddress,
|
||||
rate: values.pricing.price.toString(),
|
||||
rate: new Decimal(1).div(values.pricing.price).toString(),
|
||||
baseTokenDecimals: 18,
|
||||
vestingAmount: '0',
|
||||
vestedBlocks: 2726000,
|
||||
initialBaseTokenLiquidity: values.pricing.amountOcean.toString(),
|
||||
swapFeeLiquidityProvider: 1e15,
|
||||
swapFeeMarketRunner: 1e15
|
||||
swapFeeLiquidityProvider: (values.pricing.swapFee / 100).toString(),
|
||||
swapFeeMarketRunner: appConfig.publisherMarketPoolSwapFee
|
||||
}
|
||||
|
||||
LoggerInstance.log(
|
||||
@ -249,18 +249,17 @@ export async function createTokensAndPricing(
|
||||
)
|
||||
|
||||
// the spender in this case is the erc721Factory because we are delegating
|
||||
const pool = new Pool(web3)
|
||||
const txApprove = await approve(
|
||||
web3,
|
||||
accountId,
|
||||
config.oceanTokenAddress,
|
||||
config.erc721FactoryAddress,
|
||||
'200',
|
||||
values.pricing.amountOcean.toString(),
|
||||
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,
|
||||
nftCreateData,
|
||||
ercParams,
|
||||
@ -279,11 +278,11 @@ export async function createTokensAndPricing(
|
||||
fixedRateAddress: config.fixedRateExchangeAddress,
|
||||
baseTokenAddress: config.oceanTokenAddress,
|
||||
owner: accountId,
|
||||
marketFeeCollector: marketFeeAddress,
|
||||
marketFeeCollector: appConfig.marketFeeAddress,
|
||||
baseTokenDecimals: 18,
|
||||
datatokenDecimals: 18,
|
||||
fixedRate: values.pricing.price.toString(),
|
||||
marketFee: 1e15,
|
||||
marketFee: appConfig.publisherMarketFixedSwapFee,
|
||||
withMint: true
|
||||
}
|
||||
|
||||
@ -292,7 +291,7 @@ export async function createTokensAndPricing(
|
||||
freParams
|
||||
)
|
||||
|
||||
const result = await nftFactory.createNftErcWithFixedRate(
|
||||
const result = await nftFactory.createNftErc20WithFixedRate(
|
||||
accountId,
|
||||
nftCreateData,
|
||||
ercParams,
|
||||
@ -324,7 +323,7 @@ export async function createTokensAndPricing(
|
||||
dispenserParams
|
||||
)
|
||||
|
||||
const result = await nftFactory.createNftErcWithDispenser(
|
||||
const result = await nftFactory.createNftErc20WithDispenser(
|
||||
accountId,
|
||||
nftCreateData,
|
||||
ercParams,
|
||||
|
@ -21,7 +21,6 @@ import {
|
||||
LoggerInstance,
|
||||
DDO
|
||||
} from '@oceanprotocol/lib'
|
||||
import { useSiteMetadata } from '@hooks/useSiteMetadata'
|
||||
import { getOceanConfig } from '@utils/ocean'
|
||||
import { validationSchema } from './_validation'
|
||||
import { useAbortController } from '@hooks/useAbortController'
|
||||
@ -38,7 +37,6 @@ export default function PublishPage({
|
||||
const { accountId, web3, chainId } = useWeb3()
|
||||
const { isInPurgatory, purgatoryData } = useAccountPurgatory(accountId)
|
||||
const scrollToRef = useRef()
|
||||
const { appConfig } = useSiteMetadata()
|
||||
const nftFactory = useNftFactory()
|
||||
const newAbortController = useAbortController()
|
||||
|
||||
@ -75,7 +73,6 @@ export default function PublishPage({
|
||||
await createTokensAndPricing(
|
||||
values,
|
||||
accountId,
|
||||
appConfig.marketFeeAddress,
|
||||
config,
|
||||
nftFactory,
|
||||
web3
|
||||
|
@ -61,8 +61,9 @@ export default function SearchPage({
|
||||
setTotalResults(undefined)
|
||||
const queryResult = await getResults(parsed, chainIds, newCancelToken())
|
||||
setQueryResult(queryResult)
|
||||
setTotalResults(queryResult.totalResults)
|
||||
setTotalPagesNumber(queryResult.totalPages)
|
||||
|
||||
setTotalResults(queryResult?.totalResults || 0)
|
||||
setTotalPagesNumber(queryResult?.totalPages || 0)
|
||||
setLoading(false)
|
||||
},
|
||||
[newCancelToken, setTotalPagesNumber, setTotalResults]
|
||||
|
@ -123,7 +123,7 @@ export function getSearchQuery(
|
||||
|
||||
const filters: FilterTerm[] = []
|
||||
accessType !== undefined &&
|
||||
filters.push(getFilterTerm('nft.type', accessType))
|
||||
filters.push(getFilterTerm('services.type', accessType))
|
||||
serviceType !== undefined &&
|
||||
filters.push(getFilterTerm('metadata.type', serviceType))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user