Skip to content
LogoLogo

Ocean Node client

The escape hatch under every nautilus call

In v1 there were two services: Aquarius for metadata and Provider for everything else. v2 talks to a single ocean-node, which serves metadata, provider services, the indexer and compute — which is why metadataCacheUri and providerUri collapsed into one oceanNodeUri.

Most of the time nautilus wraps this for you. When it does not, take the client.

const  = .() 
 
const  = await .()

Which node

nautilus 2.0.0-beta.1 works with OceanProtocolEnterprise ocean-node 4.2.x, unmodified. Upstream oceanprotocol/ocean-node 4.0 and 4.2 do not resolve remote DDOs, so they do not index assets published with nautilus.

With an IPFS remote store, the node also needs an IPFS gateway (IPFS_GATEWAY) that can fetch your store's content — see remote stores.

Do not publish with the node's own key. The indexer signs its decrypt calls with the node's key (providerAddress), so with the same key as publisher their nonces collide, decrypt fails with 401, and the asset goes on chain without ever being indexed. publish, completePublish and edit warn once when they see it; getNodeAddress() returns the address.

Checking what you are pointed at

Worth doing first when something behaves unexpectedly — a v1 Provider and an ocean-node answer on the same port but are not the same thing.

const  = .()
 
const  = await .() 
const  = await .() 

Authentication

The client accepts three forms of auth, expressed as NodeAuth:

FormWhen
SignerThe default. Each call does a nonce-and-sign round trip.
JWT auth tokenSkips that round trip. The consumer address is read from the token's address claim, as ocean.js reads it; pass consumerAddress for a token without one.
Pre-computed signatureYou already hold { consumerAddress, signature, nonce }.

Minting a token is worth it for anything doing many calls:

const  = await (, ) 
 
// … many calls later
await (, , ) 

Calls nautilus does not wrap

Files

// What the node can see about a file, before you publish it.
const  = await .(
  { : 'url', : 'https://data.example/x.csv', : 'GET' } as never
)
 
// Whether a published asset's files are still readable.
const  = await .('did:ope:1234abcd...', 'service-id') 

checkDidFiles is the quickest way to find out whether an asset has rotted — a file server that went away shows up here rather than at download time.

Buckets

The node's persistent storage, which is what NodePersistentRemoteStore uses underneath. Buckets work for asset files (nodePersistentStorage file objects), but not for DDOs: ocean-node 4.2 resolves a remote DDO without a consumer address, and a bucket refuses that read.

const  = await .([], 'my-bucket') 
 
// `content` is a request body stream, not a string — a web `ReadableStream`,
// a Node stream or a `Blob` all work.
await .(, 'notes.txt', new (['hello']).()) 
 
const  = await .(, 'notes.txt')

Indexing state

When an asset is on chain but getAsset says "DDO not found", the node has usually recorded why. Its indexing state is the only place that shows it — nothing appears on chain.

const  = await .({ :  }) 
// e.g. { did: 'did:op:…', nft: '0x…', txId: '0x…', valid: false, error: 'Provider exception on decrypt DDO. …' }
 
const  = await .({  }) 
// e.g. { did: 'did:ope:…', txId: ' ', valid: true, error: ' ' }

This is GET <node>/api/aquarius/state/ddo?txId=<hash> (or ?did=<did:ope:…>, or ?nft=<address>), so curl works too — but the node matches the values exactly, as it filed them: nft checksummed, txId lower-case. getIndexingState normalizes both for you and rejects a malformed nft, txId or did. The node keeps two kinds of record under different ids:

  • Success records are filed under the asset's did:ope: DID, with no nft and a blank txId (" "). Find them with ?did=did:ope:….
  • Failure records are filed under the old did:op: form of the id, with nft and the real txId. Find them with ?txId= (preferred) or ?nft=.
  • Failure records stay. After a later successful edit, ?nft= can still return the old error. Check that the record's txId is the transaction you care about.

Treat valid: false or any non-blank error as a failure — a DDO dropped by schema validation is recorded as valid: true plus an error.

waitForIndexer reads the record for its transaction for you, and throws an IndexingError with the node's message. See how the node resolves your DDO for what it checks.

Remote validation

Local validation is what publish runs. If you also want the node's opinion:

const  = await .() 

Errors

Every call is wrapped, so failures arrive as OceanNodeError with the operation named and the error behind it on cause (for an ocean.js error, a sanitized copy). For a request nautilus sends itself (see below), the message carries the node's HTTP status and text, unquoted, and status the status, e.g. [ocean-node] computeStreamableLogs: HTTP 404: Job not found or not running. Two subclasses carry more: IndexingError, thrown by waitForIndexer when the node recorded an indexing failure, and AssetNotFoundError, thrown by resolve (and getAsset) when the node does not serve the asset, with the node's indexing error for the DID when it recorded one.

try {
  await ()
} catch () {
  if ( instanceof ) {
    .(., ., .) 
  }
}

initialize and getComputeLogs are sent by nautilus itself over HTTP, so their errors always carry the node's status and text. The other calls go through ocean.js, which reads an error answer as JSON: a JSON answer's message is unwrapped, but for a plain-text answer ocean.js passes on neither the status nor the text, and the message says so. ocean.js also logs such a failed call to the console at its default level (LogLevel.Error), with the request payload; it redacts signatures and authorization headers.

Whatever the node says is sanitized before it reaches a message: control characters and ANSI escapes are removed, credentials (signatures, tokens, JWTs, nonces, codes, passwords, API keys) are redacted, URLs keep their origin only, and the text is cut at 200 characters.

Building one directly

You rarely need to — Nautilus.create builds it — but the constructor is public.

const  = new ({
  : 'https://ocean-node.dev.pontus-x.eu',
  : 32456,
  : 
})

nodeUri also accepts a peerId or multiaddr, which switches it to the libp2p transport. A plain http:// nodeUri on a host other than localhost, 127.0.0.0/8, ::1 or *.localhost throws an OceanNodeError, as in Nautilus.create, unless you pass allowInsecureTransport: true; forEndpoint applies the same rule to the other node, and encrypt and getFileInfo to their nodeUri argument (a service's serviceEndpoint).