Skip to content
LogoLogo

Editing

Editing an asset means republishing its metadata: fetch the published document, change what you want, and write it back onto the same NFT.

The pattern

// 1. Fetch what is published.
const  = await .('did:ope:2a6ebce0af...') 
 
// 2. Change only what you want.
const  = new () 
  .('A better name') 
  .() 
 
// 3. Write it back.
const {  } = await .() 

Editing needs a remoteStore, exactly as publishing does — the DDO is signed, encrypted and stored off chain either way, in the same envelope format.

The builder must be constructed from a resolved asset. That is what supplies the baseline to merge over; passing a freshly built asset throws.

What is stripped before signing

datatokens, event and stats are derived by the indexer from chain state, proof is the signature the node attaches to every indexed DDO, and accessDetails, views and offchain are decorations some clients attach to a resolved asset. All of them are removed before the document is signed, so a republish cannot bake in a stale snapshot. You do not have to remember to delete them.

Editing metadata

const  = new ()
  .('Hourly Weather Observations, 2020–2026')
  .('Now covering six years')
  .(['revised'])
  .({ : 'CC-BY-4.0' }) 
  .()

created is preserved and updated is bumped for you.

Editing a service

Load the published service with the ServiceBuilder, change it, and add it back:

const  = ()[0].
 
const  = new <., .>({
  : , 
   
})
  .('Access Service, revised')
  .(7200)
  .()
 
const  = new ().().()

Services are matched by id, so this replaces the published one rather than adding a second.

Changing files changes the service id

A service's id is the hash of its encrypted file object. Replace the files — or the endpoint, since a different node must re-encrypt them — and the rebuilt service necessarily gets a new id. nautilus removes the old entry so the two do not linger side by side.

const  = new <., .>({
  : ,
  : ()[0].
})
  .({ 
    : 'url', 
    : 'https://data.example/observations-v2.csv', 
    : 'GET'
  }) 
  .()

Consumers holding an order against the old service id keep it, but new orders go to the new one.

The endpoint cannot change on its own: the files are encrypted for the node in the service's endpoint, so a loaded service whose setServiceEndpoint changed but which has no files added again fails with "Cannot encrypt files: no files were added to this service. Its serviceEndpoint changed, …". Add the files again with addFile() in the same edit.

Adding and removing services

const  = new <., .>({
  : .
})
  .('https://ocean-node.dev.pontus-x.eu')
  .('Compute')
  .({ : 'url', : 'https://data.example/x.csv', : 'GET' })
  .({ : 'free' }) // a new service needs its own datatoken
  .()
 
const  = new ()
  .() 
  .(()[0].) 
  .()

Only services carrying a new pricing config get a new datatoken. That is why setPricing is required on a genuinely new service and blocked on a loaded one.

Changing a price

Do not do it through the builder — a new pricing config mints a new datatoken and changes the service id. There is a dedicated call that just sets the rate on the existing exchange:

await .(, ()[0]., '2.5') 

Only works for fixed-rate services: a free service has a dispenser, which has no rate.

Editing compute options

const  = (, 'compute')?. as string
 
const  = new <., .>({
  : ,
  : 
})
  .(false)
  .([{ : 'did:ope:926098d069b017dcf...' }]) 
  .('0x0DB823218e337a...')
  .()
 
const  = new ().().()

Trusted algorithms are re-resolved to their current container and file checksums on every edit, so an algorithm whose container moved gets its pin updated.

Changing the lifecycle state

To retire or unlist an asset without touching anything else, one transaction is enough:

await .(, .) 

It resolves once the transaction is mined, and returns undefined without sending anything when the asset is already in that state. If you are changing metadata anyway, set it on the builder instead with setLifecycleState.

Without setLifecycleState, edit() writes the state the NFT has on chain at that moment, not the one in the asset you built from. So an edit built from a copy you fetched before unlisting the asset keeps it unlisted.

A lifecycle change does not update the indexed transaction id, so waitForIndexer(did, receipt.hash) cannot wait for it. Poll getAsset and getLifecycleState instead.

Waiting for the change to be visible

The metadata transaction confirms before ocean-node has indexed it, so an immediate re-read can still return the old document. Wait for your specific transaction:

const { ,  } = await .(
  new ().('Renamed').()
)
 
const  = await .( 
  . as string, 
  . 
) 

Passing the transaction hash matters here: without it, waitForIndexer is satisfied by the asset merely existing, which it already did. If the node rejects the update, the wait throws an IndexingError with the node's message rather than timing out.

One metadata event per block

ocean-node indexes only the first MetadataCreated or MetadataUpdated event of an asset in a block and ignores the rest, and nothing on chain shows that it dropped one.

Within one Nautilus instance this cannot happen. Its publish() (once the NFT is minted), completePublish(), edit() and setAssetLifecycleState() calls for one NFT run one after the other, and each waits for its receipt before the next one sends anything. edit() reads the NFT's lifecycle state and checks the signer's permissions only once its turn comes, so it sees a revoke this instance started before it. It does not wait for the previous change to be indexed; pass waitForIndexer (or call waitForIndexer) when you need to read it back.

Separate instances or processes writing to the same asset can still land in one block. So after every metadata transaction, nautilus reads that block's logs, and if it finds another metadata event for the same NFT it throws a MetadataConflictError:

try {
  await .()
} catch () {
  if (!( instanceof )) throw 
 
  . // the full PublishResponse: the transaction was mined
  . // the other metadata transactions in that block
  . // whether this one came first, so it is the one the node keeps
}

Write the metadata again with edit() to bring the chain and the index back in line. The check is best effort: when the logs cannot be read, nautilus logs a warning and returns normally.

Try it

Each edit on this page is a command in examples/:

npm start -- edit:metadata did:ope:… "New name"
npm start -- edit:service did:ope:… first "New service name"      # keeps the service id
npm start -- edit:service-files did:ope:… first https://…/v2.csv   # gives a new service id
npm start -- edit:service-allow did:ope:… first 0x… 0x…            # service-level allowlist
npm start -- edit:price did:ope:… 2.95
npm start -- edit:trusted-algorithms <datasetDid> <algoDid> [...]
npm start -- asset:unlist did:ope:…      # hide it from listings (reversible)