Publishing
Publishing an asset means creating a data NFT, minting a datatoken for each service, and writing the asset's metadata onto the NFT.
What is different in v2
Three changes matter when you come from v1:
- The DDO is signed, encrypted and stored off chain. Only an encrypted
{ remote }pointer goes on chain, so publishing needs a remote store. This follows the enterprise-market model and gives the asset a real issuer. Everything is always node-encrypted; there is no plaintext mode. - DDO v5. Assets are W3C Verifiable Credentials.
providedByis now required, anddescriptionandlicensechanged shape — though the builder still takes plain strings. - One transaction instead of three. The NFT, the first service's datatoken and that service's pricing are created together.
Set up an instance with a remote store
The remote store holds the encrypted DDO envelope. Use IpfsRemoteStore with any IPFS upload
endpoint, here Pinata with the JWT from the environment:
import { , } from 'ethers'
import { , } from '@deltadao/nautilus'
declare const : { : <string, string | undefined> }
const = new ('0x...', new ('https://rpc.dev.pontus-x.eu'))
const = { : 'https://ocean-node.dev.pontus-x.eu' }
const = await .(, {
,
: new ({
: 'https://api.pinata.cloud/pinning/pinFileToIPFS',
: { : `Bearer ${..}` },
: 'upload',
: 'https://gateway.pinata.cloud'
})
})probe: 'upload' makes publish() test the store before it sends any transaction, and
gatewayUrl is where it reads the stored envelope back before the metadata transaction
(Pinata needs one; a Kubo node is read back without). For a
Kubo node, other pinning services, S3, and why the node's own storage cannot hold DDOs, see
remote stores.
Build a service
A service is what consumers actually order. It needs an endpoint, at least one file, and a pricing scheme.
import {
,
,
} from '@deltadao/nautilus'
const = new <., .>({
: .
})
.('https://ocean-node.dev.pontus-x.eu')
.('Access Service')
.(86400)
.({
: 'url',
: 'https://data.example/observations.csv',
: 'GET'
})
.({ : 'free' })
.()Files are encrypted by the node before publishing, so their URLs and any credentials in them never appear on chain.
timeout is how long an order stays valid, in seconds — 0 never expires. Within it,
nautilus reuses an existing order instead of buying another datatoken.
Build the asset
const = new ()
.('dataset')
.('Hourly Weather Observations')
.('One row per hour, comma separated')
.('deltaDAO')
.('deltaDAO AG') // required by DDO v5
.('https://market.oceanprotocol.com/terms')
.(['weather', 'timeseries'])
.('Weather Data NFT')
.('WEATHER-NFT')
.()
.()setDescription takes a plain string and nautilus wraps it into DDO v5's language-tagged
form. Use setContentLanguage to change the
tag from the en/ltr default.
Publish
const { , , } = await .(, {
: true
})
.('published', .)waitForIndexer blocks until ocean-node has indexed the asset, so the DID is immediately
resolvable. Without it, publish returns as soon as the metadata transaction confirms — which
is earlier than the indexer has seen it.
If the node records that it could not index the asset, the wait throws an
IndexingError with the node's message instead of running into its timeout; if the node
simply has not indexed it within the timeout, it throws an OceanNodeError. Pass
{ waitForIndexer: { timeoutMs: 600_000 } } to wait longer than the 5-minute default. See
waitForIndexer.
If a publish stops halfway
The DID derives from the NFT address, so the NFT has to be minted before the DDO can be
stored. If anything fails after the mint — the store, the node, the RPC — publish() throws a
PublishIncompleteError carrying the nftAddress. Finish the publish on that NFT instead of
minting a new one:
try {
await .()
} catch () {
if (!( instanceof )) throw
// The same asset, or one rebuilt the same way: the datatokens already on the NFT are reused.
await .(., )
}Before any transaction, completePublish checks that the signer owns the NFT, that the
configured factory created it, that the signer holds the permissions the writes need, and
that every reused datatoken that already has pricing is priced the way its service says. It
also refuses while the signer has pending transactions: when the error's stored.cleanup is
'kept', the metadata transaction was sent and may still be mined. Pass
{ metadataTxHash: error.stored.txHash } to have that exact transaction checked too. See
completePublish.
Once the metadata transaction is mined, nothing is left to complete. A wait for the indexer
that fails, or a MetadataConflictError (another metadata transaction for the NFT in the
same block), still carries the whole result as error.published — see
PublishResponse.
Publishing an algorithm
An algorithm is an asset of type algorithm carrying container metadata, served by a
compute service:
const = new <., .>({
: .
})
.('https://ocean-node.dev.pontus-x.eu')
.({ : 'free' })
.({ : 'url', : 'https://example.org/algo.js', : 'GET' })
.()
const = new ()
.('algorithm')
.('Branin Regression')
.('Fits a model to the input data')
.('deltaDAO AG')
.({
: 'python',
: '0.1.0',
: {
: 'python $ALGO',
: 'oceanprotocol/algo_dockers',
: 'python-branin',
: 'sha256:8f36c4b2b7b4b0e6b0d0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0'
}
})
.()
.()An algorithm's service is published without a compute block: allowRawAlgorithms,
allowAlgorithmNetworkAccess and the trusted-algorithm settings say which algorithms may run
on a dataset, and ocean-node reads them from the dataset's service only.
Multiple services on one asset
Useful for offering an open preview alongside a restricted full dataset, or both download and compute access to the same data. The first service is bundled with the NFT; each further one gets its own datatoken on the same NFT.
const = new ()
.('dataset')
.('Weather Observations')
.('deltaDAO AG')
.()
.()
.()Try it
Every shape above is a runnable command in examples/:
npm start -- store:check # test the DDO store first, without a transaction
npm start -- publish:access-dataset # a downloadable dataset, allowlisted to the publisher and consumer
npm start -- publish:compute-dataset # a dataset algorithms can run against
npm start -- publish:saas # a SaaS offer
npm start -- publish:multi-service # one NFT carrying two services
npm start -- publish:validate # build and validate, without publishing
npm start -- publish:resume publish:access-dataset 0xNFT… 0xDatatoken… # finish a stopped publishNext steps
- Editing — change a published asset
- Download — consume one
- Credential-gated assets — require verifiable credentials