OceanNodeClient
The client for a single ocean-node. One node serves metadata, provider services, the indexer and compute — replacing v1's separate Aquarius and Provider.
Usually you take the one the instance already built rather than constructing your own.
Usage
const = .()
const = await .()Constructing one directly:
const = new ({
: 'https://ocean-node.dev.pontus-x.eu',
: 32456,
:
})Options
| Option | Type | Purpose |
|---|---|---|
nodeUri | string | HTTP URL, or a peerId/multiaddr to use the libp2p transport. |
chainId | number | |
auth | NodeAuth | A Signer, a JWT auth token, or a pre-computed signature. |
consumerAddress | string | For a JWT auth: the address to name when a call needs one. Optional: by default it is read from the token's address claim, as ocean.js reads it (decoded, not verified). Required for a token without that claim. |
allowInsecureTransport | boolean | Accept a plain http:// nodeUri on a host other than localhost, 127.0.0.0/8, ::1 or *.localhost. Default false: the constructor throws an OceanNodeError, the same rule as Nautilus.create. It also applies to the nodeUri argument of encrypt and getFileInfo. Carried over to forEndpoint clients. |
requestTimeoutMs | number | How long one encrypt call may run, its nonce retry included; over HTTP also an initialize request, and the signing of a getComputeLogs request (the log request waits for the job's first output with no timeout; abort its signal to stop). Default 120000 (2 minutes, room for a wallet's signature prompt). Must be a positive finite number. Carried over to forEndpoint clients. |
logger | Logger | Where this client's log events go, e.g. one from createLogger({ sink }). Default: the process-wide logger (configureLogging); Nautilus passes its instance's. Carried over to forEndpoint clients. |
Selected methods
Metadata
resolve(did) · resolveMany(dids) · query(searchQuery) ·
waitForIndexer(did, txid?, options?) · getIndexingState({ did } | { nft } | { txId }, signal?) ·
getNodeAddress(signal?) · getIndexerNonceState(signal?) · validateRemote(ddo)
The GETs behind resolve, waitForIndexer, getIndexingState, getNodeAddress and
getIndexerNonceState carry no credentials and no body, so they follow redirects, for
example from a reverse proxy in front of /api/aquarius. Two rules still apply where a
redirect ends, and refuse it with an OceanNodeError before its answer is read: plain
http:// on a host other than loopback, unless the node URI is plain http:// already
(allowInsecureTransport); and a loopback, private or link-local host (10.0.0.0/8,
169.254.0.0/16, fc00::/7, a single-label or .internal name, …), unless the node is on
one itself. Only the final URL is checked; fetch does not show the hops in between.
initialize does not follow redirects: its query carries the consumer's address and
userdata.
resolve(did) throws an AssetNotFoundError (status 404, did) when the node does not
serve the asset. Only then does it read the node's indexing state for the DID (both DID
forms, in parallel, within one 15 s timeout), and when the node recorded an indexing failure
the message says so ("the node recorded an indexing error for tx …: …") and state holds
the record. Over P2P the node's Not found answer is an AssetNotFoundError too, without
state: the indexing state is served over HTTP only.
getIndexingState
The indexer's record for an asset or a transaction — the only place a node reports why an asset was not indexed.
const = await .({ : })
// or: { did: 'did:ope:…' } for the success record, { nft: '0x…' } for failure records
if ( && (!. || .?.())) .(.)Returns IndexingState | undefined — { did?, chainId?, nft?, txId?, valid, error? }, or
undefined when the node has no record (404). The query is an IndexingStateQuery,
{ did } | { nft } | { txId }, sent the way the node files it: nft checksummed and txId
lower-cased, so either spelling finds the record. It reads
GET <node>/api/aquarius/state/ddo, which is HTTP only, with a 15-second request timeout.
It throws an OceanNodeError on a P2P node URI, for a blank query value, an nft that is not
an address, a txId that is not a 32-byte 0x hash, a did that is not a did:op: or
did:ope: id, any other non-2xx answer, and a response that is not JSON or has no boolean
valid.
ocean-node 4.2 keeps two kinds of record per asset, under different ids:
| Record | Filed under | nft | txId | Look it up by |
|---|---|---|---|---|
| Success | the asset's did:ope: DID | — | " " (blank) | { did } |
| Failure | the old did:op: form of the id | the NFT | the transaction | { txId } (preferred) or { nft } |
- Failure records stay. A later successful edit does not remove them, so an
{ nft }lookup can return an old error for an asset that is indexed fine now. Prefer{ txId }, and compare the record'stxIdwith yours. - A failure is
valid: falsewith the node's message. A DDO dropped at the database write (schema validation) is recorded asvalid: truewith an error, so treat any non-blankerroras a failure.erroris a single space when there is none.
waitForIndexer reads the { txId } record for you and
throws an IndexingError on a failure of that transaction. OceanNodeClient.waitForIndexer
takes the same arguments and WaitForIndexerOptions as nautilus.waitForIndexer.
getNodeAddress
const = await .()
// '0x…' (checksummed), or undefinedThe node's own address, providerAddress from its root document (GET <node>/). The indexer
signs its decrypt calls with this key. Returns undefined when the node does not report a
valid address, or is reached over P2P. A network error, a timeout (15 s), a non-2xx status or
a body that is not JSON throws an OceanNodeError (operation getNodeAddress); aborting
signal rejects with the signal's reason.
getIndexerNonceState
const state = await node.getIndexerNonceState()
// { nodeAddress: '0x…', storedNonce: 264, nextNonce: 265, nodeVersion: '4.2.0', stuck: true },
// or undefined over P2PA read-only check (two GETs, nothing signed) of whether the node will accept its
indexer's next decrypt call. The indexer signs each decrypt call with the node's key and the
next nonce, and the stored nonce advances only when a call succeeds. stuck: true means the
node will not accept a call signed with nextNonce (401 consumer address and nonce signature mismatch): a publish or edit would go on chain without being indexed until the
node operator advances the stored nonce of the node's address.
isIndexerNonceSignable(nodeAddress, nonce, nodeVersion?) is the underlying check: before
ocean-node 4.2.2 a call whose signed message hash starts with a zero byte is refused; 4.2.2
and later accept it (nodeAcceptsZeroByteNonce(version)). nodeVersion is the version
the node reports in GET / (sanitised); without one, or for a pre-release of 4.2.2, the
zero-byte rule applies. The version is self-reported, and the fix is in the OE fork
(oe-ocean-node): an upstream ocean-node with the same number still refuses such a call.
When a zero-byte nonce is let through on the version alone, getIndexerNonceState() logs
node.indexer_nonce.zero_byte_accepted at info, once per node and nonce.
publish, completePublish and edit call it before their first transaction and throw an
IndexerNonceStuckError (an OceanNodeError carrying this state) when stuck; pass
{ checkIndexerNonce: false } to skip that. When the check fails (P2P, a node without the
nonce endpoint, a network error) they go on silently.
They also read getNodeAddress() once per instance and warn once
(publish.preflight.publisher_is_node) when the publisher is that same address: the node's decrypt calls then collide with the publisher's
nonces and fail with 401, so the asset goes on chain but is never indexed. Publish with a
different key than the node's.
Encryption
encrypt(data, policyServer?, signal?, nodeUri?) has the node encrypt data and returns its
0x hex ciphertext. nautilus uses it for service file objects, the DDO envelope and the
on-chain pointer. nodeUri defaults to the client's node; node keys are node-local, so a
service's files must be encrypted by the node in its serviceEndpoint.
-
Another
nodeUrigets the client's transport rule, since the plaintext and the signed command go to it: a plainhttp://URI on a non-loopback host throws anOceanNodeError(encrypt) before anything is sent, unless the client hasallowInsecureTransport.getFileInfo(file, withChecksum?, signal?, nodeUri?)does the same (getFileInfo). -
It throws an
OceanNodeError(encrypt) unless the node answers with0xhex: "the node returned an empty ciphertext", or "the node did not return ciphertext: …" with the node's message cut to 200 characters. ocean.js passes an HTTP error body through as if it were the result, so without this check a node error could end up stored as a service's encrypted files. -
With
Signerauth, it retries once when the node rejects the nonce ("not a valid nonce"). A JWT or a pre-computed signature is not retried, since it would fail the same way. -
With
Signerauth, one client runs its encrypt calls one at a time, because concurrent calls read the same nonce and the node rejects all but one. A publish with several services therefore encrypts their files one after the other. -
Each call has
requestTimeoutMs(default 2 minutes) from the moment it starts; time spent waiting behind other calls does not count. A call that runs out throws anOceanNodeError("timed out after … ms") and the next call starts, so one node that does not answer cannot hold up the client. Abortingsignal, while the call waits or runs, rejects with the signal's reason.
Files and storage
getFileInfo(file, withChecksum?) · checkDidFiles(did, serviceId, withChecksum?) ·
createBucket(accessLists, label?) · uploadFile(bucketId, fileName, content) ·
getFileObject(bucketId, fileName)
Buckets work for asset files, but not for DDOs: ocean-node 4.2 cannot read a remote DDO from its own bucket storage.
Compute
getComputeEnvironments() · initializeCompute(params) · computeStart(params) ·
freeComputeStart(params) · computeStatus(jobId?, agreementId?) · getComputeJob(jobId) ·
computeStop(jobId, agreementId?) · getComputeResultUrl(jobId, index) ·
getComputeResult(jobId, index, offset?) · getComputeLogs(jobId)
Each jobId is <environmentHash>-<jobId>, as computeStart and freeComputeStart
return it: exactly 0x and 64 hex digits, a dash, then 64 hex digits. Anything else throws
before the request, as does an index that is not a non-negative safe integer. Jobs come
back as NodeComputeJobs under that id, from computeStart, freeComputeStart,
computeStatus, getComputeJob and computeStop alike. getComputeJob returns
undefined when the node does not know the job, and getComputeLogs streams only while the
algorithm runs. Over HTTP, getComputeLogs is serialized with the client's other signed
commands (with a Signer, two calls in flight would sign the same nonce) and retried once
when the node rejects the nonce. ocean-node sends no headers until the job writes its first
output, so the request waits for them with no timeout, and holds the signing queue for at
most 10 s meanwhile; getComputeLogs(jobId, signal) stops on signal.
Identity
hasPolicyServer() · policySessionAddress(consumerAddress) ·
initializePolicyVerification(request) · checkPolicySession(sessionId) ·
policyServerPassthrough(action)
hasPolicyServer() reads isPSConfigured from the node's status, or what an initiate
showed (undefined when neither has said; upstream ocean-node 4.2.0 does not report it). A
"yes" is kept for the client's lifetime, a "no" for 10 minutes. policySessionAddress is
the address a session is bound to: the one the node forwards to the policy server, which for
a JWT is the token's own. initializePolicyVerification returns the policy server's reply,
or null for a node without a policy server (a 404 with no body); it throws a
PolicyDeniedError only for the policy server's refusal (a reply with success: false and
a 4xx), and an OceanNodeError with the status for everything else: the node's own 401
(retried once for a rejected nonce, with a Signer), 404 (Not found) and 400, a network
error, a timeout, a rate limit or a 5xx. Over HTTP it signs and sends the command itself, as
ocean.js signs it: ocean.js keeps a failed answer only when its body is JSON, and the node
answers its own errors in plain text. checkPolicySession reports
{ verified, policyResults } from the policy server's checkSessionId: per-policy names,
outcomes and errors, never the presentation. Policy-server answers are read up to 256 KiB.
forEndpoint(uri) returns one client per node (the URI normalised), shared by every client
derived from the same one, so parallel calls to a node share its signing queue and do not
sign the same nonce.
Diagnostics
getNodeStatus() · isValidNode(uri?) · OceanNodeClient.clearValidityCache()
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, which carries the node's answer in its
message, cause is a copy with the same name and its message sanitized like the node's
text (below), not the ocean.js error itself. For the requests nautilus sends itself (resolve, initialize,
getComputeLogs and the indexing reads over HTTP), the message carries the node's status
and text, unquoted, and status the HTTP status:
[ocean-node] initialize: HTTP 400 Bad Request: Use the initializeCompute endpoint to initialize compute jobsFor the calls that go through ocean.js, a JSON string or { error } answer is unwrapped
([ocean-node] freeComputeStart: Error: Access to asset … was denied); ocean.js passes on
no status, and nothing of a plain-text answer.
The node's text is sanitized before it goes into a message: ANSI escapes and control
characters (CR and LF included) are removed; the value of every key the log redaction
treats as secret (signatures, tokens such as vp_token and id_token, request_uri, a
code that is not an error code, nonce, session ids, passwords, API keys, …; see
configureLogging) is replaced by <redacted>, in a
query, a JSON field, escaped JSON or key: value prose, and so are JWTs and Bearer and
Basic credentials anywhere; a URL keeps its
origin only, so a policy server's redirect URI or an internal path is not relayed; and the
text is cut at 200 characters. An error answer's body is read up to 64 KB.
try {
await ()
} catch () {
if ( instanceof ) {
.(., .)
}
}IndexingError is the OceanNodeError that waitForIndexer throws when the node has
recorded a failure. It carries did, txId and the node's record on state.
AssetNotFoundError is the one resolve throws for an asset the node does not serve.
PolicyDeniedError is not an OceanNodeError: the node and its policy server answered, and
refused. It carries did, serviceId, consumerAddress (the address the request was sent
with: a Signer's own, whatever the request named), code (the HTTP status) and reason
(the policy server's message).