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:
| Form | When |
|---|---|
Signer | The default. Each call does a nonce-and-sign round trip. |
| JWT auth token | Skips 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 signature | You 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 nonftand a blanktxId(" "). Find them with?did=did:ope:…. - Failure records are filed under the old
did:op:form of the id, withnftand the realtxId. 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'stxIdis 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).