Skip to content
LogoLogo

compute

Start a paid Compute-to-Data job.

Usage

const {  } = await .({ 
  : { : 'did:ope:1234abcd...' }, 
  : { : 'did:ope:5678wxyz...' }, 
  // the most the job may lock in escrow, and pay in provider fees
  : { : , : ('5', 18) }, 
  : { : , : ('0.5', 18) } 
}) 
 
const  = [0].

What happens in order:

  1. Every input is resolved and its compute service selected. Each input's userdata is checked against its service's consumer parameters, and the algorithm's algocustomdata against the parameters in its algorithm metadata, as for access: a mismatch throws a ConsumerParameterError before the environment is read or anything is sent. Only the checked values are sent on, without their undefined or null keys.
  2. On a node with a policy server, a policy-server session is opened for every input with credentials, datasets and algorithm alike, each for its own asset and service (see credential-gated assets). A refusal aborts the whole job with nothing spent — the reason this comes first. The same sessions go to initializeCompute and computeStart.
  3. initializeCompute quotes the job: provider fees, reusable orders and the escrow amount. The provider fee of every input that will be ordered is checked locally, as for access: a fee that is missing, lacks a field, or that the datatoken would reject throws a ProviderFeeSignatureError before escrow or any order. A compute fee is the same on every request, so initializeCompute is called once and a bad fee is refused at once, not requested again; contact the node operator if this happens.
  4. Everything the quote would spend is checked against what you allowed, before the first transaction:
    • the inputs' non-zero provider fees, summed per token, against maxProviderFee, or confirmProviderFees asked once with all of them (ProviderFeeNotAllowedError);
    • the escrow quote: a quote of zero funds nothing and is not checked further, so a free environment may leave its escrow address out. Otherwise its contract must be the pinned escrow contract (your config.escrow, else the chain's EnterpriseEscrow, else its Escrow), and its chain, token and payee the job's own (EscrowPaymentNotAllowedError, which no callback overrides); its amount must be within maxEscrowPayment, or confirmEscrowPayment must return true (EscrowPaymentNotAllowedError again);
    • every input's order is planned: its pricing is read from chain, and pricing that cannot be ordered (no active exchange or dispenser, or a chain config without the contract it needs) throws, as does a payer other than the signer;
    • the wallet must hold what has to be deposited.
  5. Escrow is funded in exact amounts: nautilus approves the escrow contract for the shortfall only (what the job needs less what is already available in escrow), deposits it, and authorises the environment's account to lock the job's amount on top of its current locks. A standing allowance or authorisation that already covers the job is left as it is. Nothing is approved for more than is deposited.
  6. Each input is ordered as planned in step 4 — reusing an existing order where the node says one is valid — allowed to pay exactly the provider fee approved there. compute() does not look previous orders up on chain the way access() does, so it has no orderOnLookupFailure option and does not throw an OrderCheckError. In practice nothing is reused: ocean-node reports a valid order only for a transferTxId it is sent, and compute() sends none, so every input that costs anything is bought again on every job (see the pricing guide and known limitations).
  7. The job starts. The node locks the job's payment in escrow when it accepts the job.

Steps 5 to 7, with the escrow reads they depend on, run one job at a time per chain, payer, payment token and environment account within one Nautilus instance (see Concurrent jobs).

Choosing an environment

v1 silently used the node's first environment. Prefer choosing deliberately, since resources, duration limits and fees differ:

const  = await .()
const  = [0]
 
const {  } = await .({
  : { : 'did:ope:1234abcd...' },
  : { : 'did:ope:5678wxyz...' },
  : ., 
  : [ 
    { : 'cpu', : 2 }, 
    { : 'ram', : 2 } 
  ] 
})

Returns

  • Type: ComputeResult

Properties

jobs
  • Type: NodeComputeJob[]

The started jobs, with the node's environment. Poll them with getComputeStatus. Each jobId is <environmentHash>-<jobId>, as the node names a started job; every job method takes it in that form.

environment
  • Type: ComputeEnvironment

The environment the job actually runs in, including the one nautilus picked for you.

initializeResults
  • Type: ProviderComputeInitializeResults

What the node quoted: per-input provider fees and reusable orders, plus the escrow payment.

orders
  • Type: Record<string, string>

The order transaction backing each input, keyed by did#serviceId so two inputs on the same asset keep separate orders.

Parameters

config

  • Type: ComputeConfig

Properties

dataset
  • Type: ComputeAssetRef

{ did, serviceId?, userdata? }. Defaults to the asset's first compute service. userdata must fit the service's consumer parameters.

algorithm
  • Type: ComputeAlgorithmRef

ComputeAssetRef plus algocustomdata? and envs? (environment variables for the container). userdata must fit the algorithm service's consumer parameters, and algocustomdata those of its algorithm metadata.

additionalDatasets (optional)
  • Type: ComputeAssetRef[]

Extra inputs. C2D v2 passes every dataset in one array; nautilus assembles that for you.

computeEnv (optional)
  • Type: string

Environment id. Defaults to the node's first, which is logged when it happens.

resources (optional)
  • Type: ComputeResourceRequest[]

[{ id: 'cpu', amount: 2 }]. Ids come from ComputeEnvironment.resources. A non-empty list is sent exactly as given: ocean-node gives each resource missing from it its minimum, usually 0, and 0 CPU or RAM means the job runs without that limit, so list every resource you want bounded. Without resources, or with an empty list, every resource the environment lists is requested at its minimum, raised to 1 for cpu, ram and disk within the resource's maximum; other resources, such as GPUs, at their minimum.

paymentToken (optional)
  • Type: string

Must be a token the environment prices for this chain — fees is keyed by chain id. Defaults to the first accepted token; an unaccepted one fails with the list of accepted ones.

maxJobDuration (optional)
  • Type: number

Seconds. Capped to the environment's maxJobDuration, with a warning when it is (compute.duration.capped, once per node, environment, requested duration and limit).

output (optional)
  • Type: ComputeOutput

{ remoteStorage?, encryption? }. v1's hardcoded publishAlgorithmLog and publishOutput flags no longer exist.

outputBucketId (optional)
  • Type: string

Store results in an ocean-node bucket instead. Mutually exclusive with output.

additionalViewers (optional)
  • Type: string[]

Extra addresses allowed to read the results.

metadata (optional)
  • Type: Record<string, string | number | boolean>
queueMaxWaitTime (optional)
  • Type: number

Seconds to wait in the queue when resources are unavailable.

maxProviderFee (optional)
  • Type: TokenAmount | TokenAmount[], where TokenAmount is { token: string; amount: bigint | string }
  • Default: the Nautilus.create option, else none

The most the job may pay in provider fees, per token, in the token's smallest unit: the sum of every input's fee in that token. See access.

confirmProviderFees (optional)
  • Type: (fees: ProviderFeeQuote[]) => boolean | Promise<boolean>
  • Default: the Nautilus.create option, else none

Asked once, with every input's non-zero fee, when maxProviderFee does not cover them. Return true to pay them all.

maxProviderFee and confirmProviderFees replace the Nautilus.create defaults as a pair: set either one here, even to undefined, and neither default applies to this call. A tight maxProviderFee therefore never falls back to a permissive default callback.

maxEscrowPayment (optional)
  • Type: TokenAmount | TokenAmount[]
  • Default: the Nautilus.create option, else none

The most the job may lock in escrow, per payment token, in the token's smallest unit (the node quotes payment.amount in those units). Without it or confirmEscrowPayment, a paid job is refused with an EscrowPaymentNotAllowedError before anything is spent; a quote of zero needs neither.

confirmEscrowPayment (optional)
  • Type: (payment: EscrowPaymentQuote) => boolean | Promise<boolean>
  • Default: the Nautilus.create option, else none

Asked when maxEscrowPayment does not cover the quote, with { escrowAddress, token, amount, payee, minLockSeconds, chainId } (amount a bigint). Return true to fund it. It is asked only after the escrow contract, chain, token and payee passed their checks.

maxEscrowPayment and confirmEscrowPayment replace the Nautilus.create defaults as a pair, like the provider-fee options.

Escrow contract

nautilus funds exactly one escrow contract per chain, chosen without the node:

  1. config.escrow, when you pass it to Nautilus.create: that contract, and no other.
  2. Otherwise the chain's EnterpriseEscrow in Ocean's address data (the addresses ocean.js ships, or the file ADDRESS_FILE names). This is the contract ocean-node 4.2.0 uses wherever the address data has one: OP Sepolia, Optimism, Sepolia and Ethereum mainnet among the bundled chains, and the local stack.
  3. Otherwise the chain's Escrow in the address data, on a chain that lists no EnterpriseEscrow.

A quote naming any other contract is refused with EscrowPaymentNotAllowedError ('escrow-address'), whose message names the expected contract and the rule that chose it. No callback overrides this.

On a chain whose address data lists neither (Pontus-X devnet, 32456), paid compute is refused ('unknown-escrow') until you set the chain's EnterpriseEscrow contract as config.escrow. nautilus ships no Pontus-X escrow address; ask the network operator for it.

compute is also exported as a standalone function, compute(config, context). Its context.escrow plays the part of config.escrow: set it to pin a contract, or leave it out for the default above. context.chainConfig.escrow is not read.

Concurrent jobs

Funding escrow sets absolute values read from the chain: the authorisation becomes the amount already locked plus this job's, and the lock count one more than the current one. Two jobs planned from the same reading would each authorise only their own amount, and the node would then refuse the second job's lock after its orders were paid.

So each Nautilus instance runs paid jobs that share a chain, payer, payment token and environment account one at a time, from the escrow reads to computeStart. A job waits for the previous one's lock before it reads escrow, so it plans from what that job left. The checks and the order planning in step 4 run before the job waits, and jobs for another environment, token or payer do not wait at all.

This covers one instance. Jobs started by another Nautilus instance, another process or another machine with the same wallet are not serialised: run them one after the other, or from one instance. The standalone compute(config, context) serialises only when given a lock as context.escrowLock.

Throws

  • ConsumerParameterError, as for access: an input's userdata or the algorithm's algocustomdata does not fit the declared consumer parameters, or a select among them declares no usable options ('invalid-declaration'). error.did, error.serviceId and error.field name the input.
  • PolicyDeniedError, CredentialPresentationError, ProviderFeeSignatureError and ProviderFeeNotAllowedError, as for access, and an Error when an input asks for a verifiable presentation and no credential provider is set. A refused presentation names the failed policies of the input whose session it was (error.did, error.serviceId).
  • EscrowPaymentNotAllowedError, with error.reason:
    • 'unknown-escrow': no escrow contract is pinned for the chain: no config.escrow, and no EnterpriseEscrow or Escrow in Ocean's address data. Set the chain's EnterpriseEscrow as config: { escrow: '0x…' } in Nautilus.create;
    • 'escrow-address': the node named another contract than the pinned one;
    • 'mismatch': the quote is for another chain, token or payee, or its amount is not an exact non-negative integer (a malformed amount is refused even without an escrow address);
    • 'no-limit', 'over-limit' or 'declined': the amount was not allowed.
  • An Error when an input's pricing cannot be ordered, or the payer is not the signer.
  • An Error when the wallet cannot cover the deposit.

Not an OrderCheckError: that one is thrown by access when its on-chain lookup of a previous order fails. compute() makes no such lookup.

All of these are thrown before any approval, deposit or order.