Skip to content
LogoLogo

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:

  1. 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.
  2. DDO v5. Assets are W3C Verifiable Credentials. providedBy is now required, and description and license changed shape — though the builder still takes plain strings.
  3. 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 publish

Next steps