1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-12-02 05:57:29 +01:00

Restore compute functionality (#1069)

* add balance check and check is consumable

* add isOrderable and other helpers

* finish start compute job

* removed unused methods

* add more comments

* add pool logic for order

* move asset selection to compute helper

* small fix

* fixed get algo list

* refactor start compute job and more fixes

* update order params

* use compute env and compute consumer address

* fix prices

* fix algorithms selection list on allowAllPublisher case

* fix edit compute settings

* update compute resources valid until logic

* fixes and cleanups

* wip compute jobs

* fix compute timeout value

* fixed compute jobs logic

* fix algo selection list name

* fixed compute jobs from profile loading

* update start compute flow messages

* update set algo access details

* update compute message logic

* added logs

* update package lock

* remove logs

* fix edit compute checksums for files and container

* Fix compute dataset algorithm list (#1194)

* fix query

Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro>

* remove comment

Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro>

* Fix previous order tx (#1197)

* rename nft update query

Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro>

* fix previous order

Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro>

* fix build

* handle order price, NaN and default 0

* optional value for all fee, prevent breaking when no value

* fix aquarius call and added logs

* update provider compute status call

* remove percentage fee from price sum, depends smart contract calculation (#1249)

Co-authored-by: Soon Huat <soon_huat.phan@daimler.com>

* fix display of compute datasets with free price

* removed to lowerCase on eth address

* fix compute jobs section and your jobs

* bumo ocean lib to 1.0.0-next.32

* c2d show price with fee, exclude provider fee

* wip get results

* include loading when calculating data + algo price, tooltip show order price

* update get compute url and use oceanjs helper for download

* update computeStatus signature to fix build and CI

* added logs

* refactor setting price and fees for assets

* update compute details and compute results UI and style

* update flex value

* update download buttons style

* update download buttons text

* bump ocean lib version and lint fixes

* get provier uri for compute results based on job input did

* use zero adress for price and fees order

* some fixes

* Add reuse order in start compute flow  (#1352)

* wip add reuse order logic

* add reuse order in start job

* added missing check if no jobs found

* update lib

Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro>

* fix lint

Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro>

Co-authored-by: mihaisc <mihai.scarlat@smartcontrol.ro>

* fix fixed rate

* fix build

* fix your compute jobs section when asset network not selected

* disable edit compute settings for algorithms

* fix compute jobs infinite loading when no jobs found

* fix compute form

* show token symbol for free assets also on compute price output

* removed swp file

* some decimal fixes

* partial fix for asset with pool fees, algo not working yet

* more decimal fixes

* fix algo with pool price and fees fetching

* fix selecting algorithms when on different network

* fix compute jobs table auto refresh and details  modal closing

* wip compute initialize

* order fixes

* fix lint

* fix conditions and cleanups

* fix compute status text display

* init prices and fees after starting a compute job

* start/order button tweaks

* kick in loader earlier

* update compute status feedback messages

* fixed initial price

* compute jobs refetch and reuse order

* remove logs

* removed logs and added some explanations

* use compute env max duration value in seconds

* error handling on intializeCompute and order

* removed console logs and added one new check

* use optional on initialized provider check

* remove toast from provider helper

* fix compute env issue on start order

* disable job selection during actions execution

* temporary fix publish algo with custom docker image

* fix provider fee display

* remove unnecessary condition

* fix alignment based button on action type (#1491)

* fix alignment based on action type

* moving to CSS modules

* send providerFeeAmount as string

* remove cast on providerFeeAmount

* removed some logs and added few comments

* update price output tooltip and total price logic

* set providerFee amount only when avaialable

* bump oceanlib to 1.1.2

* replace FIleMetadata to fix build

* used approveWei for approving provider fees

* fix free algo price selection and display

* fix provider fee load at first algo selection

* update compute help text

* fix provider fee approve for free assets

* cleanup

* remove commented out code
* remove unused state
* removed unused imports
* typos in comments, variables, props

* more typos

* shorten getAccessDetailsFromTokenPrice() a bit

* state & hooks access reordering

* Update src/@utils/ddo.ts

remove metadata from service type

Co-authored-by: Matthias Kretschmann <m@kretschmann.io>

* effect dependency fixes

* state renaming

* effect dependency fixes

* compute jobs profile visual fixes

* effect dependency fixes

* more comments removal

* add accountId as a dependency in effect

* move isOwner to asset provider

* refactor handleComputeOrder for less complexity and more useful error reporting

* more proper error throwing

* provider fee statement tweak

* more obvious edit action

* empty array for `publisherTrustedAlgorithms` & `publisherTrustedAlgorithmPublishers` by default

* ref #1538
* ref #1539

* don t use initial tx values as valid order use subgraph value

* fix algo list fetching

* closes #1537
* addresses #1538

* fix disable compute button if algo is consumable

* move isOwner check to single effect

* Correctly display trusted algorithms in compute asset (#1541)

* fix allowed algo

* fix trusted algo filter

Co-authored-by: mihaisc <mihai.scarlat@smartcontrol.ro>
Co-authored-by: Soon Huat <soon_huat.phan@daimler.com>
Co-authored-by: Soon Huat <soonhuat.phan@hotmail.com>
Co-authored-by: Enzo Vezzaro <enzo-vezzaro@live.it>
Co-authored-by: Matthias Kretschmann <m@kretschmann.io>
Co-authored-by: mihaisc <mihai@oceanprotocol.com>
This commit is contained in:
Bogdan Fazakas 2022-06-23 18:53:05 +03:00 committed by GitHub
parent d884a9529d
commit 5387b9a3dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1297 additions and 778 deletions

View File

@ -27,6 +27,7 @@ export interface AssetProviderValue {
error?: string error?: string
isAssetNetwork: boolean isAssetNetwork: boolean
isV3Asset: boolean isV3Asset: boolean
isOwner: boolean
oceanConfig: Config oceanConfig: Config
loading: boolean loading: boolean
fetchAsset: (token?: CancelToken) => Promise<void> fetchAsset: (token?: CancelToken) => Promise<void>
@ -49,6 +50,7 @@ function AssetProvider({
const [asset, setAsset] = useState<AssetExtended>() const [asset, setAsset] = useState<AssetExtended>()
const [title, setTitle] = useState<string>() const [title, setTitle] = useState<string>()
const [owner, setOwner] = useState<string>() const [owner, setOwner] = useState<string>()
const [isOwner, setIsOwner] = useState<boolean>()
const [error, setError] = useState<string>() const [error, setError] = useState<string>()
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [isAssetNetwork, setIsAssetNetwork] = useState<boolean>() const [isAssetNetwork, setIsAssetNetwork] = useState<boolean>()
@ -140,6 +142,16 @@ function AssetProvider({
setIsAssetNetwork(isAssetNetwork) setIsAssetNetwork(isAssetNetwork)
}, [chainId, asset?.chainId]) }, [chainId, asset?.chainId])
// -----------------------------------
// Asset owner check against wallet user
// -----------------------------------
useEffect(() => {
if (!accountId || !owner) return
const isOwner = accountId?.toLowerCase() === owner.toLowerCase()
setIsOwner(isOwner)
}, [accountId, owner])
// ----------------------------------- // -----------------------------------
// Load ocean config based on asset network // Load ocean config based on asset network
// ----------------------------------- // -----------------------------------
@ -172,6 +184,7 @@ function AssetProvider({
fetchAsset, fetchAsset,
isAssetNetwork, isAssetNetwork,
isV3Asset, isV3Asset,
isOwner,
oceanConfig oceanConfig
} as AssetProviderValue } as AssetProviderValue
} }

View File

@ -1,5 +1,5 @@
import { ComputeJob } from '@oceanprotocol/lib' import { ComputeJob } from '@oceanprotocol/lib'
import { OrdersData_tokenOrders_datatokenId as OrdersDatatoken } from './apollo/OrdersData' import { OrdersData_orders_datatoken as OrdersDatatoken } from '../@types/subgraph/OrdersData'
// declaring into global scope to be able to use this as // declaring into global scope to be able to use this as
// ambiant types despite the above imports // ambiant types despite the above imports
@ -22,10 +22,10 @@ declare global {
interface TokenOrder { interface TokenOrder {
id: string id: string
serviceId: number serviceIndex: number
datatokenId: OrdersDatatoken datatoken: OrdersDatatoken
tx: any tx: any
timestamp: number createdTimestamp: number
} }
interface ComputeResults { interface ComputeResults {

View File

@ -8,13 +8,19 @@ import {
TokensPriceQuery, TokensPriceQuery,
TokensPriceQuery_tokens as TokensPrice TokensPriceQuery_tokens as TokensPrice
} from '../@types/subgraph/TokensPriceQuery' } from '../@types/subgraph/TokensPriceQuery'
import { Asset, LoggerInstance, ProviderInstance } from '@oceanprotocol/lib' import {
Asset,
LoggerInstance,
ProviderFees,
ProviderInstance
} from '@oceanprotocol/lib'
import { AssetExtended } from 'src/@types/AssetExtended' import { AssetExtended } from 'src/@types/AssetExtended'
import { calcInGivenOut } from './pool' import { calcInGivenOut } from './pool'
import { getFixedBuyPrice } from './fixedRateExchange' import { getFixedBuyPrice } from './fixedRateExchange'
import { AccessDetails, OrderPriceAndFees } from 'src/@types/Price' import { AccessDetails, OrderPriceAndFees } from 'src/@types/Price'
import Decimal from 'decimal.js' import Decimal from 'decimal.js'
import { consumeMarketOrderFee } from '../../app.config' import { consumeMarketOrderFee } from '../../app.config'
import Web3 from 'web3'
const tokensPriceQuery = gql` const tokensPriceQuery = gql`
query TokensPriceQuery($datatokenIds: [ID!], $account: String) { query TokensPriceQuery($datatokenIds: [ID!], $account: String) {
@ -26,13 +32,20 @@ const tokensPriceQuery = gql`
publishMarketFeeToken publishMarketFeeToken
publishMarketFeeAmount publishMarketFeeAmount
orders( orders(
where: { consumer: $account } where: { payer: $account }
orderBy: createdTimestamp orderBy: createdTimestamp
orderDirection: desc orderDirection: desc
) { ) {
tx tx
serviceIndex serviceIndex
createdTimestamp createdTimestamp
reuses(orderBy: createdTimestamp, orderDirection: desc) {
id
caller
createdTimestamp
tx
block
}
} }
dispensers { dispensers {
id id
@ -91,13 +104,20 @@ const tokenPriceQuery = gql`
publishMarketFeeToken publishMarketFeeToken
publishMarketFeeAmount publishMarketFeeAmount
orders( orders(
where: { consumer: $account } where: { payer: $account }
orderBy: createdTimestamp orderBy: createdTimestamp
orderDirection: desc orderDirection: desc
) { ) {
tx tx
serviceIndex serviceIndex
createdTimestamp createdTimestamp
reuses(orderBy: createdTimestamp, orderDirection: desc) {
id
caller
createdTimestamp
tx
block
}
} }
dispensers { dispensers {
id id
@ -152,19 +172,22 @@ function getAccessDetailsFromTokenPrice(
timeout?: number timeout?: number
): AccessDetails { ): AccessDetails {
const accessDetails = {} as AccessDetails const accessDetails = {} as AccessDetails
if (tokenPrice && tokenPrice.orders && tokenPrice.orders.length > 0) {
if (tokenPrice?.orders?.length > 0) {
const order = tokenPrice.orders[0] const order = tokenPrice.orders[0]
const reusedOrder = order?.reuses?.length > 0 ? order.reuses[0] : null
// asset is owned if there is an order and asset has timeout 0 (forever) or if the condition is valid // asset is owned if there is an order and asset has timeout 0 (forever) or if the condition is valid
accessDetails.isOwned = accessDetails.isOwned =
timeout === 0 || Date.now() / 1000 - order.createdTimestamp < timeout timeout === 0 || Date.now() / 1000 - order?.createdTimestamp < timeout
accessDetails.validOrderTx = order.tx // the last valid order should be the last reuse order tx id if there is one
accessDetails.validOrderTx = reusedOrder?.tx || order?.tx
} }
// TODO: fetch order fee from sub query // TODO: fetch order fee from sub query
accessDetails.publisherMarketOrderFee = tokenPrice.publishMarketFeeAmount accessDetails.publisherMarketOrderFee = tokenPrice?.publishMarketFeeAmount
// free is always the best price // free is always the best price
if (tokenPrice.dispensers && tokenPrice.dispensers.length > 0) { if (tokenPrice?.dispensers?.length > 0) {
const dispenser = tokenPrice.dispensers[0] const dispenser = tokenPrice.dispensers[0]
accessDetails.type = 'free' accessDetails.type = 'free'
accessDetails.addressOrId = dispenser.token.id accessDetails.addressOrId = dispenser.token.id
@ -179,10 +202,7 @@ function getAccessDetailsFromTokenPrice(
} }
// checking for fixed price // checking for fixed price
if ( if (tokenPrice?.fixedRateExchanges?.length > 0) {
tokenPrice.fixedRateExchanges &&
tokenPrice.fixedRateExchanges.length > 0
) {
const fixed = tokenPrice.fixedRateExchanges[0] const fixed = tokenPrice.fixedRateExchanges[0]
accessDetails.type = 'fixed' accessDetails.type = 'fixed'
accessDetails.addressOrId = fixed.exchangeId accessDetails.addressOrId = fixed.exchangeId
@ -203,7 +223,7 @@ function getAccessDetailsFromTokenPrice(
} }
// checking for pools // checking for pools
if (tokenPrice.pools && tokenPrice.pools.length > 0) { if (tokenPrice?.pools?.length > 0) {
const pool = tokenPrice.pools[0] const pool = tokenPrice.pools[0]
accessDetails.type = 'dynamic' accessDetails.type = 'dynamic'
accessDetails.addressOrId = pool.id accessDetails.addressOrId = pool.id
@ -227,14 +247,15 @@ function getAccessDetailsFromTokenPrice(
} }
/** /**
* This will be used to get price including feed before ordering * This will be used to get price including fees before ordering
* @param {AssetExtended} asset * @param {AssetExtended} asset
* @return {Promise<OrdePriceAndFee>} * @return {Promise<OrdePriceAndFee>}
*/ */
export async function getOrderPriceAndFees( export async function getOrderPriceAndFees(
asset: AssetExtended, asset: AssetExtended,
accountId: string, accountId?: string,
paramsForPool: CalcInGivenOutParams paramsForPool?: CalcInGivenOutParams,
providerFees?: ProviderFees
): Promise<OrderPriceAndFees> { ): Promise<OrderPriceAndFees> {
const orderPriceAndFee = { const orderPriceAndFee = {
price: '0', price: '0',
@ -252,14 +273,17 @@ export async function getOrderPriceAndFees(
} as OrderPriceAndFees } as OrderPriceAndFees
// fetch provider fee // fetch provider fee
const initializeData = await ProviderInstance.initialize(
asset?.id, const initializeData =
asset.services[0].id, !providerFees &&
0, (await ProviderInstance.initialize(
accountId, asset?.id,
asset?.services[0].serviceEndpoint asset?.services[0].id,
) 0,
orderPriceAndFee.providerFee = initializeData.providerFee accountId,
asset?.services[0].serviceEndpoint
))
orderPriceAndFee.providerFee = providerFees || initializeData.providerFee
// fetch price and swap fees // fetch price and swap fees
switch (asset?.accessDetails?.type) { switch (asset?.accessDetails?.type) {
@ -286,10 +310,9 @@ export async function getOrderPriceAndFees(
} }
// calculate full price, we assume that all the values are in ocean, otherwise this will be incorrect // calculate full price, we assume that all the values are in ocean, otherwise this will be incorrect
orderPriceAndFee.price = new Decimal(orderPriceAndFee.price) orderPriceAndFee.price = new Decimal(+orderPriceAndFee.price || 0)
.add(new Decimal(orderPriceAndFee.consumeMarketOrderFee)) .add(new Decimal(+orderPriceAndFee?.consumeMarketOrderFee || 0))
.add(new Decimal(orderPriceAndFee.publisherMarketOrderFee)) .add(new Decimal(+orderPriceAndFee?.publisherMarketOrderFee || 0))
.add(new Decimal(orderPriceAndFee.providerFee.providerFeeAmount))
.toString() .toString()
return orderPriceAndFee return orderPriceAndFee
} }

View File

@ -11,6 +11,12 @@ import { transformAssetToAssetSelection } from './assetConvertor'
export const MAXIMUM_NUMBER_OF_PAGES_WITH_RESULTS = 476 export const MAXIMUM_NUMBER_OF_PAGES_WITH_RESULTS = 476
export function escapeEsReservedCharacters(value: string): string {
// eslint-disable-next-line no-useless-escape
const pattern = /([\!\*\+\-\=\<\>\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g
return value.replace(pattern, '\\$1')
}
/** /**
* @param filterField the name of the actual field from the ddo schema e.g. 'id','service.attributes.main.type' * @param filterField the name of the actual field from the ddo schema e.g. 'id','service.attributes.main.type'
* @param value the value of the filter * @param value the value of the filter
@ -174,7 +180,7 @@ export async function getAssetsFromDidList(
didList: string[], didList: string[],
chainIds: number[], chainIds: number[],
cancelToken: CancelToken cancelToken: CancelToken
): Promise<any> { ): Promise<PagedAssets> {
try { try {
if (!(didList.length > 0)) return if (!(didList.length > 0)) return
@ -249,12 +255,15 @@ export async function getAlgorithmDatasetsForCompute(
): Promise<AssetSelectionAsset[]> { ): Promise<AssetSelectionAsset[]> {
const baseQueryParams = { const baseQueryParams = {
chainIds: [datasetChainId], chainIds: [datasetChainId],
filters: [ nestedQuery: {
getFilterTerm( must: {
'service.compite.publisherTrustedAlgorithms.did', match: {
algorithmId 'services.compute.publisherTrustedAlgorithms.did': {
) query: escapeEsReservedCharacters(algorithmId)
], }
}
}
},
sortOptions: { sortOptions: {
sortBy: SortTermOptions.Created, sortBy: SortTermOptions.Created,
sortDirection: SortDirectionOptions.Descending sortDirection: SortDirectionOptions.Descending

View File

@ -18,7 +18,7 @@ export async function transformAssetToAssetSelection(
const algoComputeService = getServiceByName(asset, 'compute') const algoComputeService = getServiceByName(asset, 'compute')
if ( if (
asset?.accessDetails.price && asset?.accessDetails?.price &&
algoComputeService?.serviceEndpoint === datasetProviderEndpoint algoComputeService?.serviceEndpoint === datasetProviderEndpoint
) { ) {
let selected = false let selected = false
@ -29,7 +29,7 @@ export async function transformAssetToAssetSelection(
}) })
const algorithmAsset: AssetSelectionAsset = { const algorithmAsset: AssetSelectionAsset = {
did: asset.id, did: asset.id,
name: asset.datatokens[0].name, name: asset.metadata.name,
price: asset.accessDetails.price, price: asset.accessDetails.price,
checked: selected, checked: selected,
symbol: asset.datatokens[0].symbol symbol: asset.datatokens[0].symbol

View File

@ -1,19 +1,15 @@
// import {
// ServiceComputePrivacy,
// publisherTrustedAlgorithm as PublisherTrustedAlgorithm,
// Service,
// LoggerInstance,
// Provider,
// Config,
// Ocean,
// Account
// } from '@oceanprotocol/lib'
// import { ComputeJob } from '@oceanprotocol/lib/dist/node/ocean/interfaces/Compute'
import { import {
Asset, Asset,
ServiceComputeOptions, ServiceComputeOptions,
PublisherTrustedAlgorithm, PublisherTrustedAlgorithm,
getHash getHash,
LoggerInstance,
ComputeAlgorithm,
DDO,
Service,
ProviderInstance,
ComputeEnvironment,
ComputeJob
} from '@oceanprotocol/lib' } from '@oceanprotocol/lib'
import { CancelToken } from 'axios' import { CancelToken } from 'axios'
import { gql } from 'urql' import { gql } from 'urql'
@ -24,6 +20,11 @@ import {
retrieveDDOListByDIDs retrieveDDOListByDIDs
} from './aquarius' } from './aquarius'
import { fetchDataForMultipleChains } from './subgraph' import { fetchDataForMultipleChains } from './subgraph'
import { getServiceById, getServiceByName } from './ddo'
import { SortTermOptions } from 'src/@types/aquarius/SearchQuery'
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
import { transformAssetToAssetSelection } from './assetConvertor'
import { AssetExtended } from 'src/@types/AssetExtended'
const getComputeOrders = gql` const getComputeOrders = gql`
query ComputeOrders($user: String!) { query ComputeOrders($user: String!) {
@ -72,201 +73,256 @@ async function getAssetMetadata(
const baseQueryparams = { const baseQueryparams = {
chainIds, chainIds,
filters: [ filters: [
getFilterTerm('dataToken', queryDtList), getFilterTerm('services.datatokenAddress', queryDtList),
getFilterTerm('service.type', 'compute'), getFilterTerm('services.type', 'compute'),
getFilterTerm('service.attributes.main.type', 'dataset') getFilterTerm('metadata.type', 'dataset')
], ],
ignorePurgatory: true ignorePurgatory: true
} as BaseQueryParams } as BaseQueryParams
const query = generateBaseQuery(baseQueryparams) const query = generateBaseQuery(baseQueryparams)
const result = await queryMetadata(query, cancelToken) const result = await queryMetadata(query, cancelToken)
return result?.results
return result.results
} }
function getServiceEndpoints(data: TokenOrder[], assets: Asset[]): string[] { export async function isOrderable(
// const serviceEndpoints: string[] = [] asset: Asset | DDO,
serviceId: string,
algorithm: ComputeAlgorithm,
algorithmDDO: Asset | DDO
): Promise<boolean> {
const datasetService: Service = getServiceById(asset, serviceId)
if (!datasetService) return false
// for (let i = 0; i < data.length; i++) { if (datasetService.type === 'compute') {
// try { if (algorithm.meta) {
// const did = web3.utils // check if raw algo is allowed
// .toChecksumAddress(data[i].datatokenId.address) if (datasetService.compute.allowRawAlgorithm) return true
// .replace('0x', 'did:op:') LoggerInstance.error('ERROR: This service does not allow raw algorithm')
// const ddo = assets.filter((x) => x.id === did)[0] return false
// if (ddo === undefined) continue }
if (algorithm.documentId) {
const algoService: Service = getServiceById(
algorithmDDO,
algorithm.serviceId
)
if (algoService && algoService.type === 'compute') {
if (algoService.serviceEndpoint !== datasetService.serviceEndpoint) {
this.logger.error(
'ERROR: Both assets with compute service are not served by the same provider'
)
return false
}
}
}
}
return true
}
// const service = ddo.services.filter( export function getValidUntilTime(
// (x: Service) => x.index === data[i].serviceId computeEnvMaxJobDuration: number,
// )[0] datasetTimeout?: number,
algorithmTimeout?: number
) {
const inputValues = []
computeEnvMaxJobDuration && inputValues.push(computeEnvMaxJobDuration)
datasetTimeout && inputValues.push(datasetTimeout)
algorithmTimeout && inputValues.push(algorithmTimeout)
// if (!service || service.type !== 'compute') continue const minValue = Math.min(...inputValues)
// const { providerEndpoint } = service const mytime = new Date()
mytime.setMinutes(mytime.getMinutes() + Math.floor(minValue / 60))
return Math.floor(mytime.getTime() / 1000)
}
// const wasProviderQueried = export async function getComputeEnviroment(
// serviceEndpoints?.filter((x) => x === providerEndpoint).length > 0 asset: Asset
): Promise<ComputeEnvironment> {
if (asset?.services[0]?.type !== 'compute') return null
try {
const computeEnvs = await ProviderInstance.getComputeEnvironments(
asset.services[0].serviceEndpoint
)
if (!computeEnvs[0]) return null
return computeEnvs[0]
} catch (e) {
LoggerInstance.error('[compute] Fetch compute enviroment: ', e.message)
}
}
// if (wasProviderQueried) continue export function getQueryString(
// serviceEndpoints.push(providerEndpoint) trustedAlgorithmList: PublisherTrustedAlgorithm[],
// } catch (err) { trustedPublishersList: string[],
// LoggerInstance.error(err.message) chainId?: number
// } ): SearchQuery {
const algorithmDidList = trustedAlgorithmList?.map((x) => x.did)
const baseParams = {
chainIds: [chainId],
sort: { sortBy: SortTermOptions.Created },
filters: [getFilterTerm('metadata.type', 'algorithm')]
} as BaseQueryParams
algorithmDidList?.length > 0 &&
baseParams.filters.push(getFilterTerm('_id', algorithmDidList))
trustedPublishersList?.length > 0 &&
baseParams.filters.push(getFilterTerm('nft.owner', trustedPublishersList))
const query = generateBaseQuery(baseParams)
return query
}
export async function getAlgorithmsForAsset(
asset: Asset,
token: CancelToken
): Promise<Asset[]> {
const computeService: Service = getServiceByName(asset, 'compute')
if (
!computeService.compute ||
(computeService.compute.publisherTrustedAlgorithms?.length === 0 &&
computeService.compute.publisherTrustedAlgorithmPublishers?.length === 0)
) {
return []
}
const gueryResults = await queryMetadata(
getQueryString(
computeService.compute.publisherTrustedAlgorithms,
computeService.compute.publisherTrustedAlgorithmPublishers,
asset.chainId
),
token
)
const algorithms: Asset[] = gueryResults?.results
return algorithms
}
export async function getAlgorithmAssetSelectionList(
asset: Asset,
algorithms: Asset[]
): Promise<AssetSelectionAsset[]> {
const computeService: Service = getServiceByName(asset, 'compute')
let algorithmSelectionList: AssetSelectionAsset[]
if (!computeService.compute) {
algorithmSelectionList = []
} else {
algorithmSelectionList = await transformAssetToAssetSelection(
computeService?.serviceEndpoint,
algorithms,
[]
)
}
return algorithmSelectionList
}
async function getJobs(
providerUrls: string[],
accountId: string,
assets: Asset[]
): Promise<ComputeJobMetaData[]> {
const computeJobs: ComputeJobMetaData[] = []
// commented loop since we decide how to filter jobs
// for await (const providerUrl of providerUrls) {
try {
const providerComputeJobs = (await ProviderInstance.computeStatus(
providerUrls[0],
accountId
)) as ComputeJob[]
if (providerComputeJobs) {
providerComputeJobs.sort((a, b) => {
if (a.dateCreated > b.dateCreated) {
return -1
}
if (a.dateCreated < b.dateCreated) {
return 1
}
return 0
})
providerComputeJobs.forEach((job) => {
const did = job.inputDID[0]
const asset = assets.filter((x) => x.id === did)[0]
if (asset) {
const compJob: ComputeJobMetaData = {
...job,
assetName: asset.metadata.name,
assetDtSymbol: asset.datatokens[0].symbol,
networkId: asset.chainId
}
computeJobs.push(compJob)
}
})
}
} catch (err) {
LoggerInstance.error(err.message)
}
// } // }
return computeJobs
// return serviceEndpoints
return ['dummy']
} }
export async function getComputeJobs(
chainIds: number[],
accountId: string,
asset?: AssetExtended,
cancelToken?: CancelToken
): Promise<ComputeResults> {
if (!accountId) return
const assetDTAddress = asset?.datatokens[0]?.address
const computeResult: ComputeResults = {
computeJobs: [],
isLoaded: false
}
const variables = assetDTAddress
? {
user: accountId.toLowerCase(),
datatokenAddress: assetDTAddress.toLowerCase()
}
: {
user: accountId.toLowerCase()
}
// async function getProviders( const results = await fetchDataForMultipleChains(
// serviceEndpoints: string[], assetDTAddress ? getComputeOrdersByDatatokenAddress : getComputeOrders,
// config: Config, variables,
// ocean: Ocean assetDTAddress ? [asset?.chainId] : chainIds
// ): Promise<Provider[]> { )
// const providers: Provider[] = []
// try { let tokenOrders: TokenOrder[] = []
// for (let i = 0; i < serviceEndpoints?.length; i++) { results.map((result) =>
// const instanceConfig = { result.orders.forEach((tokenOrder: TokenOrder) =>
// config, tokenOrders.push(tokenOrder)
// web3: config.web3Provider, )
// logger: LoggerInstance, )
// ocean if (tokenOrders.length === 0) {
// } computeResult.isLoaded = true
// const provider = await Provider.getInstance(instanceConfig) return computeResult
// await provider.setBaseUrl(serviceEndpoints[i]) }
// const hasSameCompute =
// providers.filter((x) => x.computeAddress === provider.computeAddress)
// .length > 0
// if (!hasSameCompute) providers.push(provider)
// }
// } catch (err) {
// LoggerInstance.error(err.message)
// }
// return providers tokenOrders = tokenOrders.sort(
// } (a, b) => b.createdTimestamp - a.createdTimestamp
)
// async function getJobs( const datatokenAddressList = tokenOrders.map(
// providers: Provider[], (tokenOrder: TokenOrder) => tokenOrder.datatoken.address
// account: Account, )
// assets: Asset[] if (!datatokenAddressList) return
// ): Promise<ComputeJobMetaData[]> {
// const computeJobs: ComputeJobMetaData[] = []
// for (let i = 0; i < providers.length; i++) { const assets = await getAssetMetadata(
// try { datatokenAddressList,
// const providerComputeJobs = (await providers[i].computeStatus( cancelToken,
// '', chainIds
// account, )
// undefined,
// undefined,
// false
// )) as ComputeJob[]
// // means the provider uri is not good, so we ignore it and move on const providerUrls: string[] = []
// if (!providerComputeJobs) continue assets.forEach((asset: Asset) =>
// providerComputeJobs.sort((a, b) => { providerUrls.push(asset.services[0].serviceEndpoint)
// if (a.dateCreated > b.dateCreated) { )
// return -1
// }
// if (a.dateCreated < b.dateCreated) {
// return 1
// }
// return 0
// })
// for (let j = 0; j < providerComputeJobs?.length; j++) { computeResult.computeJobs = await getJobs(providerUrls, accountId, assets)
// const job = providerComputeJobs[j] computeResult.isLoaded = true
// const did = job.inputDID[0]
// const ddo = assets.filter((x) => x.id === did)[0]
// if (!ddo) continue return computeResult
}
// const compJob: ComputeJobMetaData = {
// ...job,
// assetName: ddo.metadata.name,
// assetDtSymbol: ddo.dataTokenInfo.symbol,
// networkId: ddo.chainId
// }
// computeJobs.push(compJob)
// }
// } catch (err) {
// LoggerInstance.error(err.message)
// }
// }
// return computeJobs
// }
// function getDtList(data: TokenOrder[]): string[] {
// const dtList = []
// for (let i = 0; i < data.length; i++) {
// dtList.push(data[i].datatokenId.address)
// }
// return dtList
// }
// export async function getComputeJobs(
// chainIds: number[],
// account: Account,
// ddo?: Asset,
// token?: CancelToken
// ): Promise<ComputeResults> {
// const assetDTAddress = ddo?.dataTokenInfo?.address
// let computeResult: ComputeResults = {
// computeJobs: [],
// isLoaded: false
// }
// let isLoading = true
// const variables = assetDTAddress
// ? {
// user: account?.getId().toLowerCase(),
// datatokenAddress: assetDTAddress.toLowerCase()
// }
// : {
// user: account?.getId().toLowerCase()
// }
// const result = await fetchDataForMultipleChains(
// assetDTAddress ? getComputeOrdersByDatatokenAddress : getComputeOrders,
// variables,
// assetDTAddress ? [ddo?.chainId] : chainIds
// )
// let data: TokenOrder[] = []
// for (let i = 0; i < result.length; i++) {
// if (!result[i]?.tokenOrders || result[i].tokenOrders.length === 0) continue
// result[i]?.tokenOrders.forEach((tokenOrder: TokenOrder) => {
// data.push(tokenOrder)
// })
// }
// if (!ocean || !account || !data) return
// if (data.length === 0) {
// return computeResult
// }
// data = data.sort((a, b) => b.timestamp - a.timestamp)
// const queryDtList = getDtList(data)
// if (!queryDtList) return
// const assets = await getAssetMetadata(queryDtList, token, chainIds)
// const serviceEndpoints = getServiceEndpoints(data, assets)
// const providers: Provider[] = await getProviders(
// serviceEndpoints,
// config,
// ocean
// )
// const computeJobs = await getJobs(providers, account, assets)
// isLoading = false
// computeResult = {
// computeJobs: computeJobs,
// isLoaded: isLoading
// }
// return computeResult
// }
export async function createTrustedAlgorithmList( export async function createTrustedAlgorithmList(
selectedAlgorithms: string[], // list of DIDs, selectedAlgorithms: string[], // list of DIDs,
@ -282,10 +338,16 @@ export async function createTrustedAlgorithmList(
) )
for (const selectedAlgorithm of selectedAssets) { for (const selectedAlgorithm of selectedAssets) {
const sanitizedAlgorithmContainer = {
entrypoint: selectedAlgorithm.metadata.algorithm.container.entrypoint,
image: selectedAlgorithm.metadata.algorithm.container.image,
tag: selectedAlgorithm.metadata.algorithm.container.tag,
checksum: selectedAlgorithm.metadata.algorithm.container.checksum
}
const trustedAlgorithm = { const trustedAlgorithm = {
did: selectedAlgorithm.id, did: selectedAlgorithm.id,
containerSectionChecksum: getHash( containerSectionChecksum: getHash(
JSON.stringify(selectedAlgorithm.metadata.algorithm.container) JSON.stringify(sanitizedAlgorithmContainer)
), ),
filesChecksum: getHash(selectedAlgorithm.services[0].files) filesChecksum: getHash(selectedAlgorithm.services[0].files)
} }
@ -315,3 +377,31 @@ export async function transformComputeFormToServiceComputeOptions(
return privacy return privacy
} }
export async function checkComputeResourcesValidity(
asset: Asset,
accountId: string,
computeEnvMaxJobDuration: number,
datasetTimeout?: number,
algorithmTimeout?: number,
cancelToken?: CancelToken
): Promise<boolean> {
const jobs = await getComputeJobs(
[asset?.chainId],
accountId,
asset,
cancelToken
)
if (jobs.computeJobs.length <= 0) return false
const inputValues = []
computeEnvMaxJobDuration && inputValues.push(computeEnvMaxJobDuration * 60)
datasetTimeout && inputValues.push(datasetTimeout)
algorithmTimeout && inputValues.push(algorithmTimeout)
const minValue = Math.min(...inputValues)
const jobStartDate = new Date(
parseInt(jobs.computeJobs[0].dateCreated) * 1000
)
jobStartDate.setMinutes(jobStartDate.getMinutes() + Math.floor(minValue / 60))
const currentTime = new Date().getTime() / 1000
return Math.floor(jobStartDate.getTime() / 1000) > currentTime
}

View File

@ -10,6 +10,13 @@ export function getServiceByName(
return service return service
} }
export function getServiceById(ddo: Asset | DDO, serviceId: string): Service {
if (!ddo) return
const service = ddo.services.find((s) => s.id === serviceId)
return service
}
export function mapTimeoutStringToSeconds(timeout: string): number { export function mapTimeoutStringToSeconds(timeout: string): number {
switch (timeout) { switch (timeout) {
case 'Forever': case 'Forever':

View File

@ -11,9 +11,16 @@ export function getOrderFeedback(
} }
} }
// TODO: customize for compute export function getComputeFeedback(
export const computeFeedback: { [key in number]: string } = { baseTokenSymbol?: string,
0: 'Ordering asset...', datatokenSymbol?: string,
1: 'Transfering datatoken.', assetType?: string
2: 'Access granted. Starting job...' ): { [key in number]: string } {
return {
0: `Setting price and fees for ${assetType}`,
1: `Approving ${datatokenSymbol} and ordering ${assetType} `,
2: `Approving ${baseTokenSymbol} and ordering ${assetType}`,
3: `Ordering ${assetType}`,
4: 'Generating signature. Starting compute job ...'
}
} }

View File

@ -2,6 +2,7 @@ import { FixedRateExchange, PriceAndFees } from '@oceanprotocol/lib'
import { AccessDetails } from 'src/@types/Price' import { AccessDetails } from 'src/@types/Price'
import Web3 from 'web3' import Web3 from 'web3'
import { getOceanConfig } from './ocean' import { getOceanConfig } from './ocean'
import { consumeMarketPoolSwapFee } from '../../app.config'
import { getDummyWeb3 } from './web3' import { getDummyWeb3 } from './web3'
/** /**
@ -28,7 +29,8 @@ export async function getFixedBuyPrice(
const fixed = new FixedRateExchange(web3, config.fixedRateExchangeAddress) const fixed = new FixedRateExchange(web3, config.fixedRateExchangeAddress)
const estimatedPrice = await fixed.calcBaseInGivenOutDT( const estimatedPrice = await fixed.calcBaseInGivenOutDT(
accessDetails.addressOrId, accessDetails.addressOrId,
'1' '1',
consumeMarketPoolSwapFee
) )
return estimatedPrice return estimatedPrice
} }

View File

@ -112,21 +112,6 @@ export async function setNftMetadata(
// theoretically used by aquarius or provider, not implemented yet, will remain hardcoded // theoretically used by aquarius or provider, not implemented yet, will remain hardcoded
const flags = '0x2' const flags = '0x2'
const estGasSetMetadata = await nft.estGasSetMetadata(
asset.nftAddress,
accountId,
0,
asset.services[0].serviceEndpoint,
'',
flags,
encryptedDdo,
'0x' + metadataHash,
[]
)
LoggerInstance.log(
'[setNftMetadata] est Gas set metadata --',
estGasSetMetadata
)
const setMetadataTx = await nft.setMetadata( const setMetadataTx = await nft.setMetadata(
asset.nftAddress, asset.nftAddress,
accountId, accountId,

View File

@ -1,8 +1,12 @@
import { import {
approve, approve,
approveWei,
Datatoken, Datatoken,
FreOrderParams, FreOrderParams,
LoggerInstance,
OrderParams, OrderParams,
ProviderComputeInitialize,
ProviderFees,
ProviderInstance ProviderInstance
} from '@oceanprotocol/lib' } from '@oceanprotocol/lib'
import { AssetExtended } from 'src/@types/AssetExtended' import { AssetExtended } from 'src/@types/AssetExtended'
@ -15,35 +19,44 @@ import {
consumeMarketOrderFee, consumeMarketOrderFee,
consumeMarketFixedSwapFee consumeMarketFixedSwapFee
} from '../../app.config' } from '../../app.config'
import { buyDtFromPool } from './pool'
import { toast } from 'react-toastify'
/** /**
* For pool you need to buy the datatoken beforehand, this always assumes you want to order the first service * For pool you need to buy the datatoken beforehand, this always assumes you want to order the first service
* @param web3 * @param web3
* @param asset * @param asset
* @param orderPriceAndFees
* @param accountId * @param accountId
* @param providerFees
* @param computeConsumerAddress
* @returns {TransactionReceipt} receipt of the order * @returns {TransactionReceipt} receipt of the order
*/ */
export async function order( export async function order(
web3: Web3, web3: Web3,
asset: AssetExtended, asset: AssetExtended,
orderPriceAndFees: OrderPriceAndFees, orderPriceAndFees: OrderPriceAndFees,
accountId: string accountId: string,
providerFees?: ProviderFees,
computeConsumerAddress?: string
): Promise<TransactionReceipt> { ): Promise<TransactionReceipt> {
const datatoken = new Datatoken(web3) const datatoken = new Datatoken(web3)
const config = getOceanConfig(asset.chainId) const config = getOceanConfig(asset.chainId)
const initializeData = await ProviderInstance.initialize( const initializeData =
asset.id, !providerFees &&
asset.services[0].id, (await ProviderInstance.initialize(
0, asset.id,
accountId, asset.services[0].id,
asset.services[0].serviceEndpoint 0,
) accountId,
asset.services[0].serviceEndpoint
))
const orderParams = { const orderParams = {
consumer: accountId, consumer: computeConsumerAddress || accountId,
serviceIndex: 0, serviceIndex: 0,
_providerFee: initializeData.providerFee, _providerFee: providerFees || initializeData.providerFee,
_consumeMarketFee: { _consumeMarketFee: {
consumeMarketFeeAddress: marketFeeAddress, consumeMarketFeeAddress: marketFeeAddress,
consumeMarketFeeAmount: consumeMarketOrderFee, consumeMarketFeeAmount: consumeMarketOrderFee,
@ -51,7 +64,6 @@ export async function order(
} }
} as OrderParams } as OrderParams
// TODO: we need to approve provider fee
switch (asset.accessDetails?.type) { switch (asset.accessDetails?.type) {
case 'fixed': { case 'fixed': {
// this assumes all fees are in ocean // this assumes all fees are in ocean
@ -87,9 +99,9 @@ export async function order(
const tx = await datatoken.startOrder( const tx = await datatoken.startOrder(
asset.accessDetails.datatoken.address, asset.accessDetails.datatoken.address,
accountId, accountId,
accountId, computeConsumerAddress || accountId,
0, 0,
initializeData.providerFee providerFees || initializeData.providerFee
) )
return tx return tx
} }
@ -105,3 +117,176 @@ export async function order(
} }
} }
} }
/**
* called when having a valid order, but with expired provider access, requires approval of the provider fee
* @param web3
* @param asset
* @param accountId
* @param validOrderTx
* @param providerFees
* @returns {TransactionReceipt} receipt of the order
*/
export async function reuseOrder(
web3: Web3,
asset: AssetExtended,
accountId: string,
validOrderTx: string,
providerFees?: ProviderFees
): Promise<TransactionReceipt> {
const datatoken = new Datatoken(web3)
const initializeData =
!providerFees &&
(await ProviderInstance.initialize(
asset.id,
asset.services[0].id,
0,
accountId,
asset.services[0].serviceEndpoint
))
const tx = await datatoken.reuseOrder(
asset.accessDetails.datatoken.address,
accountId,
validOrderTx,
providerFees || initializeData.providerFee
)
return tx
}
async function approveProviderFee(
asset: AssetExtended,
accountId: string,
web3: Web3,
providerFeeAmount: string
): Promise<string> {
const baseToken =
asset?.accessDetails?.type === 'free'
? getOceanConfig(asset.chainId).oceanTokenAddress
: asset?.accessDetails?.baseToken?.address
const txApproveWei = await approveWei(
web3,
accountId,
baseToken,
asset?.accessDetails?.datatoken?.address,
providerFeeAmount
)
return txApproveWei as string // thanks ocean.js
}
async function startOrder(
web3: Web3,
asset: AssetExtended,
orderPriceAndFees: OrderPriceAndFees,
accountId: string,
hasDatatoken: boolean,
initializeData: ProviderComputeInitialize,
computeConsumerAddress?: string
): Promise<TransactionReceipt> {
if (!hasDatatoken && asset?.accessDetails.type === 'dynamic') {
const poolTx = await buyDtFromPool(asset?.accessDetails, accountId, web3)
LoggerInstance.log('[compute] Bought datatoken from pool: ', poolTx)
if (!poolTx) {
toast.error('Failed to buy datatoken from pool!')
return
}
}
const tx = await order(
web3,
asset,
orderPriceAndFees,
accountId,
initializeData.providerFee,
computeConsumerAddress
)
LoggerInstance.log('[compute] Asset ordered:', tx)
return tx
}
/**
* Handles order for compute assets for the following scenarios:
* - have validOrder and no providerFees -> then order is valid, providerFees are valid, it returns the valid order value
* - have validOrder and providerFees -> then order is valid but providerFees are not valid, we need to call reuseOrder and pay only providerFees
* - no validOrder -> we need to call order, to pay 1 DT & providerFees
* @param web3
* @param asset
* @param orderPriceAndFees
* @param accountId
* @param hasDatatoken
* @param initializeData
* @param computeConsumerAddress
* @returns {Promise<string>} tx id
*/
export async function handleComputeOrder(
web3: Web3,
asset: AssetExtended,
orderPriceAndFees: OrderPriceAndFees,
accountId: string,
hasDatatoken: boolean,
initializeData: ProviderComputeInitialize,
computeConsumerAddress?: string
): Promise<string> {
LoggerInstance.log(
'[compute] Handle compute order for asset type: ',
asset.metadata.type
)
LoggerInstance.log('[compute] Using initializeData: ', initializeData)
try {
// Return early when valid order is found, and no provider fees
// are to be paid
if (initializeData?.validOrder && !initializeData.providerFee) {
LoggerInstance.log(
'[compute] Has valid order: ',
initializeData.validOrder
)
return asset?.accessDetails?.validOrderTx
}
// Approve potential Provider fee amount first
if (initializeData?.providerFee?.providerFeeAmount !== '0') {
const txApproveProvider = await approveProviderFee(
asset,
accountId,
web3,
initializeData.providerFee.providerFeeAmount
)
if (!txApproveProvider)
throw new Error('Failed to approve provider fees!')
LoggerInstance.log('[compute] Approved provider fees:', txApproveProvider)
}
if (initializeData?.validOrder) {
LoggerInstance.log('[compute] Calling reuseOrder ...', initializeData)
const txReuseOrder = await reuseOrder(
web3,
asset,
accountId,
initializeData.validOrder,
initializeData.providerFee
)
if (!txReuseOrder) throw new Error('Failed to reuse order!')
LoggerInstance.log('[compute] Reused order:', txReuseOrder)
return txReuseOrder?.transactionHash
}
LoggerInstance.log('[compute] Calling order ...', initializeData)
const txStartOrder = await startOrder(
web3,
asset,
orderPriceAndFees,
accountId,
hasDatatoken,
initializeData,
computeConsumerAddress
)
LoggerInstance.log('[compute] Order succeeded', txStartOrder)
return txStartOrder?.transactionHash
} catch (error) {
toast.error(error.message)
LoggerInstance.error(`[compute] ${error.message}`)
}
}

View File

@ -1,11 +1,54 @@
import { import {
ComputeAlgorithm,
ComputeAsset,
ComputeEnvironment,
downloadFileBrowser, downloadFileBrowser,
FileInfo, FileInfo,
LoggerInstance, LoggerInstance,
ProviderComputeInitializeResults,
ProviderInstance ProviderInstance
} from '@oceanprotocol/lib' } from '@oceanprotocol/lib'
import { AssetExtended } from 'src/@types/AssetExtended' import { AssetExtended } from 'src/@types/AssetExtended'
import Web3 from 'web3' import Web3 from 'web3'
import { getValidUntilTime } from './compute'
export async function initializeProviderForCompute(
dataset: AssetExtended,
algorithm: AssetExtended,
accountId: string,
computeEnv: ComputeEnvironment = null
): Promise<ProviderComputeInitializeResults> {
const computeAsset: ComputeAsset = {
documentId: dataset.id,
serviceId: dataset.services[0].id,
transferTxId: dataset.accessDetails.validOrderTx
}
const computeAlgo: ComputeAlgorithm = {
documentId: algorithm.id,
serviceId: algorithm.services[0].id,
transferTxId: algorithm.accessDetails.validOrderTx
}
const validUntil = getValidUntilTime(
computeEnv?.maxJobDuration,
dataset.services[0].timeout,
algorithm.services[0].timeout
)
try {
return await ProviderInstance.initializeCompute(
[computeAsset],
computeAlgo,
computeEnv?.id,
validUntil,
dataset.services[0].serviceEndpoint,
accountId
)
} catch (error) {
LoggerInstance.error(`Error initializing provider for the compute job!`)
return null
}
}
// TODO: Why do we have these one line functions ?!?!?! // TODO: Why do we have these one line functions ?!?!?!
export async function getEncryptedFiles( export async function getEncryptedFiles(

View File

@ -15,7 +15,7 @@ import {
import { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData' import { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData'
import { UserSalesQuery as UsersSalesList } from '../@types/subgraph/UserSalesQuery' import { UserSalesQuery as UsersSalesList } from '../@types/subgraph/UserSalesQuery'
import { OpcFeesQuery as OpcFeesData } from '../@types/subgraph/OpcFeesQuery' import { OpcFeesQuery as OpcFeesData } from '../@types/subgraph/OpcFeesQuery'
import { calcSingleOutGivenPoolIn, getLiquidityByShares } from './pool' import { calcSingleOutGivenPoolIn } from './pool'
import Decimal from 'decimal.js' import Decimal from 'decimal.js'
import { MAX_DECIMALS } from './constants' import { MAX_DECIMALS } from './constants'

View File

@ -3,6 +3,11 @@
margin-top: calc(var(--spacer) / 2); margin-top: calc(var(--spacer) / 2);
} }
.actionsCenter {
margin: auto;
display: block;
}
.help { .help {
font-size: var(--font-size-mini); font-size: var(--font-size-mini);
color: var(--color-secondary); color: var(--color-secondary);

View File

@ -28,7 +28,8 @@ interface ButtonBuyProps {
type?: 'submit' type?: 'submit'
priceType?: string priceType?: string
algorithmPriceType?: string algorithmPriceType?: string
algorithmConsumableStatus?: number isAlgorithmConsumable?: boolean
hasProviderFee?: boolean
} }
// TODO: we need to take a look at these messages // TODO: we need to take a look at these messages
@ -75,7 +76,8 @@ function getComputeAssetHelpText(
dtBalanceSelectedComputeAsset?: string, dtBalanceSelectedComputeAsset?: string,
selectedComputeAssettLowPoolLiquidity?: boolean, selectedComputeAssettLowPoolLiquidity?: boolean,
selectedComputeAssetType?: string, selectedComputeAssetType?: string,
algorithmConsumableStatus?: number isAlgorithmConsumable?: boolean,
hasProviderFee?: boolean
) { ) {
const computeAssetHelpText = getConsumeHelpText( const computeAssetHelpText = getConsumeHelpText(
dtBalance, dtBalance,
@ -90,14 +92,9 @@ function getComputeAssetHelpText(
) )
const computeAlgoHelpText = const computeAlgoHelpText =
(!dtSymbolSelectedComputeAsset && !dtBalanceSelectedComputeAsset) || (!dtSymbolSelectedComputeAsset && !dtBalanceSelectedComputeAsset) ||
isConsumable === false isConsumable === false ||
isAlgorithmConsumable === false
? '' ? ''
: algorithmConsumableStatus === 1
? 'The selected algorithm has been temporarily disabled by the publisher, please try again later.'
: algorithmConsumableStatus === 2
? 'Access denied, your wallet address is not found on the selected algorithm allow list.'
: algorithmConsumableStatus === 3
? 'Access denied, your wallet address is found on the selected algorithm deny list.'
: hasPreviousOrderSelectedComputeAsset : hasPreviousOrderSelectedComputeAsset
? `You already bought the selected ${selectedComputeAssetType}, allowing you to use it without paying again.` ? `You already bought the selected ${selectedComputeAssetType}, allowing you to use it without paying again.`
: hasDatatokenSelectedComputeAsset : hasDatatokenSelectedComputeAsset
@ -107,11 +104,14 @@ function getComputeAssetHelpText(
: isBalanceSufficient === false : isBalanceSufficient === false
? '' ? ''
: `Additionally, you will buy 1 ${dtSymbolSelectedComputeAsset} for the ${selectedComputeAssetType} and spend it back to its publisher and pool.` : `Additionally, you will buy 1 ${dtSymbolSelectedComputeAsset} for the ${selectedComputeAssetType} and spend it back to its publisher and pool.`
const providerFeeHelpText = hasProviderFee
? 'In order to start the job you also need to pay the fees for renting the c2d resources.'
: 'C2D resources required to start the job are available, no payment required for those fees.'
const computeHelpText = selectedComputeAssettLowPoolLiquidity const computeHelpText = selectedComputeAssettLowPoolLiquidity
? computeAlgoHelpText ? computeAlgoHelpText
: lowPoolLiquidity : lowPoolLiquidity
? computeAssetHelpText ? computeAssetHelpText
: `${computeAssetHelpText} ${computeAlgoHelpText}` : `${computeAssetHelpText} ${computeAlgoHelpText} ${providerFeeHelpText}`
return computeHelpText return computeHelpText
} }
@ -140,7 +140,8 @@ export default function ButtonBuy({
type, type,
priceType, priceType,
algorithmPriceType, algorithmPriceType,
algorithmConsumableStatus isAlgorithmConsumable,
hasProviderFee
}: ButtonBuyProps): ReactElement { }: ButtonBuyProps): ReactElement {
const buttonText = const buttonText =
action === 'download' action === 'download'
@ -149,7 +150,9 @@ export default function ButtonBuy({
: priceType === 'free' : priceType === 'free'
? 'Get' ? 'Get'
: `Buy ${assetTimeout === 'Forever' ? '' : ` for ${assetTimeout}`}` : `Buy ${assetTimeout === 'Forever' ? '' : ` for ${assetTimeout}`}`
: hasPreviousOrder && hasPreviousOrderSelectedComputeAsset : hasPreviousOrder &&
hasPreviousOrderSelectedComputeAsset &&
!hasProviderFee
? 'Start Compute Job' ? 'Start Compute Job'
: priceType === 'free' && algorithmPriceType === 'free' : priceType === 'free' && algorithmPriceType === 'free'
? 'Order Compute Job' ? 'Order Compute Job'
@ -166,6 +169,7 @@ export default function ButtonBuy({
type={type} type={type}
onClick={onClick} onClick={onClick}
disabled={disabled} disabled={disabled}
className={action === 'compute' ? styles.actionsCenter : ''}
> >
{buttonText} {buttonText}
</Button> </Button>
@ -198,7 +202,8 @@ export default function ButtonBuy({
dtBalanceSelectedComputeAsset, dtBalanceSelectedComputeAsset,
selectedComputeAssetLowPoolLiquidity, selectedComputeAssetLowPoolLiquidity,
selectedComputeAssetType, selectedComputeAssetType,
algorithmConsumableStatus isAlgorithmConsumable,
hasProviderFee
)} )}
</div> </div>
</> </>

View File

@ -12,6 +12,7 @@ export default function Blockies({
className className
}: BlockiesProps): ReactElement { }: BlockiesProps): ReactElement {
if (!accountId) return null if (!accountId) return null
const blockies = toDataUrl(accountId) const blockies = toDataUrl(accountId)
return ( return (

View File

@ -5,7 +5,6 @@ import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
import AssetComputeList from '@shared/AssetList/AssetComputeList' import AssetComputeList from '@shared/AssetList/AssetComputeList'
import { useCancelToken } from '@hooks/useCancelToken' import { useCancelToken } from '@hooks/useCancelToken'
import { getServiceByName } from '@utils/ddo' import { getServiceByName } from '@utils/ddo'
import { Asset } from '@oceanprotocol/lib'
import { AssetExtended } from 'src/@types/AssetExtended' import { AssetExtended } from 'src/@types/AssetExtended'
export default function AlgorithmDatasetsListForCompute({ export default function AlgorithmDatasetsListForCompute({
@ -15,9 +14,9 @@ export default function AlgorithmDatasetsListForCompute({
asset: AssetExtended asset: AssetExtended
algorithmDid: string algorithmDid: string
}): ReactElement { }): ReactElement {
const newCancelToken = useCancelToken()
const [datasetsForCompute, setDatasetsForCompute] = const [datasetsForCompute, setDatasetsForCompute] =
useState<AssetSelectionAsset[]>() useState<AssetSelectionAsset[]>()
const newCancelToken = useCancelToken()
useEffect(() => { useEffect(() => {
if (!asset) return if (!asset) return
@ -37,7 +36,7 @@ export default function AlgorithmDatasetsListForCompute({
setDatasetsForCompute(datasets) setDatasetsForCompute(datasets)
} }
asset.metadata.type === 'algorithm' && getDatasetsAllowedForCompute() asset.metadata.type === 'algorithm' && getDatasetsAllowedForCompute()
}, [asset?.metadata?.type]) }, [asset, algorithmDid, newCancelToken])
return ( return (
<div className={styles.datasetsContainer}> <div className={styles.datasetsContainer}>

View File

@ -10,13 +10,18 @@ import { useAsset } from '@context/Asset'
import { useWeb3 } from '@context/Web3' import { useWeb3 } from '@context/Web3'
import content from '../../../../../content/pages/startComputeDataset.json' import content from '../../../../../content/pages/startComputeDataset.json'
import { Asset } from '@oceanprotocol/lib' import { Asset } from '@oceanprotocol/lib'
import { AccessDetails } from 'src/@types/Price' import { OrderPriceAndFees } from 'src/@types/Price'
import { getAccessDetails } from '@utils/accessDetailsAndPricing'
import { AssetExtended } from 'src/@types/AssetExtended'
import Decimal from 'decimal.js'
import { MAX_DECIMALS } from '@utils/constants'
import { useMarketMetadata } from '@context/MarketMetadata' import { useMarketMetadata } from '@context/MarketMetadata'
import Alert from '@shared/atoms/Alert' import Alert from '@shared/atoms/Alert'
export default function FormStartCompute({ export default function FormStartCompute({
algorithms, algorithms,
ddoListAlgorithms, ddoListAlgorithms,
selectedAlgorithmAsset,
setSelectedAlgorithm, setSelectedAlgorithm,
isLoading, isLoading,
isComputeButtonDisabled, isComputeButtonDisabled,
@ -35,13 +40,17 @@ export default function FormStartCompute({
selectedComputeAssetType, selectedComputeAssetType,
selectedComputeAssetTimeout, selectedComputeAssetTimeout,
stepText, stepText,
algorithmConsumeDetails,
isConsumable, isConsumable,
consumableFeedback consumableFeedback,
datasetOrderPriceAndFees,
algoOrderPriceAndFees,
providerFeeAmount,
validUntil
}: { }: {
algorithms: AssetSelectionAsset[] algorithms: AssetSelectionAsset[]
ddoListAlgorithms: Asset[] ddoListAlgorithms: Asset[]
setSelectedAlgorithm: React.Dispatch<React.SetStateAction<Asset>> selectedAlgorithmAsset: AssetExtended
setSelectedAlgorithm: React.Dispatch<React.SetStateAction<AssetExtended>>
isLoading: boolean isLoading: boolean
isComputeButtonDisabled: boolean isComputeButtonDisabled: boolean
hasPreviousOrder: boolean hasPreviousOrder: boolean
@ -59,19 +68,27 @@ export default function FormStartCompute({
selectedComputeAssetType?: string selectedComputeAssetType?: string
selectedComputeAssetTimeout?: string selectedComputeAssetTimeout?: string
stepText: string stepText: string
algorithmConsumeDetails: AccessDetails
isConsumable: boolean isConsumable: boolean
consumableFeedback: string consumableFeedback: string
datasetOrderPriceAndFees?: OrderPriceAndFees
algoOrderPriceAndFees?: OrderPriceAndFees
providerFeeAmount?: string
validUntil?: string
}): ReactElement { }): ReactElement {
const { siteContent } = useMarketMetadata() const { siteContent } = useMarketMetadata()
const { accountId, balance } = useWeb3()
const { isValid, values }: FormikContextType<{ algorithm: string }> = const { isValid, values }: FormikContextType<{ algorithm: string }> =
useFormikContext() useFormikContext()
const { asset, isAssetNetwork } = useAsset() const { asset, isAssetNetwork } = useAsset()
const [totalPrice, setTotalPrice] = useState(asset?.accessDetails?.price)
const [totalPrice, setTotalPrice] = useState('0')
const [datasetOrderPrice, setDatasetOrderPrice] = useState(
asset?.accessDetails?.price
)
const [algoOrderPrice, setAlgoOrderPrice] = useState(
selectedAlgorithmAsset?.accessDetails?.price
)
const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>(false) const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>(false)
const { accountId, balance } = useWeb3()
const [algorithmConsumableStatus, setAlgorithmConsumableStatus] =
useState<number>()
function getAlgorithmAsset(algorithmId: string): Asset { function getAlgorithmAsset(algorithmId: string): Asset {
let assetDdo = null let assetDdo = null
@ -82,50 +99,79 @@ export default function FormStartCompute({
} }
useEffect(() => { useEffect(() => {
if (!values.algorithm) return if (!values.algorithm || !accountId || !isConsumable) return
const algorithmDDO = getAlgorithmAsset(values.algorithm)
setSelectedAlgorithm(algorithmDDO)
if (!accountId || !isConsumable) return async function fetchAlgorithmAssetExtended() {
async function checkIsConsumable() { const algorithmAsset = getAlgorithmAsset(values.algorithm)
// const consumable = await ocean.assets.isConsumable( const accessDetails = await getAccessDetails(
// algorithmDDO as any, algorithmAsset.chainId,
// accountId.toLowerCase() algorithmAsset.services[0].datatokenAddress,
// ) algorithmAsset.services[0].timeout,
// if (consumable) setAlgorithmConsumableStatus(consumable.status) accountId
)
const extendedAlgoAsset: AssetExtended = {
...algorithmAsset,
accessDetails
}
setSelectedAlgorithm(extendedAlgoAsset)
} }
checkIsConsumable() fetchAlgorithmAssetExtended()
}, [values.algorithm, accountId, isConsumable]) }, [values.algorithm, accountId, isConsumable])
// //
// Set price for calculation output // Set price for calculation output
// //
useEffect(() => { useEffect(() => {
if (!asset?.accessDetails || !algorithmConsumeDetails) return if (!asset?.accessDetails || !selectedAlgorithmAsset?.accessDetails) return
setDatasetOrderPrice(
datasetOrderPriceAndFees?.price || asset.accessDetails.price
)
setAlgoOrderPrice(
algoOrderPriceAndFees?.price ||
selectedAlgorithmAsset?.accessDetails.price
)
const priceDataset = const priceDataset =
hasPreviousOrder || hasDatatoken ? 0 : Number(asset.accessDetails.price) hasPreviousOrder || hasDatatoken
? new Decimal(0)
: new Decimal(
datasetOrderPriceAndFees?.price || asset.accessDetails.price
).toDecimalPlaces(MAX_DECIMALS)
const priceAlgo = const priceAlgo =
hasPreviousOrderSelectedComputeAsset || hasDatatokenSelectedComputeAsset hasPreviousOrderSelectedComputeAsset || hasDatatokenSelectedComputeAsset
? 0 ? new Decimal(0)
: Number(algorithmConsumeDetails.price) : new Decimal(
algoOrderPriceAndFees?.price ||
setTotalPrice((priceDataset + priceAlgo).toString()) selectedAlgorithmAsset.accessDetails.price
).toDecimalPlaces(MAX_DECIMALS)
const providerFees = providerFeeAmount
? new Decimal(providerFeeAmount).toDecimalPlaces(MAX_DECIMALS)
: new Decimal(0)
const totalPrice = priceDataset
.plus(priceAlgo)
.plus(providerFees)
.toDecimalPlaces(MAX_DECIMALS)
.toString()
setTotalPrice(totalPrice)
}, [ }, [
asset?.accessDetails, asset?.accessDetails,
algorithmConsumeDetails, selectedAlgorithmAsset?.accessDetails,
hasPreviousOrder, hasPreviousOrder,
hasDatatoken, hasDatatoken,
hasPreviousOrderSelectedComputeAsset, hasPreviousOrderSelectedComputeAsset,
hasDatatokenSelectedComputeAsset hasDatatokenSelectedComputeAsset,
datasetOrderPriceAndFees,
algoOrderPriceAndFees,
providerFeeAmount
]) ])
useEffect(() => { useEffect(() => {
if (!totalPrice) return if (!totalPrice || !balance?.ocean || !dtBalance) return
setIsBalanceSufficient( setIsBalanceSufficient(
compareAsBN(balance.ocean, `${totalPrice}`) || Number(dtBalance) >= 1 compareAsBN(balance.ocean, `${totalPrice}`) || Number(dtBalance) >= 1
) )
}, [totalPrice]) }, [totalPrice, balance?.ocean, dtBalance])
return ( return (
<Form className={styles.form}> <Form className={styles.form}>
@ -140,6 +186,7 @@ export default function FormStartCompute({
{...field} {...field}
options={algorithms} options={algorithms}
component={Input} component={Input}
disabled={isLoading}
/> />
))} ))}
@ -152,9 +199,13 @@ export default function FormStartCompute({
hasDatatoken={hasDatatoken} hasDatatoken={hasDatatoken}
selectedComputeAssetTimeout={selectedComputeAssetTimeout} selectedComputeAssetTimeout={selectedComputeAssetTimeout}
hasDatatokenSelectedComputeAsset={hasDatatokenSelectedComputeAsset} hasDatatokenSelectedComputeAsset={hasDatatokenSelectedComputeAsset}
algorithmConsumeDetails={algorithmConsumeDetails} algorithmConsumeDetails={selectedAlgorithmAsset?.accessDetails}
symbol={oceanSymbol} symbol={oceanSymbol}
totalPrice={Number.parseFloat(totalPrice)} totalPrice={totalPrice}
datasetOrderPrice={datasetOrderPrice}
algoOrderPrice={algoOrderPrice}
providerFeeAmount={providerFeeAmount}
validUntil={validUntil}
/> />
<ButtonBuy <ButtonBuy
@ -164,7 +215,7 @@ export default function FormStartCompute({
!isValid || !isValid ||
!isBalanceSufficient || !isBalanceSufficient ||
!isAssetNetwork || !isAssetNetwork ||
algorithmConsumableStatus > 0 !selectedAlgorithmAsset?.accessDetails?.isPurchasable
} }
hasPreviousOrder={hasPreviousOrder} hasPreviousOrder={hasPreviousOrder}
hasDatatoken={hasDatatoken} hasDatatoken={hasDatatoken}
@ -187,11 +238,14 @@ export default function FormStartCompute({
isLoading={isLoading} isLoading={isLoading}
type="submit" type="submit"
priceType={asset?.accessDetails?.type} priceType={asset?.accessDetails?.type}
algorithmPriceType={algorithmConsumeDetails?.type} algorithmPriceType={selectedAlgorithmAsset?.accessDetails?.type}
isBalanceSufficient={isBalanceSufficient} isBalanceSufficient={isBalanceSufficient}
isConsumable={isConsumable} isConsumable={isConsumable}
consumableFeedback={consumableFeedback} consumableFeedback={consumableFeedback}
algorithmConsumableStatus={algorithmConsumableStatus} isAlgorithmConsumable={
selectedAlgorithmAsset?.accessDetails?.isPurchasable
}
hasProviderFee={providerFeeAmount && providerFeeAmount !== '0'}
/> />
</Form> </Form>
) )

View File

@ -36,8 +36,9 @@
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
padding-top: calc(var(--spacer) / 7); padding-top: calc(var(--spacer) / 7);
padding-bottom: calc(var(--spacer) / 7); padding-bottom: calc(var(--spacer) / 7);
display: flex; display: grid;
justify-content: space-between; grid-template-columns: 5% 1fr auto;
column-gap: calc(var(--spacer) / 10);
} }
.priceRow:last-child { .priceRow:last-child {
@ -47,8 +48,14 @@
.sign { .sign {
display: inline-block; display: inline-block;
width: 5%;
text-align: left; text-align: left;
color: var(--color-secondary); color: var(--color-secondary);
font-size: var(--font-size-base); font-size: var(--font-size-base);
} }
.type {
display: inline-block;
text-align: left;
color: var(--color-secondary);
font-size: var(--font-size-mini);
}

View File

@ -4,9 +4,11 @@ import PriceUnit from '@shared/Price/PriceUnit'
import Tooltip from '@shared/atoms/Tooltip' import Tooltip from '@shared/atoms/Tooltip'
import styles from './PriceOutput.module.css' import styles from './PriceOutput.module.css'
import { AccessDetails } from 'src/@types/Price' import { AccessDetails } from 'src/@types/Price'
import { MAX_DECIMALS } from '@utils/constants'
import Decimal from 'decimal.js'
interface PriceOutputProps { interface PriceOutputProps {
totalPrice: number totalPrice: string
hasPreviousOrder: boolean hasPreviousOrder: boolean
hasDatatoken: boolean hasDatatoken: boolean
symbol: string symbol: string
@ -15,6 +17,10 @@ interface PriceOutputProps {
hasDatatokenSelectedComputeAsset: boolean hasDatatokenSelectedComputeAsset: boolean
algorithmConsumeDetails: AccessDetails algorithmConsumeDetails: AccessDetails
selectedComputeAssetTimeout: string selectedComputeAssetTimeout: string
datasetOrderPrice?: number
algoOrderPrice?: number
providerFeeAmount?: string
validUntil?: string
} }
function Row({ function Row({
@ -23,18 +29,21 @@ function Row({
hasDatatoken, hasDatatoken,
symbol, symbol,
timeout, timeout,
sign sign,
type
}: { }: {
price: number price: string
hasPreviousOrder?: boolean hasPreviousOrder?: boolean
hasDatatoken?: boolean hasDatatoken?: boolean
symbol?: string symbol?: string
timeout?: string timeout?: string
sign?: string sign?: string
type?: string
}) { }) {
return ( return (
<div className={styles.priceRow}> <div className={styles.priceRow}>
<div className={styles.sign}>{sign}</div> <div className={styles.sign}>{sign}</div>
<div className={styles.type}>{type}</div>
<div> <div>
<PriceUnit <PriceUnit
price={hasPreviousOrder || hasDatatoken ? '0' : `${price}`} price={hasPreviousOrder || hasDatatoken ? '0' : `${price}`}
@ -62,7 +71,11 @@ export default function PriceOutput({
hasPreviousOrderSelectedComputeAsset, hasPreviousOrderSelectedComputeAsset,
hasDatatokenSelectedComputeAsset, hasDatatokenSelectedComputeAsset,
algorithmConsumeDetails, algorithmConsumeDetails,
selectedComputeAssetTimeout selectedComputeAssetTimeout,
datasetOrderPrice,
algoOrderPrice,
providerFeeAmount,
validUntil
}: PriceOutputProps): ReactElement { }: PriceOutputProps): ReactElement {
const { asset } = useAsset() const { asset } = useAsset()
@ -76,17 +89,34 @@ export default function PriceOutput({
<Row <Row
hasPreviousOrder={hasPreviousOrder} hasPreviousOrder={hasPreviousOrder}
hasDatatoken={hasDatatoken} hasDatatoken={hasDatatoken}
price={Number.parseFloat(asset?.accessDetails?.price)} price={new Decimal(
datasetOrderPrice || asset?.accessDetails?.price || 0
)
.toDecimalPlaces(MAX_DECIMALS)
.toString()}
timeout={assetTimeout} timeout={assetTimeout}
symbol={symbol} symbol={symbol}
type="DATASET"
/> />
<Row <Row
hasPreviousOrder={hasPreviousOrderSelectedComputeAsset} hasPreviousOrder={hasPreviousOrderSelectedComputeAsset}
hasDatatoken={hasDatatokenSelectedComputeAsset} hasDatatoken={hasDatatokenSelectedComputeAsset}
price={Number.parseFloat(algorithmConsumeDetails?.price)} price={new Decimal(
algoOrderPrice || algorithmConsumeDetails?.price || 0
)
.toDecimalPlaces(MAX_DECIMALS)
.toString()}
timeout={selectedComputeAssetTimeout} timeout={selectedComputeAssetTimeout}
symbol={symbol} symbol={symbol}
sign="+" sign="+"
type="ALGORITHM"
/>
<Row
price={providerFeeAmount} // initializeCompute.provider fee amount
timeout={`${validUntil} seconds`} // valid until value
symbol={symbol}
sign="+"
type="C2D RESOURCES"
/> />
<Row price={totalPrice} symbol={symbol} sign="=" /> <Row price={totalPrice} symbol={symbol} sign="=" />
</div> </div>

View File

@ -1,232 +1,291 @@
import React, { useState, ReactElement, useEffect, useCallback } from 'react' import React, { useState, ReactElement, useEffect } from 'react'
import { import {
Asset, Asset,
DDO, DDO,
PublisherTrustedAlgorithm, FileInfo,
FileInfo Datatoken,
ProviderInstance,
ComputeAsset,
ZERO_ADDRESS,
ComputeEnvironment,
LoggerInstance,
ComputeAlgorithm,
ComputeOutput,
ProviderComputeInitializeResults,
unitsToAmount
} from '@oceanprotocol/lib' } from '@oceanprotocol/lib'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import Price from '@shared/Price' import Price from '@shared/Price'
import FileIcon from '@shared/FileIcon' import FileIcon from '@shared/FileIcon'
import Alert from '@shared/atoms/Alert' import Alert from '@shared/atoms/Alert'
import { useWeb3 } from '@context/Web3' import { useWeb3 } from '@context/Web3'
import {
generateBaseQuery,
getFilterTerm,
queryMetadata
} from '@utils/aquarius'
import { Formik } from 'formik' import { Formik } from 'formik'
import { getInitialValues, validationSchema } from './_constants' import { getInitialValues, validationSchema } from './_constants'
import axios from 'axios'
import FormStartComputeDataset from './FormComputeDataset' import FormStartComputeDataset from './FormComputeDataset'
import styles from './index.module.css' import styles from './index.module.css'
import SuccessConfetti from '@shared/SuccessConfetti' import SuccessConfetti from '@shared/SuccessConfetti'
import { getServiceByName } from '@utils/ddo' import { getServiceByName, secondsToString } from '@utils/ddo'
import {
isOrderable,
getAlgorithmAssetSelectionList,
getAlgorithmsForAsset,
getComputeEnviroment
} from '@utils/compute'
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection' import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
import AlgorithmDatasetsListForCompute from './AlgorithmDatasetsListForCompute' import AlgorithmDatasetsListForCompute from './AlgorithmDatasetsListForCompute'
import { getPreviousOrders } from '@utils/subgraph'
import AssetActionHistoryTable from '../AssetActionHistoryTable' import AssetActionHistoryTable from '../AssetActionHistoryTable'
import ComputeJobs from '../../../Profile/History/ComputeJobs' import ComputeJobs from '../../../Profile/History/ComputeJobs'
import { useCancelToken } from '@hooks/useCancelToken' import { useCancelToken } from '@hooks/useCancelToken'
import { useIsMounted } from '@hooks/useIsMounted' import { Decimal } from 'decimal.js'
import { SortTermOptions } from '../../../../@types/aquarius/SearchQuery' import { useAbortController } from '@hooks/useAbortController'
import { getAccessDetails } from '@utils/accessDetailsAndPricing' import { getOrderPriceAndFees } from '@utils/accessDetailsAndPricing'
import { AccessDetails } from 'src/@types/Price' import { OrderPriceAndFees } from 'src/@types/Price'
import { transformAssetToAssetSelection } from '@utils/assetConvertor' import { handleComputeOrder } from '@utils/order'
import { AssetExtended } from 'src/@types/AssetExtended'
import { getComputeFeedback } from '@utils/feedback'
import { usePool } from '@context/Pool'
import { useMarketMetadata } from '@context/MarketMetadata' import { useMarketMetadata } from '@context/MarketMetadata'
import { getPoolData } from '@context/Pool/_utils'
import { getDummyWeb3 } from '@utils/web3'
import { initializeProviderForCompute } from '@utils/provider'
export default function Compute({ export default function Compute({
ddo, asset,
accessDetails,
dtBalance, dtBalance,
file, file,
fileIsLoading, fileIsLoading,
isConsumable,
consumableFeedback consumableFeedback
}: { }: {
ddo: Asset asset: AssetExtended
accessDetails: AccessDetails
dtBalance: string dtBalance: string
file: FileInfo file: FileInfo
fileIsLoading?: boolean fileIsLoading?: boolean
isConsumable?: boolean
consumableFeedback?: string consumableFeedback?: string
}): ReactElement { }): ReactElement {
const { appConfig } = useMarketMetadata() const { accountId, web3 } = useWeb3()
const { accountId } = useWeb3() const { getOpcFeeForToken } = useMarketMetadata()
const [isJobStarting, setIsJobStarting] = useState(false) const { poolData } = usePool()
const newAbortController = useAbortController()
const newCancelToken = useCancelToken()
const [isOrdering, setIsOrdering] = useState(false)
const [isOrdered, setIsOrdered] = useState(false)
const [error, setError] = useState<string>() const [error, setError] = useState<string>()
const [algorithmList, setAlgorithmList] = useState<AssetSelectionAsset[]>() const [algorithmList, setAlgorithmList] = useState<AssetSelectionAsset[]>()
const [ddoAlgorithmList, setDdoAlgorithmList] = useState<Asset[]>() const [ddoAlgorithmList, setDdoAlgorithmList] = useState<Asset[]>()
const [selectedAlgorithmAsset, setSelectedAlgorithmAsset] = useState<Asset>() const [selectedAlgorithmAsset, setSelectedAlgorithmAsset] =
useState<AssetExtended>()
const [hasAlgoAssetDatatoken, setHasAlgoAssetDatatoken] = useState<boolean>() const [hasAlgoAssetDatatoken, setHasAlgoAssetDatatoken] = useState<boolean>()
const [isPublished, setIsPublished] = useState(false) const [algorithmDTBalance, setAlgorithmDTBalance] = useState<string>()
const [hasPreviousDatasetOrder, setHasPreviousDatasetOrder] = useState(false)
const [previousDatasetOrderId, setPreviousDatasetOrderId] = useState<string>() const [validOrderTx, setValidOrderTx] = useState('')
const [hasPreviousAlgorithmOrder, setHasPreviousAlgorithmOrder] = const [validAlgorithmOrderTx, setValidAlgorithmOrderTx] = useState('')
useState(false)
const [algorithmDTBalance, setalgorithmDTBalance] = useState<string>()
const [algorithmConsumeDetails, setAlgorithmConsumeDetails] =
useState<AccessDetails>()
const [previousAlgorithmOrderId, setPreviousAlgorithmOrderId] =
useState<string>()
const [datasetTimeout, setDatasetTimeout] = useState<string>()
const [algorithmTimeout, setAlgorithmTimeout] = useState<string>()
const newCancelToken = useCancelToken()
const hasDatatoken = Number(dtBalance) >= 1
const isMounted = useIsMounted()
const [isConsumablePrice, setIsConsumablePrice] = useState(true) const [isConsumablePrice, setIsConsumablePrice] = useState(true)
const [isAlgoConsumablePrice, setIsAlgoConsumablePrice] = useState(true) const [isConsumableaAlgorithmPrice, setIsConsumableAlgorithmPrice] =
useState(true)
const [computeStatusText, setComputeStatusText] = useState('')
const [computeEnv, setComputeEnv] = useState<ComputeEnvironment>()
const [initializedProviderResponse, setInitializedProviderResponse] =
useState<ProviderComputeInitializeResults>()
const [providerFeeAmount, setProviderFeeAmount] = useState<string>('0')
const [computeValidUntil, setComputeValidUntil] = useState<string>('0')
const [datasetOrderPriceAndFees, setDatasetOrderPriceAndFees] =
useState<OrderPriceAndFees>()
const [algoOrderPriceAndFees, setAlgoOrderPriceAndFees] =
useState<OrderPriceAndFees>()
const [isRequestingAlgoOrderPrice, setIsRequestingAlgoOrderPrice] =
useState(false)
const [refetchJobs, setRefetchJobs] = useState(false)
const hasDatatoken = Number(dtBalance) >= 1
const isComputeButtonDisabled = const isComputeButtonDisabled =
isJobStarting === true || isOrdering === true ||
file === null || file === null ||
(!hasPreviousDatasetOrder && !hasDatatoken && !isConsumablePrice) || (!validOrderTx && !hasDatatoken && !isConsumablePrice) ||
(!hasPreviousAlgorithmOrder && (!validAlgorithmOrderTx &&
!hasAlgoAssetDatatoken && !hasAlgoAssetDatatoken &&
!isAlgoConsumablePrice) !isConsumableaAlgorithmPrice)
const service = ddo?.services[0] async function checkAssetDTBalance(asset: DDO): Promise<boolean> {
const { timeout } = service if (!asset?.services[0].datatokenAddress) return
const web3 = await getDummyWeb3(asset?.chainId)
async function checkPreviousOrders(ddo: DDO) { const datatokenInstance = new Datatoken(web3)
const { type } = ddo.metadata const dtBalance = await datatokenInstance.balance(
asset?.services[0].datatokenAddress,
const orderId = await getPreviousOrders( accountId
ddo.services[0].datatokenAddress?.toLowerCase(),
accountId?.toLowerCase(),
timeout.toString()
) )
setAlgorithmDTBalance(new Decimal(dtBalance).toString())
if (!isMounted()) return const hasAlgoDt = Number(dtBalance) >= 1
if (type === 'algorithm') { setHasAlgoAssetDatatoken(hasAlgoDt)
setPreviousAlgorithmOrderId(orderId) return hasAlgoDt
setHasPreviousAlgorithmOrder(!!orderId)
} else {
setPreviousDatasetOrderId(orderId)
setHasPreviousDatasetOrder(!!orderId)
}
} }
async function checkAssetDTBalance(asset: DDO) { async function initPriceAndFees() {
// const AssetDtBalance = await ocean.datatokens.balance( try {
// asset.services[0].datatokenAddress, const computeEnv = await getComputeEnviroment(asset)
// accountId if (!computeEnv || !computeEnv.id)
// ) throw new Error(`Error getting compute environments!`)
// setalgorithmDTBalance(AssetDtBalance)
// setHasAlgoAssetDatatoken(Number(AssetDtBalance) >= 1)
}
function getQuerryString( setComputeEnv(computeEnv)
trustedAlgorithmList: PublisherTrustedAlgorithm[], const initializedProvider = await initializeProviderForCompute(
chainId?: number asset,
): SearchQuery { selectedAlgorithmAsset,
const algorithmDidList = trustedAlgorithmList.map((x) => x.did) accountId,
computeEnv
const baseParams = {
chainIds: [chainId],
sort: { sortBy: SortTermOptions.Created },
filters: [
getFilterTerm('service.attributes.main.type', 'algorithm'),
getFilterTerm('id', algorithmDidList)
]
} as BaseQueryParams
const query = generateBaseQuery(baseParams)
return query
}
async function getAlgorithmList(): Promise<AssetSelectionAsset[]> {
const source = axios.CancelToken.source()
const computeService = ddo.services[0]
let algorithmSelectionList: AssetSelectionAsset[]
if (
!computeService.compute ||
!computeService.compute.publisherTrustedAlgorithms ||
computeService.compute.publisherTrustedAlgorithms.length === 0
) {
algorithmSelectionList = []
} else {
const gueryResults = await queryMetadata(
getQuerryString(
computeService.compute.publisherTrustedAlgorithms,
ddo.chainId
),
source.token
) )
setDdoAlgorithmList(gueryResults.results) if (
!initializedProvider ||
algorithmSelectionList = await transformAssetToAssetSelection( !initializedProvider?.datasets ||
computeService?.serviceEndpoint, !initializedProvider?.algorithm
gueryResults.results,
[]
) )
} throw new Error(`Error initializing provider for the compute job!`)
return algorithmSelectionList
}
const initMetadata = useCallback(async (ddo: Asset): Promise<void> => { setInitializedProviderResponse(initializedProvider)
if (!ddo) return setProviderFeeAmount(
const accessDetails = await getAccessDetails( await unitsToAmount(
ddo.chainId, web3,
ddo.services[0].datatokenAddress initializedProvider?.datasets?.[0]?.providerFee?.providerFeeToken,
) initializedProvider?.datasets?.[0]?.providerFee?.providerFeeAmount
setAlgorithmConsumeDetails(accessDetails) )
}, []) )
const computeDuration = (
parseInt(initializedProvider?.datasets?.[0]?.providerFee?.validUntil) -
Math.floor(Date.now() / 1000)
).toString()
setComputeValidUntil(computeDuration)
useEffect(() => { if (
if (!algorithmConsumeDetails) return asset?.accessDetails?.addressOrId !== ZERO_ADDRESS &&
asset?.accessDetails?.type !== 'free' &&
initializedProvider?.datasets?.[0]?.providerFee
) {
setComputeStatusText(
getComputeFeedback(
asset.accessDetails?.baseToken?.symbol,
asset.accessDetails?.datatoken?.symbol,
asset.metadata.type
)[0]
)
const poolParams =
asset?.accessDetails?.type === 'dynamic'
? {
tokenInLiquidity: poolData?.baseTokenLiquidity,
tokenOutLiquidity: poolData?.datatokenLiquidity,
tokenOutAmount: '1',
opcFee: getOpcFeeForToken(
asset?.accessDetails?.baseToken.address,
asset?.chainId
),
lpSwapFee: poolData?.liquidityProviderSwapFee,
publishMarketSwapFee:
asset?.accessDetails?.publisherMarketOrderFee,
consumeMarketSwapFee: '0'
}
: null
const datasetPriceAndFees = await getOrderPriceAndFees(
asset,
ZERO_ADDRESS,
poolParams,
initializedProvider?.datasets?.[0]?.providerFee
)
if (!datasetPriceAndFees)
throw new Error('Error setting dataset price and fees!')
setIsAlgoConsumablePrice(algorithmConsumeDetails.isPurchasable) setDatasetOrderPriceAndFees(datasetPriceAndFees)
}, [algorithmConsumeDetails])
useEffect(() => {
if (!accessDetails) return
setIsConsumablePrice(accessDetails.isPurchasable)
}, [accessDetails])
// useEffect(() => {
// setDatasetTimeout(secondsToString(timeout))
// }, [ddo])
useEffect(() => {
if (!ddo) return
getAlgorithmList().then((algorithms) => {
setAlgorithmList(algorithms)
})
}, [ddo])
useEffect(() => {
if (!accountId) return
checkPreviousOrders(ddo)
}, [ddo, accountId])
useEffect(() => {
if (!selectedAlgorithmAsset) return
initMetadata(selectedAlgorithmAsset)
const { timeout } = ddo.services[0]
// setAlgorithmTimeout(secondsToString(timeout))
if (accountId) {
if (getServiceByName(selectedAlgorithmAsset, 'access')) {
checkPreviousOrders(selectedAlgorithmAsset).then(() => {
if (
!hasPreviousAlgorithmOrder &&
getServiceByName(selectedAlgorithmAsset, 'compute')
) {
checkPreviousOrders(selectedAlgorithmAsset)
}
})
} else if (getServiceByName(selectedAlgorithmAsset, 'compute')) {
checkPreviousOrders(selectedAlgorithmAsset)
} }
if (
selectedAlgorithmAsset?.accessDetails?.addressOrId !== ZERO_ADDRESS &&
selectedAlgorithmAsset?.accessDetails?.type !== 'free' &&
initializedProvider?.algorithm?.providerFee
) {
setComputeStatusText(
getComputeFeedback(
selectedAlgorithmAsset?.accessDetails?.baseToken?.symbol,
selectedAlgorithmAsset?.accessDetails?.datatoken?.symbol,
selectedAlgorithmAsset?.metadata?.type
)[0]
)
let algoPoolParams = null
if (selectedAlgorithmAsset?.accessDetails?.type === 'dynamic') {
const response = await getPoolData(
selectedAlgorithmAsset.chainId,
selectedAlgorithmAsset.accessDetails?.addressOrId,
selectedAlgorithmAsset?.nft.owner,
accountId || ''
)
algoPoolParams = {
tokenInLiquidity: response?.poolData?.baseTokenLiquidity,
tokenOutLiquidity: response?.poolData?.datatokenLiquidity,
tokenOutAmount: '1',
opcFee: getOpcFeeForToken(
selectedAlgorithmAsset?.accessDetails?.baseToken.address,
selectedAlgorithmAsset?.chainId
),
lpSwapFee: response?.poolData?.liquidityProviderSwapFee,
publishMarketSwapFee:
selectedAlgorithmAsset?.accessDetails?.publisherMarketOrderFee,
consumeMarketSwapFee: '0'
}
}
const algorithmOrderPriceAndFees = await getOrderPriceAndFees(
selectedAlgorithmAsset,
ZERO_ADDRESS,
algoPoolParams,
initializedProvider.algorithm.providerFee
)
if (!algorithmOrderPriceAndFees)
throw new Error('Error setting algorithm price and fees!')
setAlgoOrderPriceAndFees(algorithmOrderPriceAndFees)
}
} catch (error) {
setError(error.message)
LoggerInstance.error(`[compute] ${error.message} `)
} }
// ocean && checkAssetDTBalance(selectedAlgorithmAsset) }
}, [ddo, selectedAlgorithmAsset, accountId, hasPreviousAlgorithmOrder])
useEffect(() => {
if (!asset?.accessDetails || !accountId) return
setIsConsumablePrice(asset?.accessDetails?.isPurchasable)
setValidOrderTx(asset?.accessDetails?.validOrderTx)
}, [asset?.accessDetails, accountId])
useEffect(() => {
if (!selectedAlgorithmAsset?.accessDetails || !accountId) return
setIsRequestingAlgoOrderPrice(true)
setIsConsumableAlgorithmPrice(
selectedAlgorithmAsset?.accessDetails?.isPurchasable
)
setValidAlgorithmOrderTx(
selectedAlgorithmAsset?.accessDetails?.validOrderTx
)
setAlgoOrderPriceAndFees(null)
async function initSelectedAlgo() {
await checkAssetDTBalance(selectedAlgorithmAsset)
await initPriceAndFees()
setIsRequestingAlgoOrderPrice(false)
}
initSelectedAlgo()
}, [selectedAlgorithmAsset, accountId])
useEffect(() => {
if (!asset) return
getAlgorithmsForAsset(asset, newCancelToken()).then((algorithmsAssets) => {
setDdoAlgorithmList(algorithmsAssets)
getAlgorithmAssetSelectionList(asset, algorithmsAssets).then(
(algorithmSelectionList) => {
setAlgorithmList(algorithmSelectionList)
}
)
})
}, [asset])
// Output errors in toast UI // Output errors in toast UI
useEffect(() => { useEffect(() => {
@ -235,168 +294,131 @@ export default function Compute({
toast.error(newError) toast.error(newError)
}, [error]) }, [error])
// async function startJob(algorithmId: string) { async function startJob(): Promise<void> {
// try { try {
// if (!ocean) return setIsOrdering(true)
setIsOrdered(false)
setError(undefined)
const computeService = getServiceByName(asset, 'compute')
const computeAlgorithm: ComputeAlgorithm = {
documentId: selectedAlgorithmAsset.id,
serviceId: selectedAlgorithmAsset.services[0].id
}
const allowed = await isOrderable(
asset,
computeService.id,
computeAlgorithm,
selectedAlgorithmAsset
)
LoggerInstance.log('[compute] Is data set orderable?', allowed)
if (!allowed)
throw new Error(
'Data set is not orderable in combination with selected algorithm.'
)
// setIsJobStarting(true) setComputeStatusText(
// setIsPublished(false) getComputeFeedback(
// setError(undefined) asset.accessDetails.baseToken?.symbol,
asset.accessDetails.datatoken?.symbol,
asset.metadata.type
)[
asset.accessDetails?.type === 'fixed'
? 2
: asset.accessDetails?.type === 'dynamic'
? 1
: 3
]
)
const datasetOrderTx = await handleComputeOrder(
web3,
asset,
datasetOrderPriceAndFees,
accountId,
hasDatatoken,
initializedProviderResponse.datasets[0],
computeEnv.consumerAddress
)
if (!datasetOrderTx) throw new Error('Failed to order dataset.')
// const computeService = getServiceByName(ddo, 'compute') setComputeStatusText(
// const serviceAlgo = getServiceByName(selectedAlgorithmAsset, 'access') getComputeFeedback(
// ? getServiceByName(selectedAlgorithmAsset, 'access') selectedAlgorithmAsset.accessDetails.baseToken?.symbol,
// : getServiceByName(selectedAlgorithmAsset, 'compute') selectedAlgorithmAsset.accessDetails.datatoken?.symbol,
selectedAlgorithmAsset.metadata.type
)[
selectedAlgorithmAsset.accessDetails?.type === 'fixed'
? 2
: selectedAlgorithmAsset.accessDetails?.type === 'dynamic'
? 1
: 3
]
)
// const computeAlgorithm: ComputeAlgorithm = { const algorithmOrderTx = await handleComputeOrder(
// did: selectedAlgorithmAsset.id, web3,
// serviceIndex: serviceAlgo.index, selectedAlgorithmAsset,
// dataToken: selectedAlgorithmAsset.services[0].datatokenAddress algoOrderPriceAndFees,
// } accountId,
// const allowed = await ocean.compute.isOrderable( hasAlgoAssetDatatoken,
// ddo.id, initializedProviderResponse.algorithm,
// computeService.index, computeEnv.consumerAddress
// computeAlgorithm )
// ) if (!algorithmOrderTx) throw new Error('Failed to order algorithm.')
// LoggerInstance.log('[compute] Is data set orderable?', allowed)
// if (!allowed) { LoggerInstance.log('[compute] Starting compute job.')
// setError( const computeAsset: ComputeAsset = {
// 'Data set is not orderable in combination with selected algorithm.' documentId: asset.id,
// ) serviceId: asset.services[0].id,
// LoggerInstance.error( transferTxId: datasetOrderTx
// '[compute] Error starting compute job. Dataset is not orderable in combination with selected algorithm.' }
// ) computeAlgorithm.transferTxId = algorithmOrderTx
// return const output: ComputeOutput = {
// } publishAlgorithmLog: true,
publishOutput: true
}
setComputeStatusText(getComputeFeedback()[4])
const response = await ProviderInstance.computeStart(
asset.services[0].serviceEndpoint,
web3,
accountId,
computeEnv?.id,
computeAsset,
computeAlgorithm,
newAbortController(),
null,
output
)
if (!response) throw new Error('Error starting compute job.')
// if (!hasPreviousDatasetOrder && !hasDatatoken) { LoggerInstance.log('[compute] Starting compute job response: ', response)
// const tx = await buyDT('1', price, ddo) setIsOrdered(true)
// if (!tx) { setRefetchJobs(!refetchJobs)
// setError('Error buying datatoken.') initPriceAndFees()
// LoggerInstance.error('[compute] Error buying datatoken for data set ', ddo.id) } catch (error) {
// return setError(error.message)
// } LoggerInstance.error(`[compute] ${error.message} `)
// } } finally {
setIsOrdering(false)
// if (!hasPreviousAlgorithmOrder && !hasAlgoAssetDatatoken) { }
// const tx = await buyDT('1', algorithmPrice, selectedAlgorithmAsset) }
// if (!tx) {
// setError('Error buying datatoken.')
// LoggerInstance.error(
// '[compute] Error buying datatoken for algorithm ',
// selectedAlgorithmAsset.id
// )
// return
// }
// }
// // TODO: pricingError is always undefined even upon errors during buyDT for whatever reason.
// // So manually drop out above, but ideally could be replaced with this alone.
// if (pricingError) {
// setError(pricingError)
// return
// }
// const assetOrderId = hasPreviousDatasetOrder
// ? previousDatasetOrderId
// : await ocean.compute.orderAsset(
// accountId,
// ddo.id,
// computeService.index,
// computeAlgorithm,
// appConfig.marketFeeAddress,
// undefined,
// null,
// false
// )
// assetOrderId &&
// LoggerInstance.log(
// `[compute] Got ${
// hasPreviousDatasetOrder ? 'existing' : 'new'
// } order ID for dataset: `,
// assetOrderId
// )
// const algorithmAssetOrderId = hasPreviousAlgorithmOrder
// ? previousAlgorithmOrderId
// : await ocean.compute.orderAlgorithm(
// algorithmId,
// serviceAlgo.type,
// accountId,
// serviceAlgo.index,
// appConfig.marketFeeAddress,
// undefined,
// null,
// false
// )
// algorithmAssetOrderId &&
// LoggerInstance.log(
// `[compute] Got ${
// hasPreviousAlgorithmOrder ? 'existing' : 'new'
// } order ID for algorithm: `,
// algorithmAssetOrderId
// )
// if (!assetOrderId || !algorithmAssetOrderId) {
// setError('Error ordering assets.')
// return
// }
// computeAlgorithm.transferTxId = algorithmAssetOrderId
// LoggerInstance.log('[compute] Starting compute job.')
// const output: ComputeOutput = {
// publishAlgorithmLog: true,
// publishOutput: true
// }
// const response = await ocean.compute.start(
// ddo.id,
// assetOrderId,
// ddo.services[0].datatokenAddress,
// account,
// computeAlgorithm,
// output,
// `${computeService.index}`,
// computeService.type
// )
// if (!response) {
// setError('Error starting compute job.')
// return
// }
// LoggerInstance.log('[compute] Starting compute job response: ', response)
// await checkPreviousOrders(selectedAlgorithmAsset)
// await checkPreviousOrders(ddo)
// setIsPublished(true)
// } catch (error) {
// await checkPreviousOrders(selectedAlgorithmAsset)
// await checkPreviousOrders(ddo)
// setError('Failed to start job!')
// LoggerInstance.error('[compute] Failed to start job: ', error.message)
// } finally {
// setIsJobStarting(false)
// }
// }
return ( return (
<> <>
<div className={styles.info}> <div className={styles.info}>
<FileIcon file={file} isLoading={fileIsLoading} small /> <FileIcon file={file} isLoading={fileIsLoading} small />
<Price accessDetails={accessDetails} conversion size="large" /> <Price accessDetails={asset?.accessDetails} conversion />
</div> </div>
{ddo.metadata.type === 'algorithm' ? ( {asset.metadata.type === 'algorithm' ? (
<> <>
<Alert <Alert
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!" 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" state="info"
/> />
<AlgorithmDatasetsListForCompute algorithmDid={ddo.id} asset={ddo} /> <AlgorithmDatasetsListForCompute
algorithmDid={asset.id}
asset={asset}
/>
</> </>
) : ( ) : (
<Formik <Formik
@ -404,48 +426,64 @@ export default function Compute({
validateOnMount validateOnMount
validationSchema={validationSchema} validationSchema={validationSchema}
onSubmit={async (values) => { onSubmit={async (values) => {
// await startJob(values.algorithm) if (!values.algorithm) return
await startJob()
}} }}
> >
<FormStartComputeDataset <FormStartComputeDataset
algorithms={algorithmList} algorithms={algorithmList}
ddoListAlgorithms={ddoAlgorithmList} ddoListAlgorithms={ddoAlgorithmList}
selectedAlgorithmAsset={selectedAlgorithmAsset}
setSelectedAlgorithm={setSelectedAlgorithmAsset} setSelectedAlgorithm={setSelectedAlgorithmAsset}
isLoading={isJobStarting} isLoading={isOrdering || isRequestingAlgoOrderPrice}
isComputeButtonDisabled={isComputeButtonDisabled} isComputeButtonDisabled={isComputeButtonDisabled}
hasPreviousOrder={hasPreviousDatasetOrder} hasPreviousOrder={validOrderTx !== undefined}
hasDatatoken={hasDatatoken} hasDatatoken={hasDatatoken}
dtBalance={dtBalance} dtBalance={dtBalance}
datasetLowPoolLiquidity={!isConsumablePrice} datasetLowPoolLiquidity={!isConsumablePrice}
assetType={ddo?.metadata.type} assetType={asset?.metadata.type}
assetTimeout={datasetTimeout} assetTimeout={secondsToString(asset?.services[0].timeout)}
hasPreviousOrderSelectedComputeAsset={hasPreviousAlgorithmOrder} hasPreviousOrderSelectedComputeAsset={
validAlgorithmOrderTx !== undefined
}
hasDatatokenSelectedComputeAsset={hasAlgoAssetDatatoken} hasDatatokenSelectedComputeAsset={hasAlgoAssetDatatoken}
oceanSymbol={accessDetails?.baseToken?.symbol || ''} oceanSymbol={
asset?.accessDetails?.baseToken?.symbol ||
selectedAlgorithmAsset?.accessDetails?.baseToken?.symbol ||
'OCEAN'
}
dtSymbolSelectedComputeAsset={ dtSymbolSelectedComputeAsset={
selectedAlgorithmAsset?.datatokens[0]?.symbol selectedAlgorithmAsset?.datatokens[0]?.symbol
} }
dtBalanceSelectedComputeAsset={algorithmDTBalance} dtBalanceSelectedComputeAsset={algorithmDTBalance}
selectedComputeAssetLowPoolLiquidity={!isAlgoConsumablePrice}
selectedComputeAssetType="algorithm" selectedComputeAssetType="algorithm"
selectedComputeAssetTimeout={algorithmTimeout} selectedComputeAssetTimeout={secondsToString(
selectedAlgorithmAsset?.services[0]?.timeout
)}
// lazy comment when removing pricingStepText // lazy comment when removing pricingStepText
stepText={'pricingStepText' || 'Starting Compute Job...'} stepText={computeStatusText}
algorithmConsumeDetails={algorithmConsumeDetails} isConsumable={isConsumablePrice}
isConsumable={isConsumable}
consumableFeedback={consumableFeedback} consumableFeedback={consumableFeedback}
datasetOrderPriceAndFees={datasetOrderPriceAndFees}
algoOrderPriceAndFees={algoOrderPriceAndFees}
providerFeeAmount={providerFeeAmount}
validUntil={computeValidUntil}
/> />
</Formik> </Formik>
)} )}
<footer className={styles.feedback}> <footer className={styles.feedback}>
{isPublished && ( {isOrdered && (
<SuccessConfetti success="Your job started successfully! Watch the progress below or on your profile." /> <SuccessConfetti success="Your job started successfully! Watch the progress below or on your profile." />
)} )}
</footer> </footer>
{accountId && accessDetails?.datatoken && ( {accountId && asset?.accessDetails?.datatoken && (
<AssetActionHistoryTable title="Your Compute Jobs"> <AssetActionHistoryTable title="Your Compute Jobs">
<ComputeJobs minimal /> <ComputeJobs
minimal
assetChainIds={[asset?.chainId]}
refetchJobs={refetchJobs}
/>
</AssetActionHistoryTable> </AssetActionHistoryTable>
)} )}
</> </>

View File

@ -113,8 +113,7 @@ export default function AssetActions({
const UseContent = isCompute ? ( const UseContent = isCompute ? (
<Compute <Compute
ddo={asset} asset={asset}
accessDetails={asset?.accessDetails}
dtBalance={dtBalance} dtBalance={dtBalance}
file={fileMetadata} file={fileMetadata}
fileIsLoading={fileIsLoading} fileIsLoading={fileIsLoading}

View File

@ -3,12 +3,12 @@ import { useAsset } from '@context/Asset'
import ExplorerLink from '@shared/ExplorerLink' import ExplorerLink from '@shared/ExplorerLink'
import Time from '@shared/atoms/Time' import Time from '@shared/atoms/Time'
import { gql, OperationContext, useQuery } from 'urql' import { gql, OperationContext, useQuery } from 'urql'
import { ReceiptData_nftUpdates as ReceiptData } from '../../../@types/subgraph/ReceiptData' import { NftUpdate_nftUpdates as NftUpdate } from '../../../@types/subgraph/NftUpdate'
import { getQueryContext } from '@utils/subgraph' import { getQueryContext } from '@utils/subgraph'
import styles from './EditHistory.module.css' import styles from './EditHistory.module.css'
const getReceipts = gql` const getReceipts = gql`
query ReceiptData($address: String!) { query NftUpdate($address: String!) {
nftUpdates( nftUpdates(
where: { nft: $address } where: { nft: $address }
orderBy: timestamp orderBy: timestamp
@ -30,8 +30,8 @@ export default function EditHistory({
receipts, receipts,
setReceipts setReceipts
}: { }: {
receipts: ReceiptData[] receipts: NftUpdate[]
setReceipts: (receipts: ReceiptData[]) => void setReceipts: (receipts: NftUpdate[]) => void
}): ReactElement { }): ReactElement {
const { asset } = useAsset() const { asset } = useAsset()

View File

@ -35,20 +35,7 @@
text-align: center; text-align: center;
margin-top: var(--spacer); margin-top: var(--spacer);
margin-bottom: calc(var(--spacer) * 1.5); margin-bottom: calc(var(--spacer) * 1.5);
margin-left: -2rem; padding: calc(var(--spacer) / 3) var(--spacer);
margin-right: -2rem;
padding: calc(var(--spacer) / 4) var(--spacer);
border-top: 1px solid var(--border-color); border-top: 1px solid var(--border-color);
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
} }
.ownerActions a,
.ownerActions button {
color: var(--color-secondary);
margin-left: calc(var(--spacer) / 4);
margin-right: calc(var(--spacer) / 4);
}
.separator {
color: var(--color-secondary);
}

View File

@ -15,17 +15,15 @@ import styles from './index.module.css'
import NetworkName from '@shared/NetworkName' import NetworkName from '@shared/NetworkName'
import content from '../../../../content/purgatory.json' import content from '../../../../content/purgatory.json'
import { AssetExtended } from 'src/@types/AssetExtended' import { AssetExtended } from 'src/@types/AssetExtended'
import { useWeb3 } from '@context/Web3'
import Web3 from 'web3' import Web3 from 'web3'
import Button from '@shared/atoms/Button'
export default function AssetContent({ export default function AssetContent({
asset asset
}: { }: {
asset: AssetExtended asset: AssetExtended
}): ReactElement { }): ReactElement {
const [isOwner, setIsOwner] = useState(false) const { isInPurgatory, purgatoryData, isOwner, isAssetNetwork } = useAsset()
const { accountId } = useWeb3()
const { isInPurgatory, purgatoryData, owner, isAssetNetwork } = useAsset()
const { debug } = useUserPreferences() const { debug } = useUserPreferences()
const [receipts, setReceipts] = useState([]) const [receipts, setReceipts] = useState([])
const [nftPublisher, setNftPublisher] = useState<string>() const [nftPublisher, setNftPublisher] = useState<string>()
@ -38,13 +36,6 @@ export default function AssetContent({
) )
}, [receipts]) }, [receipts])
useEffect(() => {
if (!accountId || !owner) return
const isOwner = accountId.toLowerCase() === owner.toLowerCase()
setIsOwner(isOwner)
}, [accountId, owner, asset])
return ( return (
<> <>
<div className={styles.networkWrap}> <div className={styles.networkWrap}>
@ -84,9 +75,9 @@ export default function AssetContent({
<AssetActions asset={asset} /> <AssetActions asset={asset} />
{isOwner && isAssetNetwork && ( {isOwner && isAssetNetwork && (
<div className={styles.ownerActions}> <div className={styles.ownerActions}>
<Link href={`/asset/${asset?.id}/edit`}> <Button style="text" size="small" to={`/asset/${asset?.id}/edit`}>
<a>Edit</a> Edit Asset
</Link> </Button>
</div> </div>
)} )}
</div> </div>

View File

@ -1,5 +1,4 @@
import React, { ReactElement, useState, useEffect } from 'react' import React, { ReactElement, useState, useEffect } from 'react'
import { LoggerInstance } from '@oceanprotocol/lib'
import { useAsset } from '@context/Asset' import { useAsset } from '@context/Asset'
import styles from './index.module.css' import styles from './index.module.css'
import Tabs from '@shared/atoms/Tabs' import Tabs from '@shared/atoms/Tabs'
@ -33,7 +32,7 @@ export default function Edit({ uri }: { uri: string }): ReactElement {
{ {
title: 'Edit Compute Settings', title: 'Edit Compute Settings',
content: <EditComputeDataset asset={asset} />, content: <EditComputeDataset asset={asset} />,
disabled: !isCompute disabled: !isCompute || asset?.metadata?.type === 'algorithm'
} }
].filter((tab) => tab !== undefined) ].filter((tab) => tab !== undefined)

View File

@ -62,6 +62,20 @@
.assetMeta code { .assetMeta code {
color: var(--color-secondary); color: var(--color-secondary);
font-size: var(--font-size-small); font-size: var(--font-size-small);
overflow: hidden;
width: 100%;
flex: 6;
text-overflow: ellipsis;
white-space: nowrap;
}
.assetMeta span {
flex: 1;
}
.assetMeta {
display: flex;
flex-wrap: wrap;
} }
.meta { .meta {

View File

@ -33,7 +33,8 @@ function Asset({
</a> </a>
</h3> </h3>
<p className={styles.assetMeta}> <p className={styles.assetMeta}>
{symbol} | <code>{did}</code> <span className={styles.assetMeta}> {`${symbol} | `}</span>
<code className={styles.assetMeta}>{did}</code>
</p> </p>
</div> </div>
) )
@ -41,9 +42,11 @@ function Asset({
function DetailsAssets({ job }: { job: ComputeJobMetaData }) { function DetailsAssets({ job }: { job: ComputeJobMetaData }) {
const { appConfig } = useMarketMetadata() const { appConfig } = useMarketMetadata()
const newCancelToken = useCancelToken()
const [algoName, setAlgoName] = useState<string>() const [algoName, setAlgoName] = useState<string>()
const [algoDtSymbol, setAlgoDtSymbol] = useState<string>() const [algoDtSymbol, setAlgoDtSymbol] = useState<string>()
const newCancelToken = useCancelToken()
useEffect(() => { useEffect(() => {
async function getAlgoMetadata() { async function getAlgoMetadata() {
const ddo = await retrieveAsset(job.algoDID, newCancelToken()) const ddo = await retrieveAsset(job.algoDID, newCancelToken())
@ -51,7 +54,7 @@ function DetailsAssets({ job }: { job: ComputeJobMetaData }) {
setAlgoName(ddo?.metadata.name) setAlgoName(ddo?.metadata.name)
} }
getAlgoMetadata() getAlgoMetadata()
}, [appConfig.metadataCacheUri, job.algoDID]) }, [appConfig.metadataCacheUri, job.algoDID, newCancelToken])
return ( return (
<> <>
@ -97,12 +100,6 @@ export default function Details({
/> />
)} )}
<MetaItem title="Job ID" content={<code>{job.jobId}</code>} /> <MetaItem title="Job ID" content={<code>{job.jobId}</code>} />
{/* {job.resultsDid && (
<MetaItem
title="Published Results DID"
content={<code>{job.resultsDid}</code>}
/>
)} */}
</div> </div>
</Modal> </Modal>
</> </>

View File

@ -3,6 +3,12 @@
border-bottom-left-radius: var(--border-radius) !important; border-bottom-left-radius: var(--border-radius) !important;
} }
.title {
font-size: var(--font-size-base);
color: var(--font-color-text);
margin-bottom: calc(var(--spacer) / 3);
}
.help { .help {
margin-top: calc(var(--spacer) / 3); margin-top: calc(var(--spacer) / 3);
} }

View File

@ -1,90 +1,108 @@
import { LoggerInstance } from '@oceanprotocol/lib' import {
import React, { ReactElement, useState } from 'react' ComputeResultType,
import Loader from '@shared/atoms/Loader' downloadFileBrowser,
LoggerInstance,
Provider
} from '@oceanprotocol/lib'
import React, { ReactElement, useEffect, useState } from 'react'
import { ListItem } from '@shared/atoms/Lists' import { ListItem } from '@shared/atoms/Lists'
import Button from '@shared/atoms/Button' import Button from '@shared/atoms/Button'
import styles from './Results.module.css' import styles from './Results.module.css'
import FormHelp from '@shared/FormInput/Help' import FormHelp from '@shared/FormInput/Help'
import content from '../../../../../content/pages/history.json' import content from '../../../../../content/pages/history.json'
import { useWeb3 } from '@context/Web3' import { useWeb3 } from '@context/Web3'
import { useCancelToken } from '@hooks/useCancelToken'
import { retrieveAsset } from '@utils/aquarius'
export default function Results({ export default function Results({
job job
}: { }: {
job: ComputeJobMetaData job: ComputeJobMetaData
}): ReactElement { }): ReactElement {
const { accountId } = useWeb3() const providerInstance = new Provider()
const { accountId, web3 } = useWeb3()
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const [hasFetched, setHasFetched] = useState(false)
const isFinished = job.dateFinished !== null const isFinished = job.dateFinished !== null
async function getResults() { const [datasetProvider, setDatasetProvider] = useState<string>()
const newCancelToken = useCancelToken()
useEffect(() => {
async function getAssetMetadata() {
const ddo = await retrieveAsset(job.inputDID[0], newCancelToken())
setDatasetProvider(ddo.services[0].serviceEndpoint)
}
getAssetMetadata()
}, [job.inputDID, newCancelToken])
function getDownloadButtonValue(type: ComputeResultType): string {
let buttonName
switch (type) {
case 'output':
buttonName = 'results'
break
case 'algorithmLog':
buttonName = 'algorithm logs'
break
case 'configrationLog':
buttonName = 'configuration logs'
break
case 'publishLog':
buttonName = 'publish logs'
break
default:
buttonName = 'results'
break
}
return buttonName
}
async function downloadResults(resultIndex: number) {
if (!accountId || !job) return if (!accountId || !job) return
try { try {
setIsLoading(true) setIsLoading(true)
// const jobStatus = await ocean.compute.status( const jobResult = await providerInstance.getComputeResultUrl(
// account, datasetProvider,
// job.did, web3,
// undefined, accountId,
// undefined, job.jobId,
// job.jobId resultIndex
// ) )
// if (jobStatus?.length > 0) { await downloadFileBrowser(jobResult)
// job.algorithmLogUrl = jobStatus[0].algorithmLogUrl
// job.resultsUrl = jobStatus[0].resultsUrl
// }
} catch (error) { } catch (error) {
LoggerInstance.error(error.message) LoggerInstance.error(error.message)
} finally { } finally {
setIsLoading(false) setIsLoading(false)
setHasFetched(true)
} }
} }
return ( return (
<div className={styles.results}> <div className={styles.results}>
{hasFetched ? ( <h4 className={styles.title}>Results</h4>
{isFinished ? (
<ul> <ul>
<ListItem> {job.results &&
{/* {job.algorithmLogUrl ? ( Array.isArray(job.results) &&
<a href={job.algorithmLogUrl} target="_blank" rel="noreferrer"> job.results.map((jobResult, i) =>
View Log jobResult.filename ? (
</a> <ListItem key={i}>
) : ( <Button
'No logs found.' style="text"
)} */} size="small"
</ListItem> onClick={() => downloadResults(i)}
download
{/* {job.resultsUrl && >
Array.isArray(job.resultsUrl) && {getDownloadButtonValue(jobResult.type)}
job.resultsUrl.map((url, i) => </Button>
url ? (
<ListItem key={job.jobId}>
<a href={url} target="_blank" rel="noreferrer">
View Result {i + 1}
</a>
</ListItem> </ListItem>
) : ( ) : (
<ListItem>No results found.</ListItem> <ListItem>No results found.</ListItem>
) )
)} */} )}
</ul> </ul>
) : ( ) : (
<Button <p> Waiting for results...</p>
style="primary"
size="small"
onClick={() => getResults()}
disabled={isLoading || !isFinished}
>
{isLoading ? (
<Loader />
) : !isFinished ? (
'Waiting for results...'
) : (
'Get Results'
)}
</Button>
)} )}
<FormHelp className={styles.help}>{content.compute.storage}</FormHelp> <FormHelp className={styles.help}>{content.compute.storage}</FormHelp>
</div> </div>

View File

@ -1,8 +1,6 @@
import React, { ReactElement, useEffect, useState, useCallback } from 'react' import React, { ReactElement, useEffect, useState, useCallback } from 'react'
import Time from '@shared/atoms/Time' import Time from '@shared/atoms/Time'
import Link from 'next/link'
import { LoggerInstance } from '@oceanprotocol/lib' import { LoggerInstance } from '@oceanprotocol/lib'
import Dotdotdot from 'react-dotdotdot'
import Table from '@shared/atoms/Table' import Table from '@shared/atoms/Table'
import Button from '@shared/atoms/Button' import Button from '@shared/atoms/Button'
import { useWeb3 } from '@context/Web3' import { useWeb3 } from '@context/Web3'
@ -10,10 +8,12 @@ import Details from './Details'
import Refresh from '@images/refresh.svg' import Refresh from '@images/refresh.svg'
import { useUserPreferences } from '@context/UserPreferences' import { useUserPreferences } from '@context/UserPreferences'
import NetworkName from '@shared/NetworkName' import NetworkName from '@shared/NetworkName'
// import { getComputeJobs } from '@utils/compute' import { getComputeJobs } from '@utils/compute'
import styles from './index.module.css' import styles from './index.module.css'
import { useAsset } from '@context/Asset' import { useAsset } from '@context/Asset'
import { useIsMounted } from '@hooks/useIsMounted' import { useIsMounted } from '@hooks/useIsMounted'
import { useCancelToken } from '@hooks/useCancelToken'
import AssetListTitle from '@shared/AssetList/AssetListTitle'
export function Status({ children }: { children: string }): ReactElement { export function Status({ children }: { children: string }): ReactElement {
return <div className={styles.status}>{children}</div> return <div className={styles.status}>{children}</div>
@ -23,13 +23,7 @@ const columns = [
{ {
name: 'Data Set', name: 'Data Set',
selector: function getAssetRow(row: ComputeJobMetaData) { selector: function getAssetRow(row: ComputeJobMetaData) {
return ( return <AssetListTitle did={row.inputDID[0]} title={row.assetName} />
<Dotdotdot clamp={2}>
<Link href={`/asset/${row.inputDID[0]}`}>
<a>{row.assetName}</a>
</Link>
</Dotdotdot>
)
} }
}, },
{ {
@ -69,18 +63,23 @@ const columns = [
] ]
export default function ComputeJobs({ export default function ComputeJobs({
minimal minimal,
assetChainIds,
refetchJobs
}: { }: {
minimal?: boolean minimal?: boolean
assetChainIds?: number[]
refetchJobs?: boolean
}): ReactElement { }): ReactElement {
const { accountId, networkId } = useWeb3() const { accountId } = useWeb3()
const { asset } = useAsset() const { asset } = useAsset()
const { chainIds } = useUserPreferences() const { chainIds } = useUserPreferences()
const isMounted = useIsMounted()
const newCancelToken = useCancelToken()
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const [jobs, setJobs] = useState<ComputeJobMetaData[]>([]) const [jobs, setJobs] = useState<ComputeJobMetaData[]>([])
const isMounted = useIsMounted() const [columnsMinimal] = useState([columns[4], columns[5], columns[3]])
const columnsMinimal = [columns[4], columns[5], columns[3]]
const fetchJobs = useCallback(async () => { const fetchJobs = useCallback(async () => {
if (!chainIds || chainIds.length === 0 || !accountId) { if (!chainIds || chainIds.length === 0 || !accountId) {
@ -90,17 +89,22 @@ export default function ComputeJobs({
} }
try { try {
setIsLoading(true) setIsLoading(true)
// const jobs = await getComputeJobs(chainIds, accountId, ddo) const jobs = await getComputeJobs(
// isMounted() && setJobs(jobs.computeJobs) assetChainIds || chainIds,
// setIsLoading(jobs.isLoaded) accountId,
asset,
newCancelToken()
)
isMounted() && setJobs(jobs.computeJobs)
setIsLoading(!jobs.isLoaded)
} catch (error) { } catch (error) {
LoggerInstance.error(error.message) LoggerInstance.error(error.message)
} }
}, [chainIds, accountId, asset, isMounted]) }, [chainIds, accountId, asset, isMounted, assetChainIds, newCancelToken])
useEffect(() => { useEffect(() => {
fetchJobs() fetchJobs()
}, [fetchJobs]) }, [fetchJobs, refetchJobs])
return accountId ? ( return accountId ? (
<> <>

View File

@ -41,8 +41,8 @@ export const wizardSteps: StepContent[] = [
const computeOptions: ServiceComputeOptions = { const computeOptions: ServiceComputeOptions = {
allowRawAlgorithm: false, allowRawAlgorithm: false,
allowNetworkAccess: true, allowNetworkAccess: true,
publisherTrustedAlgorithmPublishers: null, publisherTrustedAlgorithmPublishers: [],
publisherTrustedAlgorithms: null publisherTrustedAlgorithms: []
} }
export const initialValues: FormPublishData = { export const initialValues: FormPublishData = {

View File

@ -135,7 +135,8 @@ export async function transformPublishFormToDdo(
: getAlgorithmContainerPreset(dockerImage).tag, : getAlgorithmContainerPreset(dockerImage).tag,
checksum: checksum:
dockerImage === 'custom' dockerImage === 'custom'
? dockerImageCustomChecksum ? // ? dockerImageCustomChecksum
''
: getAlgorithmContainerPreset(dockerImage).checksum : getAlgorithmContainerPreset(dockerImage).checksum
} }
} }