From 5387b9a3dd078739e3fb5be9930a1ea954c10ab7 Mon Sep 17 00:00:00 2001 From: Bogdan Fazakas Date: Thu, 23 Jun 2022 18:53:05 +0300 Subject: [PATCH] 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 * remove comment Signed-off-by: mihaisc * Fix previous order tx (#1197) * rename nft update query Signed-off-by: mihaisc * fix previous order Signed-off-by: mihaisc * 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 * 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 * fix lint Signed-off-by: mihaisc Co-authored-by: mihaisc * 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 * 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 Co-authored-by: Soon Huat Co-authored-by: Soon Huat Co-authored-by: Enzo Vezzaro Co-authored-by: Matthias Kretschmann Co-authored-by: mihaisc --- src/@context/Asset.tsx | 13 + src/@types/Compute.d.ts | 8 +- src/@utils/accessDetailsAndPricing.ts | 79 +- src/@utils/aquarius.ts | 23 +- src/@utils/assetConvertor.ts | 4 +- src/@utils/compute.ts | 462 +++++++----- src/@utils/ddo.ts | 7 + src/@utils/feedback.ts | 17 +- src/@utils/fixedRateExchange.ts | 4 +- src/@utils/nft.ts | 15 - src/@utils/order.ts | 211 +++++- src/@utils/provider.ts | 43 ++ src/@utils/subgraph.ts | 2 +- .../@shared/ButtonBuy/index.module.css | 5 + src/components/@shared/ButtonBuy/index.tsx | 31 +- .../@shared/atoms/Blockies/index.tsx | 1 + .../AlgorithmDatasetsListForCompute.tsx | 5 +- .../Compute/FormComputeDataset.tsx | 124 ++- .../Compute/PriceOutput.module.css | 13 +- .../AssetActions/Compute/PriceOutput.tsx | 42 +- .../Asset/AssetActions/Compute/index.tsx | 710 +++++++++--------- src/components/Asset/AssetActions/index.tsx | 3 +- .../Asset/AssetContent/EditHistory.tsx | 8 +- .../Asset/AssetContent/index.module.css | 15 +- src/components/Asset/AssetContent/index.tsx | 19 +- src/components/Asset/Edit/index.tsx | 3 +- .../History/ComputeJobs/Details.module.css | 14 + .../Profile/History/ComputeJobs/Details.tsx | 15 +- .../History/ComputeJobs/Results.module.css | 6 + .../Profile/History/ComputeJobs/Results.tsx | 122 +-- .../Profile/History/ComputeJobs/index.tsx | 44 +- src/components/Publish/_constants.tsx | 4 +- src/components/Publish/_utils.ts | 3 +- 33 files changed, 1297 insertions(+), 778 deletions(-) diff --git a/src/@context/Asset.tsx b/src/@context/Asset.tsx index abaed455f..a554e528e 100644 --- a/src/@context/Asset.tsx +++ b/src/@context/Asset.tsx @@ -27,6 +27,7 @@ export interface AssetProviderValue { error?: string isAssetNetwork: boolean isV3Asset: boolean + isOwner: boolean oceanConfig: Config loading: boolean fetchAsset: (token?: CancelToken) => Promise @@ -49,6 +50,7 @@ function AssetProvider({ const [asset, setAsset] = useState() const [title, setTitle] = useState() const [owner, setOwner] = useState() + const [isOwner, setIsOwner] = useState() const [error, setError] = useState() const [loading, setLoading] = useState(false) const [isAssetNetwork, setIsAssetNetwork] = useState() @@ -140,6 +142,16 @@ function AssetProvider({ setIsAssetNetwork(isAssetNetwork) }, [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 // ----------------------------------- @@ -172,6 +184,7 @@ function AssetProvider({ fetchAsset, isAssetNetwork, isV3Asset, + isOwner, oceanConfig } as AssetProviderValue } diff --git a/src/@types/Compute.d.ts b/src/@types/Compute.d.ts index 93df8670f..c318cf398 100644 --- a/src/@types/Compute.d.ts +++ b/src/@types/Compute.d.ts @@ -1,5 +1,5 @@ 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 // ambiant types despite the above imports @@ -22,10 +22,10 @@ declare global { interface TokenOrder { id: string - serviceId: number - datatokenId: OrdersDatatoken + serviceIndex: number + datatoken: OrdersDatatoken tx: any - timestamp: number + createdTimestamp: number } interface ComputeResults { diff --git a/src/@utils/accessDetailsAndPricing.ts b/src/@utils/accessDetailsAndPricing.ts index 1d718d6be..612c77978 100644 --- a/src/@utils/accessDetailsAndPricing.ts +++ b/src/@utils/accessDetailsAndPricing.ts @@ -8,13 +8,19 @@ import { TokensPriceQuery, TokensPriceQuery_tokens as TokensPrice } 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 { calcInGivenOut } from './pool' import { getFixedBuyPrice } from './fixedRateExchange' import { AccessDetails, OrderPriceAndFees } from 'src/@types/Price' import Decimal from 'decimal.js' import { consumeMarketOrderFee } from '../../app.config' +import Web3 from 'web3' const tokensPriceQuery = gql` query TokensPriceQuery($datatokenIds: [ID!], $account: String) { @@ -26,13 +32,20 @@ const tokensPriceQuery = gql` publishMarketFeeToken publishMarketFeeAmount orders( - where: { consumer: $account } + where: { payer: $account } orderBy: createdTimestamp orderDirection: desc ) { tx serviceIndex createdTimestamp + reuses(orderBy: createdTimestamp, orderDirection: desc) { + id + caller + createdTimestamp + tx + block + } } dispensers { id @@ -91,13 +104,20 @@ const tokenPriceQuery = gql` publishMarketFeeToken publishMarketFeeAmount orders( - where: { consumer: $account } + where: { payer: $account } orderBy: createdTimestamp orderDirection: desc ) { tx serviceIndex createdTimestamp + reuses(orderBy: createdTimestamp, orderDirection: desc) { + id + caller + createdTimestamp + tx + block + } } dispensers { id @@ -152,19 +172,22 @@ function getAccessDetailsFromTokenPrice( timeout?: number ): AccessDetails { const accessDetails = {} as AccessDetails - if (tokenPrice && tokenPrice.orders && tokenPrice.orders.length > 0) { + + if (tokenPrice?.orders?.length > 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 accessDetails.isOwned = - timeout === 0 || Date.now() / 1000 - order.createdTimestamp < timeout - accessDetails.validOrderTx = order.tx + timeout === 0 || Date.now() / 1000 - order?.createdTimestamp < timeout + // 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 - accessDetails.publisherMarketOrderFee = tokenPrice.publishMarketFeeAmount + accessDetails.publisherMarketOrderFee = tokenPrice?.publishMarketFeeAmount // free is always the best price - if (tokenPrice.dispensers && tokenPrice.dispensers.length > 0) { + if (tokenPrice?.dispensers?.length > 0) { const dispenser = tokenPrice.dispensers[0] accessDetails.type = 'free' accessDetails.addressOrId = dispenser.token.id @@ -179,10 +202,7 @@ function getAccessDetailsFromTokenPrice( } // checking for fixed price - if ( - tokenPrice.fixedRateExchanges && - tokenPrice.fixedRateExchanges.length > 0 - ) { + if (tokenPrice?.fixedRateExchanges?.length > 0) { const fixed = tokenPrice.fixedRateExchanges[0] accessDetails.type = 'fixed' accessDetails.addressOrId = fixed.exchangeId @@ -203,7 +223,7 @@ function getAccessDetailsFromTokenPrice( } // checking for pools - if (tokenPrice.pools && tokenPrice.pools.length > 0) { + if (tokenPrice?.pools?.length > 0) { const pool = tokenPrice.pools[0] accessDetails.type = 'dynamic' 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 * @return {Promise} */ export async function getOrderPriceAndFees( asset: AssetExtended, - accountId: string, - paramsForPool: CalcInGivenOutParams + accountId?: string, + paramsForPool?: CalcInGivenOutParams, + providerFees?: ProviderFees ): Promise { const orderPriceAndFee = { price: '0', @@ -252,14 +273,17 @@ export async function getOrderPriceAndFees( } as OrderPriceAndFees // fetch provider fee - const initializeData = await ProviderInstance.initialize( - asset?.id, - asset.services[0].id, - 0, - accountId, - asset?.services[0].serviceEndpoint - ) - orderPriceAndFee.providerFee = initializeData.providerFee + + const initializeData = + !providerFees && + (await ProviderInstance.initialize( + asset?.id, + asset?.services[0].id, + 0, + accountId, + asset?.services[0].serviceEndpoint + )) + orderPriceAndFee.providerFee = providerFees || initializeData.providerFee // fetch price and swap fees 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 - orderPriceAndFee.price = new Decimal(orderPriceAndFee.price) - .add(new Decimal(orderPriceAndFee.consumeMarketOrderFee)) - .add(new Decimal(orderPriceAndFee.publisherMarketOrderFee)) - .add(new Decimal(orderPriceAndFee.providerFee.providerFeeAmount)) + orderPriceAndFee.price = new Decimal(+orderPriceAndFee.price || 0) + .add(new Decimal(+orderPriceAndFee?.consumeMarketOrderFee || 0)) + .add(new Decimal(+orderPriceAndFee?.publisherMarketOrderFee || 0)) .toString() return orderPriceAndFee } diff --git a/src/@utils/aquarius.ts b/src/@utils/aquarius.ts index 4a9319c20..8eb6208c9 100644 --- a/src/@utils/aquarius.ts +++ b/src/@utils/aquarius.ts @@ -11,6 +11,12 @@ import { transformAssetToAssetSelection } from './assetConvertor' 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 value the value of the filter @@ -174,7 +180,7 @@ export async function getAssetsFromDidList( didList: string[], chainIds: number[], cancelToken: CancelToken -): Promise { +): Promise { try { if (!(didList.length > 0)) return @@ -249,12 +255,15 @@ export async function getAlgorithmDatasetsForCompute( ): Promise { const baseQueryParams = { chainIds: [datasetChainId], - filters: [ - getFilterTerm( - 'service.compite.publisherTrustedAlgorithms.did', - algorithmId - ) - ], + nestedQuery: { + must: { + match: { + 'services.compute.publisherTrustedAlgorithms.did': { + query: escapeEsReservedCharacters(algorithmId) + } + } + } + }, sortOptions: { sortBy: SortTermOptions.Created, sortDirection: SortDirectionOptions.Descending diff --git a/src/@utils/assetConvertor.ts b/src/@utils/assetConvertor.ts index 0528bcdcf..de12c9fa3 100644 --- a/src/@utils/assetConvertor.ts +++ b/src/@utils/assetConvertor.ts @@ -18,7 +18,7 @@ export async function transformAssetToAssetSelection( const algoComputeService = getServiceByName(asset, 'compute') if ( - asset?.accessDetails.price && + asset?.accessDetails?.price && algoComputeService?.serviceEndpoint === datasetProviderEndpoint ) { let selected = false @@ -29,7 +29,7 @@ export async function transformAssetToAssetSelection( }) const algorithmAsset: AssetSelectionAsset = { did: asset.id, - name: asset.datatokens[0].name, + name: asset.metadata.name, price: asset.accessDetails.price, checked: selected, symbol: asset.datatokens[0].symbol diff --git a/src/@utils/compute.ts b/src/@utils/compute.ts index 6ce0f9a1b..7f7cfd350 100644 --- a/src/@utils/compute.ts +++ b/src/@utils/compute.ts @@ -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 { Asset, ServiceComputeOptions, PublisherTrustedAlgorithm, - getHash + getHash, + LoggerInstance, + ComputeAlgorithm, + DDO, + Service, + ProviderInstance, + ComputeEnvironment, + ComputeJob } from '@oceanprotocol/lib' import { CancelToken } from 'axios' import { gql } from 'urql' @@ -24,6 +20,11 @@ import { retrieveDDOListByDIDs } from './aquarius' 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` query ComputeOrders($user: String!) { @@ -72,201 +73,256 @@ async function getAssetMetadata( const baseQueryparams = { chainIds, filters: [ - getFilterTerm('dataToken', queryDtList), - getFilterTerm('service.type', 'compute'), - getFilterTerm('service.attributes.main.type', 'dataset') + getFilterTerm('services.datatokenAddress', queryDtList), + getFilterTerm('services.type', 'compute'), + getFilterTerm('metadata.type', 'dataset') ], ignorePurgatory: true } as BaseQueryParams const query = generateBaseQuery(baseQueryparams) const result = await queryMetadata(query, cancelToken) - - return result.results + return result?.results } -function getServiceEndpoints(data: TokenOrder[], assets: Asset[]): string[] { - // const serviceEndpoints: string[] = [] +export async function isOrderable( + asset: Asset | DDO, + serviceId: string, + algorithm: ComputeAlgorithm, + algorithmDDO: Asset | DDO +): Promise { + const datasetService: Service = getServiceById(asset, serviceId) + if (!datasetService) return false - // for (let i = 0; i < data.length; i++) { - // try { - // const did = web3.utils - // .toChecksumAddress(data[i].datatokenId.address) - // .replace('0x', 'did:op:') - // const ddo = assets.filter((x) => x.id === did)[0] - // if (ddo === undefined) continue + if (datasetService.type === 'compute') { + if (algorithm.meta) { + // check if raw algo is allowed + if (datasetService.compute.allowRawAlgorithm) return true + LoggerInstance.error('ERROR: This service does not allow raw algorithm') + return false + } + 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( - // (x: Service) => x.index === data[i].serviceId - // )[0] +export function getValidUntilTime( + computeEnvMaxJobDuration: number, + 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 { providerEndpoint } = service + const minValue = Math.min(...inputValues) + const mytime = new Date() + mytime.setMinutes(mytime.getMinutes() + Math.floor(minValue / 60)) + return Math.floor(mytime.getTime() / 1000) +} - // const wasProviderQueried = - // serviceEndpoints?.filter((x) => x === providerEndpoint).length > 0 +export async function getComputeEnviroment( + asset: Asset +): Promise { + 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 - // serviceEndpoints.push(providerEndpoint) - // } catch (err) { - // LoggerInstance.error(err.message) - // } +export function getQueryString( + trustedAlgorithmList: PublisherTrustedAlgorithm[], + trustedPublishersList: string[], + 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 { + 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 { + 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 { + 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 serviceEndpoints - - return ['dummy'] + return computeJobs } +export async function getComputeJobs( + chainIds: number[], + accountId: string, + asset?: AssetExtended, + cancelToken?: CancelToken +): Promise { + 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( -// serviceEndpoints: string[], -// config: Config, -// ocean: Ocean -// ): Promise { -// const providers: Provider[] = [] + const results = await fetchDataForMultipleChains( + assetDTAddress ? getComputeOrdersByDatatokenAddress : getComputeOrders, + variables, + assetDTAddress ? [asset?.chainId] : chainIds + ) -// try { -// for (let i = 0; i < serviceEndpoints?.length; i++) { -// const instanceConfig = { -// config, -// web3: config.web3Provider, -// logger: LoggerInstance, -// ocean -// } -// const provider = await Provider.getInstance(instanceConfig) -// 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) -// } + let tokenOrders: TokenOrder[] = [] + results.map((result) => + result.orders.forEach((tokenOrder: TokenOrder) => + tokenOrders.push(tokenOrder) + ) + ) + if (tokenOrders.length === 0) { + computeResult.isLoaded = true + return computeResult + } -// return providers -// } + tokenOrders = tokenOrders.sort( + (a, b) => b.createdTimestamp - a.createdTimestamp + ) -// async function getJobs( -// providers: Provider[], -// account: Account, -// assets: Asset[] -// ): Promise { -// const computeJobs: ComputeJobMetaData[] = [] + const datatokenAddressList = tokenOrders.map( + (tokenOrder: TokenOrder) => tokenOrder.datatoken.address + ) + if (!datatokenAddressList) return -// for (let i = 0; i < providers.length; i++) { -// try { -// const providerComputeJobs = (await providers[i].computeStatus( -// '', -// account, -// undefined, -// undefined, -// false -// )) as ComputeJob[] + const assets = await getAssetMetadata( + datatokenAddressList, + cancelToken, + chainIds + ) -// // means the provider uri is not good, so we ignore it and move on -// if (!providerComputeJobs) continue -// providerComputeJobs.sort((a, b) => { -// if (a.dateCreated > b.dateCreated) { -// return -1 -// } -// if (a.dateCreated < b.dateCreated) { -// return 1 -// } -// return 0 -// }) + const providerUrls: string[] = [] + assets.forEach((asset: Asset) => + providerUrls.push(asset.services[0].serviceEndpoint) + ) -// for (let j = 0; j < providerComputeJobs?.length; j++) { -// const job = providerComputeJobs[j] -// const did = job.inputDID[0] -// const ddo = assets.filter((x) => x.id === did)[0] + computeResult.computeJobs = await getJobs(providerUrls, accountId, assets) + computeResult.isLoaded = true -// if (!ddo) continue - -// 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 { -// 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 -// } + return computeResult +} export async function createTrustedAlgorithmList( selectedAlgorithms: string[], // list of DIDs, @@ -282,10 +338,16 @@ export async function createTrustedAlgorithmList( ) 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 = { did: selectedAlgorithm.id, containerSectionChecksum: getHash( - JSON.stringify(selectedAlgorithm.metadata.algorithm.container) + JSON.stringify(sanitizedAlgorithmContainer) ), filesChecksum: getHash(selectedAlgorithm.services[0].files) } @@ -315,3 +377,31 @@ export async function transformComputeFormToServiceComputeOptions( return privacy } + +export async function checkComputeResourcesValidity( + asset: Asset, + accountId: string, + computeEnvMaxJobDuration: number, + datasetTimeout?: number, + algorithmTimeout?: number, + cancelToken?: CancelToken +): Promise { + 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 +} diff --git a/src/@utils/ddo.ts b/src/@utils/ddo.ts index 14ccd87cc..0bc817e4f 100644 --- a/src/@utils/ddo.ts +++ b/src/@utils/ddo.ts @@ -10,6 +10,13 @@ export function getServiceByName( 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 { switch (timeout) { case 'Forever': diff --git a/src/@utils/feedback.ts b/src/@utils/feedback.ts index 2df15688b..6e936cc5c 100644 --- a/src/@utils/feedback.ts +++ b/src/@utils/feedback.ts @@ -11,9 +11,16 @@ export function getOrderFeedback( } } -// TODO: customize for compute -export const computeFeedback: { [key in number]: string } = { - 0: 'Ordering asset...', - 1: 'Transfering datatoken.', - 2: 'Access granted. Starting job...' +export function getComputeFeedback( + baseTokenSymbol?: string, + datatokenSymbol?: string, + assetType?: string +): { [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 ...' + } } diff --git a/src/@utils/fixedRateExchange.ts b/src/@utils/fixedRateExchange.ts index 9b75dc79f..e8d1b1360 100644 --- a/src/@utils/fixedRateExchange.ts +++ b/src/@utils/fixedRateExchange.ts @@ -2,6 +2,7 @@ import { FixedRateExchange, PriceAndFees } from '@oceanprotocol/lib' import { AccessDetails } from 'src/@types/Price' import Web3 from 'web3' import { getOceanConfig } from './ocean' +import { consumeMarketPoolSwapFee } from '../../app.config' import { getDummyWeb3 } from './web3' /** @@ -28,7 +29,8 @@ export async function getFixedBuyPrice( const fixed = new FixedRateExchange(web3, config.fixedRateExchangeAddress) const estimatedPrice = await fixed.calcBaseInGivenOutDT( accessDetails.addressOrId, - '1' + '1', + consumeMarketPoolSwapFee ) return estimatedPrice } diff --git a/src/@utils/nft.ts b/src/@utils/nft.ts index 326900141..d75518d74 100644 --- a/src/@utils/nft.ts +++ b/src/@utils/nft.ts @@ -112,21 +112,6 @@ export async function setNftMetadata( // theoretically used by aquarius or provider, not implemented yet, will remain hardcoded 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( asset.nftAddress, accountId, diff --git a/src/@utils/order.ts b/src/@utils/order.ts index 96d1acdce..71b3db8a9 100644 --- a/src/@utils/order.ts +++ b/src/@utils/order.ts @@ -1,8 +1,12 @@ import { approve, + approveWei, Datatoken, FreOrderParams, + LoggerInstance, OrderParams, + ProviderComputeInitialize, + ProviderFees, ProviderInstance } from '@oceanprotocol/lib' import { AssetExtended } from 'src/@types/AssetExtended' @@ -15,35 +19,44 @@ import { consumeMarketOrderFee, consumeMarketFixedSwapFee } 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 * @param web3 * @param asset + * @param orderPriceAndFees * @param accountId + * @param providerFees + * @param computeConsumerAddress * @returns {TransactionReceipt} receipt of the order */ export async function order( web3: Web3, asset: AssetExtended, orderPriceAndFees: OrderPriceAndFees, - accountId: string + accountId: string, + providerFees?: ProviderFees, + computeConsumerAddress?: string ): Promise { const datatoken = new Datatoken(web3) const config = getOceanConfig(asset.chainId) - const initializeData = await ProviderInstance.initialize( - asset.id, - asset.services[0].id, - 0, - accountId, - asset.services[0].serviceEndpoint - ) + const initializeData = + !providerFees && + (await ProviderInstance.initialize( + asset.id, + asset.services[0].id, + 0, + accountId, + asset.services[0].serviceEndpoint + )) const orderParams = { - consumer: accountId, + consumer: computeConsumerAddress || accountId, serviceIndex: 0, - _providerFee: initializeData.providerFee, + _providerFee: providerFees || initializeData.providerFee, _consumeMarketFee: { consumeMarketFeeAddress: marketFeeAddress, consumeMarketFeeAmount: consumeMarketOrderFee, @@ -51,7 +64,6 @@ export async function order( } } as OrderParams - // TODO: we need to approve provider fee switch (asset.accessDetails?.type) { case 'fixed': { // this assumes all fees are in ocean @@ -87,9 +99,9 @@ export async function order( const tx = await datatoken.startOrder( asset.accessDetails.datatoken.address, accountId, - accountId, + computeConsumerAddress || accountId, 0, - initializeData.providerFee + providerFees || initializeData.providerFee ) 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 { + 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 { + 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 { + 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} tx id + */ +export async function handleComputeOrder( + web3: Web3, + asset: AssetExtended, + orderPriceAndFees: OrderPriceAndFees, + accountId: string, + hasDatatoken: boolean, + initializeData: ProviderComputeInitialize, + computeConsumerAddress?: string +): Promise { + 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}`) + } +} diff --git a/src/@utils/provider.ts b/src/@utils/provider.ts index 817b6d376..358361bb4 100644 --- a/src/@utils/provider.ts +++ b/src/@utils/provider.ts @@ -1,11 +1,54 @@ import { + ComputeAlgorithm, + ComputeAsset, + ComputeEnvironment, downloadFileBrowser, FileInfo, LoggerInstance, + ProviderComputeInitializeResults, ProviderInstance } from '@oceanprotocol/lib' import { AssetExtended } from 'src/@types/AssetExtended' import Web3 from 'web3' +import { getValidUntilTime } from './compute' + +export async function initializeProviderForCompute( + dataset: AssetExtended, + algorithm: AssetExtended, + accountId: string, + computeEnv: ComputeEnvironment = null +): Promise { + 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 ?!?!?! export async function getEncryptedFiles( diff --git a/src/@utils/subgraph.ts b/src/@utils/subgraph.ts index 2972980dd..7b32da86b 100644 --- a/src/@utils/subgraph.ts +++ b/src/@utils/subgraph.ts @@ -15,7 +15,7 @@ import { import { OrdersData_orders as OrdersData } from '../@types/subgraph/OrdersData' import { UserSalesQuery as UsersSalesList } from '../@types/subgraph/UserSalesQuery' import { OpcFeesQuery as OpcFeesData } from '../@types/subgraph/OpcFeesQuery' -import { calcSingleOutGivenPoolIn, getLiquidityByShares } from './pool' +import { calcSingleOutGivenPoolIn } from './pool' import Decimal from 'decimal.js' import { MAX_DECIMALS } from './constants' diff --git a/src/components/@shared/ButtonBuy/index.module.css b/src/components/@shared/ButtonBuy/index.module.css index 19c711524..99516295e 100644 --- a/src/components/@shared/ButtonBuy/index.module.css +++ b/src/components/@shared/ButtonBuy/index.module.css @@ -3,6 +3,11 @@ margin-top: calc(var(--spacer) / 2); } +.actionsCenter { + margin: auto; + display: block; +} + .help { font-size: var(--font-size-mini); color: var(--color-secondary); diff --git a/src/components/@shared/ButtonBuy/index.tsx b/src/components/@shared/ButtonBuy/index.tsx index 2cbf4bf00..8d321d610 100644 --- a/src/components/@shared/ButtonBuy/index.tsx +++ b/src/components/@shared/ButtonBuy/index.tsx @@ -28,7 +28,8 @@ interface ButtonBuyProps { type?: 'submit' priceType?: string algorithmPriceType?: string - algorithmConsumableStatus?: number + isAlgorithmConsumable?: boolean + hasProviderFee?: boolean } // TODO: we need to take a look at these messages @@ -75,7 +76,8 @@ function getComputeAssetHelpText( dtBalanceSelectedComputeAsset?: string, selectedComputeAssettLowPoolLiquidity?: boolean, selectedComputeAssetType?: string, - algorithmConsumableStatus?: number + isAlgorithmConsumable?: boolean, + hasProviderFee?: boolean ) { const computeAssetHelpText = getConsumeHelpText( dtBalance, @@ -90,14 +92,9 @@ function getComputeAssetHelpText( ) const computeAlgoHelpText = (!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 ? `You already bought the selected ${selectedComputeAssetType}, allowing you to use it without paying again.` : hasDatatokenSelectedComputeAsset @@ -107,11 +104,14 @@ function getComputeAssetHelpText( : isBalanceSufficient === false ? '' : `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 ? computeAlgoHelpText : lowPoolLiquidity ? computeAssetHelpText - : `${computeAssetHelpText} ${computeAlgoHelpText}` + : `${computeAssetHelpText} ${computeAlgoHelpText} ${providerFeeHelpText}` return computeHelpText } @@ -140,7 +140,8 @@ export default function ButtonBuy({ type, priceType, algorithmPriceType, - algorithmConsumableStatus + isAlgorithmConsumable, + hasProviderFee }: ButtonBuyProps): ReactElement { const buttonText = action === 'download' @@ -149,7 +150,9 @@ export default function ButtonBuy({ : priceType === 'free' ? 'Get' : `Buy ${assetTimeout === 'Forever' ? '' : ` for ${assetTimeout}`}` - : hasPreviousOrder && hasPreviousOrderSelectedComputeAsset + : hasPreviousOrder && + hasPreviousOrderSelectedComputeAsset && + !hasProviderFee ? 'Start Compute Job' : priceType === 'free' && algorithmPriceType === 'free' ? 'Order Compute Job' @@ -166,6 +169,7 @@ export default function ButtonBuy({ type={type} onClick={onClick} disabled={disabled} + className={action === 'compute' ? styles.actionsCenter : ''} > {buttonText} @@ -198,7 +202,8 @@ export default function ButtonBuy({ dtBalanceSelectedComputeAsset, selectedComputeAssetLowPoolLiquidity, selectedComputeAssetType, - algorithmConsumableStatus + isAlgorithmConsumable, + hasProviderFee )} diff --git a/src/components/@shared/atoms/Blockies/index.tsx b/src/components/@shared/atoms/Blockies/index.tsx index cb361ea65..9957ddefe 100644 --- a/src/components/@shared/atoms/Blockies/index.tsx +++ b/src/components/@shared/atoms/Blockies/index.tsx @@ -12,6 +12,7 @@ export default function Blockies({ className }: BlockiesProps): ReactElement { if (!accountId) return null + const blockies = toDataUrl(accountId) return ( diff --git a/src/components/Asset/AssetActions/Compute/AlgorithmDatasetsListForCompute.tsx b/src/components/Asset/AssetActions/Compute/AlgorithmDatasetsListForCompute.tsx index 4243bb642..79a7b24b4 100644 --- a/src/components/Asset/AssetActions/Compute/AlgorithmDatasetsListForCompute.tsx +++ b/src/components/Asset/AssetActions/Compute/AlgorithmDatasetsListForCompute.tsx @@ -5,7 +5,6 @@ import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection' import AssetComputeList from '@shared/AssetList/AssetComputeList' import { useCancelToken } from '@hooks/useCancelToken' import { getServiceByName } from '@utils/ddo' -import { Asset } from '@oceanprotocol/lib' import { AssetExtended } from 'src/@types/AssetExtended' export default function AlgorithmDatasetsListForCompute({ @@ -15,9 +14,9 @@ export default function AlgorithmDatasetsListForCompute({ asset: AssetExtended algorithmDid: string }): ReactElement { + const newCancelToken = useCancelToken() const [datasetsForCompute, setDatasetsForCompute] = useState() - const newCancelToken = useCancelToken() useEffect(() => { if (!asset) return @@ -37,7 +36,7 @@ export default function AlgorithmDatasetsListForCompute({ setDatasetsForCompute(datasets) } asset.metadata.type === 'algorithm' && getDatasetsAllowedForCompute() - }, [asset?.metadata?.type]) + }, [asset, algorithmDid, newCancelToken]) return (
diff --git a/src/components/Asset/AssetActions/Compute/FormComputeDataset.tsx b/src/components/Asset/AssetActions/Compute/FormComputeDataset.tsx index 77971b7eb..a0f17b43a 100644 --- a/src/components/Asset/AssetActions/Compute/FormComputeDataset.tsx +++ b/src/components/Asset/AssetActions/Compute/FormComputeDataset.tsx @@ -10,13 +10,18 @@ import { useAsset } from '@context/Asset' import { useWeb3 } from '@context/Web3' import content from '../../../../../content/pages/startComputeDataset.json' import { Asset } from '@oceanprotocol/lib' -import { AccessDetails } from 'src/@types/Price' +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 Alert from '@shared/atoms/Alert' export default function FormStartCompute({ algorithms, ddoListAlgorithms, + selectedAlgorithmAsset, setSelectedAlgorithm, isLoading, isComputeButtonDisabled, @@ -35,13 +40,17 @@ export default function FormStartCompute({ selectedComputeAssetType, selectedComputeAssetTimeout, stepText, - algorithmConsumeDetails, isConsumable, - consumableFeedback + consumableFeedback, + datasetOrderPriceAndFees, + algoOrderPriceAndFees, + providerFeeAmount, + validUntil }: { algorithms: AssetSelectionAsset[] ddoListAlgorithms: Asset[] - setSelectedAlgorithm: React.Dispatch> + selectedAlgorithmAsset: AssetExtended + setSelectedAlgorithm: React.Dispatch> isLoading: boolean isComputeButtonDisabled: boolean hasPreviousOrder: boolean @@ -59,19 +68,27 @@ export default function FormStartCompute({ selectedComputeAssetType?: string selectedComputeAssetTimeout?: string stepText: string - algorithmConsumeDetails: AccessDetails isConsumable: boolean consumableFeedback: string + datasetOrderPriceAndFees?: OrderPriceAndFees + algoOrderPriceAndFees?: OrderPriceAndFees + providerFeeAmount?: string + validUntil?: string }): ReactElement { const { siteContent } = useMarketMetadata() + const { accountId, balance } = useWeb3() const { isValid, values }: FormikContextType<{ algorithm: string }> = useFormikContext() 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(false) - const { accountId, balance } = useWeb3() - const [algorithmConsumableStatus, setAlgorithmConsumableStatus] = - useState() function getAlgorithmAsset(algorithmId: string): Asset { let assetDdo = null @@ -82,50 +99,79 @@ export default function FormStartCompute({ } useEffect(() => { - if (!values.algorithm) return - const algorithmDDO = getAlgorithmAsset(values.algorithm) - setSelectedAlgorithm(algorithmDDO) + if (!values.algorithm || !accountId || !isConsumable) return - if (!accountId || !isConsumable) return - async function checkIsConsumable() { - // const consumable = await ocean.assets.isConsumable( - // algorithmDDO as any, - // accountId.toLowerCase() - // ) - // if (consumable) setAlgorithmConsumableStatus(consumable.status) + async function fetchAlgorithmAssetExtended() { + const algorithmAsset = getAlgorithmAsset(values.algorithm) + const accessDetails = await getAccessDetails( + algorithmAsset.chainId, + algorithmAsset.services[0].datatokenAddress, + algorithmAsset.services[0].timeout, + accountId + ) + const extendedAlgoAsset: AssetExtended = { + ...algorithmAsset, + accessDetails + } + setSelectedAlgorithm(extendedAlgoAsset) } - checkIsConsumable() + fetchAlgorithmAssetExtended() }, [values.algorithm, accountId, isConsumable]) // // Set price for calculation output // 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 = - hasPreviousOrder || hasDatatoken ? 0 : Number(asset.accessDetails.price) + hasPreviousOrder || hasDatatoken + ? new Decimal(0) + : new Decimal( + datasetOrderPriceAndFees?.price || asset.accessDetails.price + ).toDecimalPlaces(MAX_DECIMALS) const priceAlgo = hasPreviousOrderSelectedComputeAsset || hasDatatokenSelectedComputeAsset - ? 0 - : Number(algorithmConsumeDetails.price) - - setTotalPrice((priceDataset + priceAlgo).toString()) + ? new Decimal(0) + : new Decimal( + algoOrderPriceAndFees?.price || + 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, - algorithmConsumeDetails, + selectedAlgorithmAsset?.accessDetails, hasPreviousOrder, hasDatatoken, hasPreviousOrderSelectedComputeAsset, - hasDatatokenSelectedComputeAsset + hasDatatokenSelectedComputeAsset, + datasetOrderPriceAndFees, + algoOrderPriceAndFees, + providerFeeAmount ]) useEffect(() => { - if (!totalPrice) return + if (!totalPrice || !balance?.ocean || !dtBalance) return + setIsBalanceSufficient( compareAsBN(balance.ocean, `${totalPrice}`) || Number(dtBalance) >= 1 ) - }, [totalPrice]) + }, [totalPrice, balance?.ocean, dtBalance]) return (
@@ -140,6 +186,7 @@ export default function FormStartCompute({ {...field} options={algorithms} component={Input} + disabled={isLoading} /> ))} @@ -152,9 +199,13 @@ export default function FormStartCompute({ hasDatatoken={hasDatatoken} selectedComputeAssetTimeout={selectedComputeAssetTimeout} hasDatatokenSelectedComputeAsset={hasDatatokenSelectedComputeAsset} - algorithmConsumeDetails={algorithmConsumeDetails} + algorithmConsumeDetails={selectedAlgorithmAsset?.accessDetails} symbol={oceanSymbol} - totalPrice={Number.parseFloat(totalPrice)} + totalPrice={totalPrice} + datasetOrderPrice={datasetOrderPrice} + algoOrderPrice={algoOrderPrice} + providerFeeAmount={providerFeeAmount} + validUntil={validUntil} /> 0 + !selectedAlgorithmAsset?.accessDetails?.isPurchasable } hasPreviousOrder={hasPreviousOrder} hasDatatoken={hasDatatoken} @@ -187,11 +238,14 @@ export default function FormStartCompute({ isLoading={isLoading} type="submit" priceType={asset?.accessDetails?.type} - algorithmPriceType={algorithmConsumeDetails?.type} + algorithmPriceType={selectedAlgorithmAsset?.accessDetails?.type} isBalanceSufficient={isBalanceSufficient} isConsumable={isConsumable} consumableFeedback={consumableFeedback} - algorithmConsumableStatus={algorithmConsumableStatus} + isAlgorithmConsumable={ + selectedAlgorithmAsset?.accessDetails?.isPurchasable + } + hasProviderFee={providerFeeAmount && providerFeeAmount !== '0'} /> ) diff --git a/src/components/Asset/AssetActions/Compute/PriceOutput.module.css b/src/components/Asset/AssetActions/Compute/PriceOutput.module.css index e5c30b0b1..413f2f859 100644 --- a/src/components/Asset/AssetActions/Compute/PriceOutput.module.css +++ b/src/components/Asset/AssetActions/Compute/PriceOutput.module.css @@ -36,8 +36,9 @@ border-bottom: 1px solid var(--border-color); padding-top: calc(var(--spacer) / 7); padding-bottom: calc(var(--spacer) / 7); - display: flex; - justify-content: space-between; + display: grid; + grid-template-columns: 5% 1fr auto; + column-gap: calc(var(--spacer) / 10); } .priceRow:last-child { @@ -47,8 +48,14 @@ .sign { display: inline-block; - width: 5%; text-align: left; color: var(--color-secondary); font-size: var(--font-size-base); } + +.type { + display: inline-block; + text-align: left; + color: var(--color-secondary); + font-size: var(--font-size-mini); +} diff --git a/src/components/Asset/AssetActions/Compute/PriceOutput.tsx b/src/components/Asset/AssetActions/Compute/PriceOutput.tsx index 202ff1e1b..3cfe3f18a 100644 --- a/src/components/Asset/AssetActions/Compute/PriceOutput.tsx +++ b/src/components/Asset/AssetActions/Compute/PriceOutput.tsx @@ -4,9 +4,11 @@ import PriceUnit from '@shared/Price/PriceUnit' import Tooltip from '@shared/atoms/Tooltip' import styles from './PriceOutput.module.css' import { AccessDetails } from 'src/@types/Price' +import { MAX_DECIMALS } from '@utils/constants' +import Decimal from 'decimal.js' interface PriceOutputProps { - totalPrice: number + totalPrice: string hasPreviousOrder: boolean hasDatatoken: boolean symbol: string @@ -15,6 +17,10 @@ interface PriceOutputProps { hasDatatokenSelectedComputeAsset: boolean algorithmConsumeDetails: AccessDetails selectedComputeAssetTimeout: string + datasetOrderPrice?: number + algoOrderPrice?: number + providerFeeAmount?: string + validUntil?: string } function Row({ @@ -23,18 +29,21 @@ function Row({ hasDatatoken, symbol, timeout, - sign + sign, + type }: { - price: number + price: string hasPreviousOrder?: boolean hasDatatoken?: boolean symbol?: string timeout?: string sign?: string + type?: string }) { return (
{sign}
+
{type}
+
diff --git a/src/components/Asset/AssetActions/Compute/index.tsx b/src/components/Asset/AssetActions/Compute/index.tsx index e07179539..d70294c30 100644 --- a/src/components/Asset/AssetActions/Compute/index.tsx +++ b/src/components/Asset/AssetActions/Compute/index.tsx @@ -1,232 +1,291 @@ -import React, { useState, ReactElement, useEffect, useCallback } from 'react' +import React, { useState, ReactElement, useEffect } from 'react' import { Asset, DDO, - PublisherTrustedAlgorithm, - FileInfo + FileInfo, + Datatoken, + ProviderInstance, + ComputeAsset, + ZERO_ADDRESS, + ComputeEnvironment, + LoggerInstance, + ComputeAlgorithm, + ComputeOutput, + ProviderComputeInitializeResults, + unitsToAmount } from '@oceanprotocol/lib' import { toast } from 'react-toastify' import Price from '@shared/Price' import FileIcon from '@shared/FileIcon' import Alert from '@shared/atoms/Alert' import { useWeb3 } from '@context/Web3' -import { - generateBaseQuery, - getFilterTerm, - queryMetadata -} from '@utils/aquarius' import { Formik } from 'formik' import { getInitialValues, validationSchema } from './_constants' -import axios from 'axios' import FormStartComputeDataset from './FormComputeDataset' import styles from './index.module.css' 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 AlgorithmDatasetsListForCompute from './AlgorithmDatasetsListForCompute' -import { getPreviousOrders } from '@utils/subgraph' import AssetActionHistoryTable from '../AssetActionHistoryTable' import ComputeJobs from '../../../Profile/History/ComputeJobs' import { useCancelToken } from '@hooks/useCancelToken' -import { useIsMounted } from '@hooks/useIsMounted' -import { SortTermOptions } from '../../../../@types/aquarius/SearchQuery' -import { getAccessDetails } from '@utils/accessDetailsAndPricing' -import { AccessDetails } from 'src/@types/Price' -import { transformAssetToAssetSelection } from '@utils/assetConvertor' +import { Decimal } from 'decimal.js' +import { useAbortController } from '@hooks/useAbortController' +import { getOrderPriceAndFees } from '@utils/accessDetailsAndPricing' +import { OrderPriceAndFees } from 'src/@types/Price' +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 { getPoolData } from '@context/Pool/_utils' +import { getDummyWeb3 } from '@utils/web3' +import { initializeProviderForCompute } from '@utils/provider' export default function Compute({ - ddo, - accessDetails, + asset, dtBalance, file, fileIsLoading, - isConsumable, consumableFeedback }: { - ddo: Asset - accessDetails: AccessDetails + asset: AssetExtended dtBalance: string file: FileInfo fileIsLoading?: boolean - isConsumable?: boolean consumableFeedback?: string }): ReactElement { - const { appConfig } = useMarketMetadata() - const { accountId } = useWeb3() - const [isJobStarting, setIsJobStarting] = useState(false) + const { accountId, web3 } = useWeb3() + const { getOpcFeeForToken } = useMarketMetadata() + const { poolData } = usePool() + const newAbortController = useAbortController() + const newCancelToken = useCancelToken() + + const [isOrdering, setIsOrdering] = useState(false) + const [isOrdered, setIsOrdered] = useState(false) const [error, setError] = useState() const [algorithmList, setAlgorithmList] = useState() const [ddoAlgorithmList, setDdoAlgorithmList] = useState() - const [selectedAlgorithmAsset, setSelectedAlgorithmAsset] = useState() + const [selectedAlgorithmAsset, setSelectedAlgorithmAsset] = + useState() const [hasAlgoAssetDatatoken, setHasAlgoAssetDatatoken] = useState() - const [isPublished, setIsPublished] = useState(false) - const [hasPreviousDatasetOrder, setHasPreviousDatasetOrder] = useState(false) - const [previousDatasetOrderId, setPreviousDatasetOrderId] = useState() - const [hasPreviousAlgorithmOrder, setHasPreviousAlgorithmOrder] = - useState(false) - const [algorithmDTBalance, setalgorithmDTBalance] = useState() - const [algorithmConsumeDetails, setAlgorithmConsumeDetails] = - useState() - const [previousAlgorithmOrderId, setPreviousAlgorithmOrderId] = - useState() - const [datasetTimeout, setDatasetTimeout] = useState() - const [algorithmTimeout, setAlgorithmTimeout] = useState() - const newCancelToken = useCancelToken() - const hasDatatoken = Number(dtBalance) >= 1 - const isMounted = useIsMounted() + const [algorithmDTBalance, setAlgorithmDTBalance] = useState() + + const [validOrderTx, setValidOrderTx] = useState('') + const [validAlgorithmOrderTx, setValidAlgorithmOrderTx] = useState('') + const [isConsumablePrice, setIsConsumablePrice] = useState(true) - const [isAlgoConsumablePrice, setIsAlgoConsumablePrice] = useState(true) + const [isConsumableaAlgorithmPrice, setIsConsumableAlgorithmPrice] = + useState(true) + const [computeStatusText, setComputeStatusText] = useState('') + const [computeEnv, setComputeEnv] = useState() + const [initializedProviderResponse, setInitializedProviderResponse] = + useState() + const [providerFeeAmount, setProviderFeeAmount] = useState('0') + const [computeValidUntil, setComputeValidUntil] = useState('0') + const [datasetOrderPriceAndFees, setDatasetOrderPriceAndFees] = + useState() + const [algoOrderPriceAndFees, setAlgoOrderPriceAndFees] = + useState() + const [isRequestingAlgoOrderPrice, setIsRequestingAlgoOrderPrice] = + useState(false) + const [refetchJobs, setRefetchJobs] = useState(false) + + const hasDatatoken = Number(dtBalance) >= 1 const isComputeButtonDisabled = - isJobStarting === true || + isOrdering === true || file === null || - (!hasPreviousDatasetOrder && !hasDatatoken && !isConsumablePrice) || - (!hasPreviousAlgorithmOrder && + (!validOrderTx && !hasDatatoken && !isConsumablePrice) || + (!validAlgorithmOrderTx && !hasAlgoAssetDatatoken && - !isAlgoConsumablePrice) + !isConsumableaAlgorithmPrice) - const service = ddo?.services[0] - const { timeout } = service - - async function checkPreviousOrders(ddo: DDO) { - const { type } = ddo.metadata - - const orderId = await getPreviousOrders( - ddo.services[0].datatokenAddress?.toLowerCase(), - accountId?.toLowerCase(), - timeout.toString() + async function checkAssetDTBalance(asset: DDO): Promise { + if (!asset?.services[0].datatokenAddress) return + const web3 = await getDummyWeb3(asset?.chainId) + const datatokenInstance = new Datatoken(web3) + const dtBalance = await datatokenInstance.balance( + asset?.services[0].datatokenAddress, + accountId ) - - if (!isMounted()) return - if (type === 'algorithm') { - setPreviousAlgorithmOrderId(orderId) - setHasPreviousAlgorithmOrder(!!orderId) - } else { - setPreviousDatasetOrderId(orderId) - setHasPreviousDatasetOrder(!!orderId) - } + setAlgorithmDTBalance(new Decimal(dtBalance).toString()) + const hasAlgoDt = Number(dtBalance) >= 1 + setHasAlgoAssetDatatoken(hasAlgoDt) + return hasAlgoDt } - async function checkAssetDTBalance(asset: DDO) { - // const AssetDtBalance = await ocean.datatokens.balance( - // asset.services[0].datatokenAddress, - // accountId - // ) - // setalgorithmDTBalance(AssetDtBalance) - // setHasAlgoAssetDatatoken(Number(AssetDtBalance) >= 1) - } + async function initPriceAndFees() { + try { + const computeEnv = await getComputeEnviroment(asset) + if (!computeEnv || !computeEnv.id) + throw new Error(`Error getting compute environments!`) - function getQuerryString( - trustedAlgorithmList: PublisherTrustedAlgorithm[], - chainId?: number - ): SearchQuery { - const algorithmDidList = trustedAlgorithmList.map((x) => x.did) - - 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 { - 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 + setComputeEnv(computeEnv) + const initializedProvider = await initializeProviderForCompute( + asset, + selectedAlgorithmAsset, + accountId, + computeEnv ) - setDdoAlgorithmList(gueryResults.results) - - algorithmSelectionList = await transformAssetToAssetSelection( - computeService?.serviceEndpoint, - gueryResults.results, - [] + if ( + !initializedProvider || + !initializedProvider?.datasets || + !initializedProvider?.algorithm ) - } - return algorithmSelectionList - } + throw new Error(`Error initializing provider for the compute job!`) - const initMetadata = useCallback(async (ddo: Asset): Promise => { - if (!ddo) return - const accessDetails = await getAccessDetails( - ddo.chainId, - ddo.services[0].datatokenAddress - ) - setAlgorithmConsumeDetails(accessDetails) - }, []) + setInitializedProviderResponse(initializedProvider) + setProviderFeeAmount( + await unitsToAmount( + web3, + initializedProvider?.datasets?.[0]?.providerFee?.providerFeeToken, + initializedProvider?.datasets?.[0]?.providerFee?.providerFeeAmount + ) + ) + const computeDuration = ( + parseInt(initializedProvider?.datasets?.[0]?.providerFee?.validUntil) - + Math.floor(Date.now() / 1000) + ).toString() + setComputeValidUntil(computeDuration) - useEffect(() => { - if (!algorithmConsumeDetails) return + if ( + 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) - }, [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) + setDatasetOrderPriceAndFees(datasetPriceAndFees) } + + 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 useEffect(() => { @@ -235,168 +294,131 @@ export default function Compute({ toast.error(newError) }, [error]) - // async function startJob(algorithmId: string) { - // try { - // if (!ocean) return + async function startJob(): Promise { + try { + 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) - // setIsPublished(false) - // setError(undefined) + setComputeStatusText( + getComputeFeedback( + 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') - // const serviceAlgo = getServiceByName(selectedAlgorithmAsset, 'access') - // ? getServiceByName(selectedAlgorithmAsset, 'access') - // : getServiceByName(selectedAlgorithmAsset, 'compute') + setComputeStatusText( + getComputeFeedback( + selectedAlgorithmAsset.accessDetails.baseToken?.symbol, + selectedAlgorithmAsset.accessDetails.datatoken?.symbol, + selectedAlgorithmAsset.metadata.type + )[ + selectedAlgorithmAsset.accessDetails?.type === 'fixed' + ? 2 + : selectedAlgorithmAsset.accessDetails?.type === 'dynamic' + ? 1 + : 3 + ] + ) - // const computeAlgorithm: ComputeAlgorithm = { - // did: selectedAlgorithmAsset.id, - // serviceIndex: serviceAlgo.index, - // dataToken: selectedAlgorithmAsset.services[0].datatokenAddress - // } - // const allowed = await ocean.compute.isOrderable( - // ddo.id, - // computeService.index, - // computeAlgorithm - // ) - // LoggerInstance.log('[compute] Is data set orderable?', allowed) + const algorithmOrderTx = await handleComputeOrder( + web3, + selectedAlgorithmAsset, + algoOrderPriceAndFees, + accountId, + hasAlgoAssetDatatoken, + initializedProviderResponse.algorithm, + computeEnv.consumerAddress + ) + if (!algorithmOrderTx) throw new Error('Failed to order algorithm.') - // if (!allowed) { - // setError( - // 'Data set is not orderable in combination with selected algorithm.' - // ) - // LoggerInstance.error( - // '[compute] Error starting compute job. Dataset is not orderable in combination with selected algorithm.' - // ) - // return - // } + LoggerInstance.log('[compute] Starting compute job.') + const computeAsset: ComputeAsset = { + documentId: asset.id, + serviceId: asset.services[0].id, + transferTxId: datasetOrderTx + } + computeAlgorithm.transferTxId = algorithmOrderTx + 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) { - // const tx = await buyDT('1', price, ddo) - // if (!tx) { - // setError('Error buying datatoken.') - // LoggerInstance.error('[compute] Error buying datatoken for data set ', ddo.id) - // return - // } - // } - - // 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) - // } - // } + LoggerInstance.log('[compute] Starting compute job response: ', response) + setIsOrdered(true) + setRefetchJobs(!refetchJobs) + initPriceAndFees() + } catch (error) { + setError(error.message) + LoggerInstance.error(`[compute] ${error.message} `) + } finally { + setIsOrdering(false) + } + } return ( <>
- +
- {ddo.metadata.type === 'algorithm' ? ( + {asset.metadata.type === 'algorithm' ? ( <> - + ) : ( { - // await startJob(values.algorithm) + if (!values.algorithm) return + await startJob() }} > )}
- {isPublished && ( + {isOrdered && ( )}
- {accountId && accessDetails?.datatoken && ( + {accountId && asset?.accessDetails?.datatoken && ( - + )} diff --git a/src/components/Asset/AssetActions/index.tsx b/src/components/Asset/AssetActions/index.tsx index 4995acbc0..a062d4762 100644 --- a/src/components/Asset/AssetActions/index.tsx +++ b/src/components/Asset/AssetActions/index.tsx @@ -113,8 +113,7 @@ export default function AssetActions({ const UseContent = isCompute ? ( void + receipts: NftUpdate[] + setReceipts: (receipts: NftUpdate[]) => void }): ReactElement { const { asset } = useAsset() diff --git a/src/components/Asset/AssetContent/index.module.css b/src/components/Asset/AssetContent/index.module.css index 9a3b3d705..992dd5081 100644 --- a/src/components/Asset/AssetContent/index.module.css +++ b/src/components/Asset/AssetContent/index.module.css @@ -35,20 +35,7 @@ text-align: center; margin-top: var(--spacer); margin-bottom: calc(var(--spacer) * 1.5); - margin-left: -2rem; - margin-right: -2rem; - padding: calc(var(--spacer) / 4) var(--spacer); + padding: calc(var(--spacer) / 3) var(--spacer); border-top: 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); -} diff --git a/src/components/Asset/AssetContent/index.tsx b/src/components/Asset/AssetContent/index.tsx index c8cdc92dc..f9ea05266 100644 --- a/src/components/Asset/AssetContent/index.tsx +++ b/src/components/Asset/AssetContent/index.tsx @@ -15,17 +15,15 @@ import styles from './index.module.css' import NetworkName from '@shared/NetworkName' import content from '../../../../content/purgatory.json' import { AssetExtended } from 'src/@types/AssetExtended' -import { useWeb3 } from '@context/Web3' import Web3 from 'web3' +import Button from '@shared/atoms/Button' export default function AssetContent({ asset }: { asset: AssetExtended }): ReactElement { - const [isOwner, setIsOwner] = useState(false) - const { accountId } = useWeb3() - const { isInPurgatory, purgatoryData, owner, isAssetNetwork } = useAsset() + const { isInPurgatory, purgatoryData, isOwner, isAssetNetwork } = useAsset() const { debug } = useUserPreferences() const [receipts, setReceipts] = useState([]) const [nftPublisher, setNftPublisher] = useState() @@ -38,13 +36,6 @@ export default function AssetContent({ ) }, [receipts]) - useEffect(() => { - if (!accountId || !owner) return - - const isOwner = accountId.toLowerCase() === owner.toLowerCase() - setIsOwner(isOwner) - }, [accountId, owner, asset]) - return ( <>
@@ -84,9 +75,9 @@ export default function AssetContent({ {isOwner && isAssetNetwork && (
- - Edit - +
)}
diff --git a/src/components/Asset/Edit/index.tsx b/src/components/Asset/Edit/index.tsx index 99f84c006..40d144254 100644 --- a/src/components/Asset/Edit/index.tsx +++ b/src/components/Asset/Edit/index.tsx @@ -1,5 +1,4 @@ import React, { ReactElement, useState, useEffect } from 'react' -import { LoggerInstance } from '@oceanprotocol/lib' import { useAsset } from '@context/Asset' import styles from './index.module.css' import Tabs from '@shared/atoms/Tabs' @@ -33,7 +32,7 @@ export default function Edit({ uri }: { uri: string }): ReactElement { { title: 'Edit Compute Settings', content: , - disabled: !isCompute + disabled: !isCompute || asset?.metadata?.type === 'algorithm' } ].filter((tab) => tab !== undefined) diff --git a/src/components/Profile/History/ComputeJobs/Details.module.css b/src/components/Profile/History/ComputeJobs/Details.module.css index 53cb32c06..f6254c757 100644 --- a/src/components/Profile/History/ComputeJobs/Details.module.css +++ b/src/components/Profile/History/ComputeJobs/Details.module.css @@ -62,6 +62,20 @@ .assetMeta code { color: var(--color-secondary); 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 { diff --git a/src/components/Profile/History/ComputeJobs/Details.tsx b/src/components/Profile/History/ComputeJobs/Details.tsx index 337b3db70..5199a4958 100644 --- a/src/components/Profile/History/ComputeJobs/Details.tsx +++ b/src/components/Profile/History/ComputeJobs/Details.tsx @@ -33,7 +33,8 @@ function Asset({

- {symbol} | {did} + {`${symbol} | `} + {did}

) @@ -41,9 +42,11 @@ function Asset({ function DetailsAssets({ job }: { job: ComputeJobMetaData }) { const { appConfig } = useMarketMetadata() + const newCancelToken = useCancelToken() + const [algoName, setAlgoName] = useState() const [algoDtSymbol, setAlgoDtSymbol] = useState() - const newCancelToken = useCancelToken() + useEffect(() => { async function getAlgoMetadata() { const ddo = await retrieveAsset(job.algoDID, newCancelToken()) @@ -51,7 +54,7 @@ function DetailsAssets({ job }: { job: ComputeJobMetaData }) { setAlgoName(ddo?.metadata.name) } getAlgoMetadata() - }, [appConfig.metadataCacheUri, job.algoDID]) + }, [appConfig.metadataCacheUri, job.algoDID, newCancelToken]) return ( <> @@ -97,12 +100,6 @@ export default function Details({ /> )} {job.jobId}} /> - {/* {job.resultsDid && ( - {job.resultsDid}} - /> - )} */}
diff --git a/src/components/Profile/History/ComputeJobs/Results.module.css b/src/components/Profile/History/ComputeJobs/Results.module.css index b270c97c3..563be31b7 100644 --- a/src/components/Profile/History/ComputeJobs/Results.module.css +++ b/src/components/Profile/History/ComputeJobs/Results.module.css @@ -3,6 +3,12 @@ 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 { margin-top: calc(var(--spacer) / 3); } diff --git a/src/components/Profile/History/ComputeJobs/Results.tsx b/src/components/Profile/History/ComputeJobs/Results.tsx index 6231aae48..0ed6cd1f2 100644 --- a/src/components/Profile/History/ComputeJobs/Results.tsx +++ b/src/components/Profile/History/ComputeJobs/Results.tsx @@ -1,90 +1,108 @@ -import { LoggerInstance } from '@oceanprotocol/lib' -import React, { ReactElement, useState } from 'react' -import Loader from '@shared/atoms/Loader' +import { + ComputeResultType, + downloadFileBrowser, + LoggerInstance, + Provider +} from '@oceanprotocol/lib' +import React, { ReactElement, useEffect, useState } from 'react' import { ListItem } from '@shared/atoms/Lists' import Button from '@shared/atoms/Button' import styles from './Results.module.css' import FormHelp from '@shared/FormInput/Help' import content from '../../../../../content/pages/history.json' import { useWeb3 } from '@context/Web3' +import { useCancelToken } from '@hooks/useCancelToken' +import { retrieveAsset } from '@utils/aquarius' export default function Results({ job }: { job: ComputeJobMetaData }): ReactElement { - const { accountId } = useWeb3() + const providerInstance = new Provider() + const { accountId, web3 } = useWeb3() const [isLoading, setIsLoading] = useState(false) - const [hasFetched, setHasFetched] = useState(false) const isFinished = job.dateFinished !== null - async function getResults() { + const [datasetProvider, setDatasetProvider] = useState() + 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 try { setIsLoading(true) - // const jobStatus = await ocean.compute.status( - // account, - // job.did, - // undefined, - // undefined, - // job.jobId - // ) - // if (jobStatus?.length > 0) { - // job.algorithmLogUrl = jobStatus[0].algorithmLogUrl - // job.resultsUrl = jobStatus[0].resultsUrl - // } + const jobResult = await providerInstance.getComputeResultUrl( + datasetProvider, + web3, + accountId, + job.jobId, + resultIndex + ) + await downloadFileBrowser(jobResult) } catch (error) { LoggerInstance.error(error.message) } finally { setIsLoading(false) - setHasFetched(true) } } return (
- {hasFetched ? ( +

Results

+ {isFinished ? (
    - - {/* {job.algorithmLogUrl ? ( - - View Log - - ) : ( - 'No logs found.' - )} */} - - - {/* {job.resultsUrl && - Array.isArray(job.resultsUrl) && - job.resultsUrl.map((url, i) => - url ? ( - - - View Result {i + 1} - + {job.results && + Array.isArray(job.results) && + job.results.map((jobResult, i) => + jobResult.filename ? ( + + ) : ( No results found. ) - )} */} + )}
) : ( - +

Waiting for results...

)} {content.compute.storage}
diff --git a/src/components/Profile/History/ComputeJobs/index.tsx b/src/components/Profile/History/ComputeJobs/index.tsx index 346534b3f..c3643ed77 100644 --- a/src/components/Profile/History/ComputeJobs/index.tsx +++ b/src/components/Profile/History/ComputeJobs/index.tsx @@ -1,8 +1,6 @@ import React, { ReactElement, useEffect, useState, useCallback } from 'react' import Time from '@shared/atoms/Time' -import Link from 'next/link' import { LoggerInstance } from '@oceanprotocol/lib' -import Dotdotdot from 'react-dotdotdot' import Table from '@shared/atoms/Table' import Button from '@shared/atoms/Button' import { useWeb3 } from '@context/Web3' @@ -10,10 +8,12 @@ import Details from './Details' import Refresh from '@images/refresh.svg' import { useUserPreferences } from '@context/UserPreferences' import NetworkName from '@shared/NetworkName' -// import { getComputeJobs } from '@utils/compute' +import { getComputeJobs } from '@utils/compute' import styles from './index.module.css' import { useAsset } from '@context/Asset' import { useIsMounted } from '@hooks/useIsMounted' +import { useCancelToken } from '@hooks/useCancelToken' +import AssetListTitle from '@shared/AssetList/AssetListTitle' export function Status({ children }: { children: string }): ReactElement { return
{children}
@@ -23,13 +23,7 @@ const columns = [ { name: 'Data Set', selector: function getAssetRow(row: ComputeJobMetaData) { - return ( - - - {row.assetName} - - - ) + return } }, { @@ -69,18 +63,23 @@ const columns = [ ] export default function ComputeJobs({ - minimal + minimal, + assetChainIds, + refetchJobs }: { minimal?: boolean + assetChainIds?: number[] + refetchJobs?: boolean }): ReactElement { - const { accountId, networkId } = useWeb3() + const { accountId } = useWeb3() const { asset } = useAsset() const { chainIds } = useUserPreferences() + const isMounted = useIsMounted() + const newCancelToken = useCancelToken() + const [isLoading, setIsLoading] = useState(false) const [jobs, setJobs] = useState([]) - const isMounted = useIsMounted() - - const columnsMinimal = [columns[4], columns[5], columns[3]] + const [columnsMinimal] = useState([columns[4], columns[5], columns[3]]) const fetchJobs = useCallback(async () => { if (!chainIds || chainIds.length === 0 || !accountId) { @@ -90,17 +89,22 @@ export default function ComputeJobs({ } try { setIsLoading(true) - // const jobs = await getComputeJobs(chainIds, accountId, ddo) - // isMounted() && setJobs(jobs.computeJobs) - // setIsLoading(jobs.isLoaded) + const jobs = await getComputeJobs( + assetChainIds || chainIds, + accountId, + asset, + newCancelToken() + ) + isMounted() && setJobs(jobs.computeJobs) + setIsLoading(!jobs.isLoaded) } catch (error) { LoggerInstance.error(error.message) } - }, [chainIds, accountId, asset, isMounted]) + }, [chainIds, accountId, asset, isMounted, assetChainIds, newCancelToken]) useEffect(() => { fetchJobs() - }, [fetchJobs]) + }, [fetchJobs, refetchJobs]) return accountId ? ( <> diff --git a/src/components/Publish/_constants.tsx b/src/components/Publish/_constants.tsx index 15460e440..5df7852b4 100644 --- a/src/components/Publish/_constants.tsx +++ b/src/components/Publish/_constants.tsx @@ -41,8 +41,8 @@ export const wizardSteps: StepContent[] = [ const computeOptions: ServiceComputeOptions = { allowRawAlgorithm: false, allowNetworkAccess: true, - publisherTrustedAlgorithmPublishers: null, - publisherTrustedAlgorithms: null + publisherTrustedAlgorithmPublishers: [], + publisherTrustedAlgorithms: [] } export const initialValues: FormPublishData = { diff --git a/src/components/Publish/_utils.ts b/src/components/Publish/_utils.ts index 503dd0856..2f396e1df 100644 --- a/src/components/Publish/_utils.ts +++ b/src/components/Publish/_utils.ts @@ -135,7 +135,8 @@ export async function transformPublishFormToDdo( : getAlgorithmContainerPreset(dockerImage).tag, checksum: dockerImage === 'custom' - ? dockerImageCustomChecksum + ? // ? dockerImageCustomChecksum + '' : getAlgorithmContainerPreset(dockerImage).checksum } }