mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Restore order (#1068)
* minor refactors * minor refactors * fixes * buy dt * consumePrice + estimation * various fixes * cleanup * fix build * fix ssh issue * feedback * build fix * ssh fix * remove console.log * suggested fixes * other fixes * switch to decimal * more fixes * more fixes * fix * some fee refactors * more fee refactoring * lib update, fre rename Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro> * minor refactors Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro> * build fixes Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro> * update + more refactoring * calc price * fix build * restore accountId in effect * fix order * fix build and update lib * fix order index * fix comments * pool fix * remove console.log * fix order fixed rate exchange * fixed free order and messaging * add comment * minor type fix * more type fixes
This commit is contained in:
parent
4576151e0c
commit
8d1782a800
@ -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"
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
17
package-lock.json
generated
17
package-lock.json
generated
@ -13,7 +13,7 @@
|
||||
"@coingecko/cryptoformat": "^0.4.4",
|
||||
"@loadable/component": "^5.15.2",
|
||||
"@oceanprotocol/art": "^3.2.0",
|
||||
"@oceanprotocol/lib": "^1.0.0-next.14",
|
||||
"@oceanprotocol/lib": "^1.0.0-next.17",
|
||||
"@oceanprotocol/typographies": "^0.1.0",
|
||||
"@portis/web3": "^4.0.6",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
@ -86,10 +86,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"
|
||||
},
|
||||
"engines": {
|
||||
@ -3453,9 +3456,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@oceanprotocol/lib": {
|
||||
"version": "1.0.0-next.14",
|
||||
"resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-1.0.0-next.14.tgz",
|
||||
"integrity": "sha512-qz41c6LlvfELvz9oTJncIsZ/cZudEzq2gjCgI4E3iQfo71qB7jzwPd6oqiqIYHmRHl8J/F6QXe9fQsp81R4TIg==",
|
||||
"version": "1.0.0-next.17",
|
||||
"resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-1.0.0-next.17.tgz",
|
||||
"integrity": "sha512-qZQ2nWe/qNN/2M9YZEacwHSMyStwFoN6gXKDCw17G9E8QLwl3v5z54WjAqLlGOpNnGKMIvkrj4gUpUAMtjLSxw==",
|
||||
"dependencies": {
|
||||
"@oceanprotocol/contracts": "1.0.0-alpha.18",
|
||||
"bignumber.js": "^9.0.2",
|
||||
@ -30132,9 +30135,9 @@
|
||||
}
|
||||
},
|
||||
"@oceanprotocol/lib": {
|
||||
"version": "1.0.0-next.14",
|
||||
"resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-1.0.0-next.14.tgz",
|
||||
"integrity": "sha512-qz41c6LlvfELvz9oTJncIsZ/cZudEzq2gjCgI4E3iQfo71qB7jzwPd6oqiqIYHmRHl8J/F6QXe9fQsp81R4TIg==",
|
||||
"version": "1.0.0-next.17",
|
||||
"resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-1.0.0-next.17.tgz",
|
||||
"integrity": "sha512-qZQ2nWe/qNN/2M9YZEacwHSMyStwFoN6gXKDCw17G9E8QLwl3v5z54WjAqLlGOpNnGKMIvkrj4gUpUAMtjLSxw==",
|
||||
"requires": {
|
||||
"@oceanprotocol/contracts": "1.0.0-alpha.18",
|
||||
"bignumber.js": "^9.0.2",
|
||||
|
@ -21,7 +21,7 @@
|
||||
"@coingecko/cryptoformat": "^0.4.4",
|
||||
"@loadable/component": "^5.15.2",
|
||||
"@oceanprotocol/art": "^3.2.0",
|
||||
"@oceanprotocol/lib": "^1.0.0-next.14",
|
||||
"@oceanprotocol/lib": "^1.0.0-next.17",
|
||||
"@oceanprotocol/typographies": "^0.1.0",
|
||||
"@portis/web3": "^4.0.6",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
@ -94,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": {
|
||||
|
@ -95,7 +95,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,
|
||||
|
@ -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)
|
||||
@ -193,10 +194,10 @@ 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 || 0)
|
||||
@ -237,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,
|
||||
|
@ -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
|
||||
|
50
src/@types/Price.d.ts
vendored
50
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 {
|
||||
|
@ -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) {
|
||||
@ -132,25 +137,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.price = '0'
|
||||
accessDetails.isPurchasable = dispenser.active
|
||||
accessDetails.datatoken = {
|
||||
address: dispenser.token.id,
|
||||
name: dispenser.token.name,
|
||||
@ -164,21 +177,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.id
|
||||
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 +201,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 +221,81 @@ 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
|
||||
break
|
||||
}
|
||||
case 'fixed': {
|
||||
const fixed = await getFixedBuyPrice(accessDetails, asset.chainId)
|
||||
orderPriceAndFee.price = fixed.baseTokenAmount
|
||||
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 +303,7 @@ export async function getAccessDetails(
|
||||
TokenPriceQuery,
|
||||
{
|
||||
datatokenId: datatokenAddress.toLowerCase(),
|
||||
account: account.toLowerCase()
|
||||
account: account?.toLowerCase()
|
||||
},
|
||||
queryContext
|
||||
)
|
||||
@ -247,7 +321,6 @@ 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()
|
||||
@ -264,20 +337,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
|
||||
})
|
||||
}
|
||||
|
@ -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 }
|
||||
|
96
src/@utils/order.ts
Normal file
96
src/@utils/order.ts
Normal file
@ -0,0 +1,96 @@
|
||||
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,
|
||||
_providerFees: initializeData.providerFee
|
||||
} 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 } 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<PriceAndEstimation>}
|
||||
*/
|
||||
export async function calculateBuyPrice(
|
||||
accessDetails: AccessDetails,
|
||||
chainId?: number,
|
||||
web3?: Web3
|
||||
): Promise<string> {
|
||||
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,
|
||||
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).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
|
||||
}
|
@ -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,
|
||||
|
@ -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}
|
||||
|
@ -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}>
|
||||
|
@ -10,6 +10,7 @@ import { useAsset } from '@context/Asset'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import content from '../../../../../content/pages/startComputeDataset.json'
|
||||
import { Asset } from '@oceanprotocol/lib'
|
||||
import { AccessDetails } from 'src/@types/Price'
|
||||
|
||||
export default function FormStartCompute({
|
||||
algorithms,
|
||||
@ -106,7 +107,7 @@ export default function FormStartCompute({
|
||||
? 0
|
||||
: Number(algorithmConsumeDetails.price)
|
||||
|
||||
setTotalPrice(priceDataset + priceAlgo)
|
||||
setTotalPrice((priceDataset + priceAlgo).toString())
|
||||
}, [
|
||||
asset?.accessDetails,
|
||||
algorithmConsumeDetails,
|
||||
@ -145,7 +146,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="+"
|
||||
|
@ -14,7 +14,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,
|
||||
@ -37,6 +36,7 @@ import { useCancelToken } from '@hooks/useCancelToken'
|
||||
import { useIsMounted } from '@hooks/useIsMounted'
|
||||
import { SortTermOptions } from '../../../../@types/aquarius/SearchQuery'
|
||||
import { getAccessDetails } from '@utils/accessDetailsAndPricing'
|
||||
import { AccessDetails } from 'src/@types/Price'
|
||||
|
||||
export default function Compute({
|
||||
ddo,
|
||||
@ -57,7 +57,6 @@ export default function Compute({
|
||||
}): ReactElement {
|
||||
const { appConfig } = useSiteMetadata()
|
||||
const { accountId } = useWeb3()
|
||||
const { buyDT, pricingError, pricingStepText } = usePricing()
|
||||
const [isJobStarting, setIsJobStarting] = useState(false)
|
||||
const [error, setError] = useState<string>()
|
||||
|
||||
@ -181,13 +180,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(() => {
|
||||
@ -234,10 +233,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) {
|
||||
// try {
|
||||
@ -400,7 +399,7 @@ export default function Compute({
|
||||
text="This algorithm has been set to private by the publisher and can't be downloaded. You can run it against any allowed data sets though!"
|
||||
state="info"
|
||||
/>
|
||||
<AlgorithmDatasetsListForCompute algorithmDid={ddo.id} ddo={ddo} />
|
||||
<AlgorithmDatasetsListForCompute algorithmDid={ddo.id} asset={ddo} />
|
||||
</>
|
||||
) : (
|
||||
<Formik
|
||||
@ -433,7 +432,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>
|
||||
)
|
||||
}
|
@ -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}
|
||||
/>
|
||||
)
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -42,7 +42,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'
|
||||
|
@ -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: '0.1',
|
||||
swapFeeMarketRunner: '0'
|
||||
swapFeeLiquidityProvider: (values.pricing.swapFee / 100).toString(),
|
||||
swapFeeMarketRunner: appConfig.publisherMarketPoolSwapFee
|
||||
}
|
||||
|
||||
LoggerInstance.log(
|
||||
@ -249,16 +249,15 @@ 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.createNftErc20WithPool(
|
||||
accountId,
|
||||
@ -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: '0',
|
||||
marketFee: appConfig.publisherMarketFixedSwapFee,
|
||||
withMint: true
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user