Skip to content

Commit

Permalink
feat: allow off-chain asset create and resolve
Browse files Browse the repository at this point in the history
  • Loading branch information
aaitor committed Aug 30, 2023
1 parent 4208330 commit bfb1fd7
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 66 deletions.
21 changes: 21 additions & 0 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

## Breaking changes

### Defining the asset registration options

The `assets.create` function now receives an `AssetPublicationOptions` object instead of a `PublishMetadataOptions` parameter. This object allows to define the asset off-chain and on-chain registration options:

```typescript
await nevermined.assets.create(assetAttributes, publisher, {
metadata: PublishMetadataOptions.OnlyMetadataAPI,
did: PublishOnChainOptions.OnlyOffchain,
})
```

### Defining Asset Price

The assetPrice is not part of AssetAttributes anymore and is part of each individual service added to the DDO when registering a service. So for services having a price, this must be added as part of the services array of the AssetAttributes.
Expand Down Expand Up @@ -73,3 +84,13 @@ Most of them were migrated to the `DDO` class:
- `getNftHolderFromService` -> `DDO.getNftHolderFromService`
- `getNftAmountFromService` -> `DDO.getNftAmountFromService`
- `getNftContractAddressFromService` -> `DDO.getNftContractAddressFromService`

## Not breaking changes

### Assets resolution will be off-chain by default

The `assets.resolve` function will now resolve the asset off-chain by default. This means that the DDO will be fetched from the metadata api and not retrieve the metadata url from the on-chain DIDRegistry. This behavior can be modified passing different `DIDResolvePolicy` options.

### Assets can be registered only off-chain

For assets not requiring the be registered on-chain, the `assets.create` function will now only register the asset off-chain. This behavior can be modified passing different `AssetPublicationOptions` options.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { config } from '../config'
import { Signer, Transaction, ethers } from 'ethers'
import { sleep } from '../utils/utils'

describe('Lazy registration of assets', () => {
describe.skip('Lazy registration of assets', () => {
let publisher: Account
let relayer: Account
let publisherSigner: Signer
Expand Down Expand Up @@ -64,7 +64,7 @@ describe('Lazy registration of assets', () => {
fragment = await registryContract[functionName].getFragment(...functionArgs)
assert.isDefined(fragment)

unsignedTx = await registryContract[functionName].populateTransaction(...functionArgs)
unsignedTx = await registryContract[functionName].populateTransaction(...functionArgs)
assert.isDefined(unsignedTx)

// unsignedTx.from = relayer.getId()
Expand All @@ -81,35 +81,36 @@ describe('Lazy registration of assets', () => {

const gasLimit = await registryContract[functionName].estimateGas(...functionArgs, {
from: relayer.getId(),
})
})

console.log(`Relayer ETH balance: `, await relayer.getEtherBalance())

const feeData = await nevermined.utils.contractHandler.getFeeData()
console.log(`Fee Data: `, feeData)
// const feeData = await nevermined.web3.getFeeData()

tx.chainId = await nevermined.keeper.getNetworkId()
tx.type = 2
tx.nonce = await relayerSigner.getNonce()
tx.gasLimit = gasLimit
// tx.value = ethers.parseEther("0.01")

if (Object.keys(feeData).includes('gasPrice')) {
tx.gasPrice = feeData['gasPrice']! // eslint-disable-line @typescript-eslint/no-non-null-assertion
} else {

// tx.maxFeePerGas = ethers.parseUnits(Math.ceil(Number(feeData['maxFeePerGas']!)) + '', 'wei')
tx.maxFeePerGas = feeData['maxFeePerGas']! // eslint-disable-line @typescript-eslint/no-non-null-assertion
// tx.maxPriorityFeePerGas = ethers.parseUnits(Math.ceil(Number(feeData['maxPriorityFeePerGas']!)) + '', 'wei')

// tx.maxPriorityFeePerGas = ethers.parseUnits(Math.ceil(Number(feeData['maxPriorityFeePerGas']!)) + '', 'wei')
tx.maxPriorityFeePerGas = feeData['maxPriorityFeePerGas']! // eslint-disable-line @typescript-eslint/no-non-null-assertion
}

console.log(`Deserialized tx: `, JSON.stringify(tx))

// const txResponse = await publisherSigner.sendTransaction(tx)
const txResponse = await nevermined.web3.broadcastTransaction(tx.serialized)
const txResponse = await relayerSigner.sendTransaction(tx)
// const provider = new ethers.JsonRpcProvider(config.web3ProviderUri, undefined, {cacheTimeout: -1})

// const txResponse = await nevermined.web3.broadcastTransaction(tx.serialized)

// const txResponse = await provider.sendTransaction(Transaction.from(signedTx))

Expand Down
51 changes: 50 additions & 1 deletion integration/nevermined/Assets.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import { config } from '../config'
import { getAssetPrice, getMetadata } from '../utils'
import { Nevermined, Account, MetaData, DDO, AssetPrice, AssetAttributes } from '../../src'
import { generateId } from '../../src/utils'
import { PublishMetadataOptions, DIDResolvePolicy } from '../../src/nevermined'
import {
PublishMetadataOptions,
DIDResolvePolicy,
PublishOnChainOptions,
} from '../../src/nevermined'
import { rejects } from 'assert'

let nevermined: Nevermined
let publisher: Account
Expand Down Expand Up @@ -425,4 +430,48 @@ describe('Assets', () => {
assert.equal(assets.totalResults.value, 0)
})
})

describe('#register() and #resolve() totally off-chain', () => {
let offchainDID

it('register an asset but just off-chain', async () => {
const nonce = Math.random()
createdMetadata = getMetadata(nonce, `Off-Chain Test ${nonce}`)

createdMetadata.main.ercType = 721
createdMetadata.additionalInformation.tags = ['offchain']

const assetAttributes = AssetAttributes.getInstance({
metadata: createdMetadata,
services: [
{
serviceType: 'access',
price: assetPrice,
},
],
})
const offchainDDO = await nevermined.assets.create(assetAttributes, publisher, {
metadata: PublishMetadataOptions.OnlyMetadataAPI,
did: PublishOnChainOptions.OnlyOffchain,
})

assert.isDefined(offchainDDO)
assert.equal(offchainDDO._nvm.versions.length, 1)

const metadata = offchainDDO.findServiceByType('metadata')
assert.equal(metadata.attributes.main.ercType, 721)
assert.equal(metadata.attributes.additionalInformation.tags[0], 'offchain')
offchainDID = offchainDDO.id
})

it('resolve from the metadata api', async () => {
const resolvedDDO = await nevermined.assets.resolve(offchainDID, DIDResolvePolicy.NoRegistry)
assert.isDefined(resolvedDDO)
assert.equal(resolvedDDO._nvm.versions.length, 1)
})

it('dont resolve from the DIDRegistry', async () => {
rejects(nevermined.assets.resolve(offchainDID, DIDResolvePolicy.MetadataAPIFirst))
})
})
})
3 changes: 2 additions & 1 deletion src/ddo/DDO.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,8 @@ export class DDO {
* @returns the DID
*/
public static getDIDFromService(service: Service): string {
return `did:nv:${DDO.getParameterFromCondition(service, 'escrowPayment', '_did') as string}`
const shortId = DDO.getParameterFromCondition(service, 'escrowPayment', '_did') as string
return shortId.startsWith('did:nv:') ? shortId : `did:nv:${shortId}`
}

/**
Expand Down
6 changes: 4 additions & 2 deletions src/nevermined/api/AssetsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,15 @@ export class AssetPublicationOptions {
* * ImmutableFirst - It checks if there is a reference to an immutable data-store (IPFS, Filecoin, etc) on-chain. If that's the case uses the URL to resolve the Metadata. If not try to resolve the metadata using the URL of the Metadata/Marketplace API
* * MetadataAPIFirst - Try to resolve the metadata from the Marketplace/Metadata API, if it can't tries to resolve using the immutable url
* * OnlyImmutable - Try to resolve the metadata only from the immutable data store URL
* * OnlyMetadataAPI - Try to resolve the metadata only from the Marketplace/Metadata API
* * OnlyMetadataAPI - Try to resolve the metadata only from the Metadata API. It gets the metadata api url from the DIDRegistry
* * NoRegisry - Gets the metadata from the Metadata API using as endpoint the metadata api url from the SDK config. This method don't gets any on-chain information because assumes the DID is not registered on-chain
*/
export enum DIDResolvePolicy {
ImmutableFirst,
MetadataAPIFirst,
OnlyImmutable,
OnlyMetadataAPI,
NoRegistry,
}

/**
Expand Down Expand Up @@ -131,7 +133,7 @@ export class AssetsApi extends RegistryBaseApi {
*/
public async resolve(
did: string,
policy: DIDResolvePolicy = DIDResolvePolicy.MetadataAPIFirst,
policy: DIDResolvePolicy = DIDResolvePolicy.NoRegistry,
): Promise<DDO> {
return this.resolveAsset(did, policy)
}
Expand Down
111 changes: 59 additions & 52 deletions src/nevermined/api/RegistryBaseApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,69 +219,71 @@ export abstract class RegistryBaseApi extends Instantiable {
observer.next(CreateProgressStep.RegisteringDid)

// On-chain asset registration
if (nftAttributes) {
this.logger.log('Registering Mintable Asset', ddo.id)

const nftAttributesWithoutRoyalties = { ...nftAttributes, royaltyAttributes: undefined }

if (nftAttributes.ercType === 721) {
await didRegistry.registerMintableDID721(
didSeed,
nftAttributes.nftContractAddress,
checksum,
assetAttributes.providers || [this.config.neverminedNodeAddress],
publisher.getId(),
nftAttributesWithoutRoyalties,
serviceEndpoint,
ddoVersion.immutableUrl,
DEFAULT_REGISTRATION_ACTIVITY_ID,
txParams,
)
if (publicationOptions.did != PublishOnChainOptions.OnlyOffchain) {
if (nftAttributes) {
this.logger.log('Registering Mintable Asset', ddo.id)

const nftAttributesWithoutRoyalties = { ...nftAttributes, royaltyAttributes: undefined }

if (nftAttributes.ercType === 721) {
await didRegistry.registerMintableDID721(
didSeed,
nftAttributes.nftContractAddress,
checksum,
assetAttributes.providers || [this.config.neverminedNodeAddress],
publisher.getId(),
nftAttributesWithoutRoyalties,
serviceEndpoint,
ddoVersion.immutableUrl,
DEFAULT_REGISTRATION_ACTIVITY_ID,
txParams,
)
} else {
await didRegistry.registerMintableDID(
didSeed,
nftAttributes.nftContractAddress,
checksum,
assetAttributes.providers || [this.config.neverminedNodeAddress],
publisher.getId(),
nftAttributesWithoutRoyalties,
serviceEndpoint,
ddoVersion.immutableUrl,
DEFAULT_REGISTRATION_ACTIVITY_ID,
txParams,
)
}

if (nftAttributes.royaltyAttributes != undefined) {
this.logger.log(`Setting up royalties`)

observer.next(CreateProgressStep.SettingRoyaltyScheme)
await didRegistry.setDIDRoyalties(
ddo.shortId(),
nftAttributes.royaltyAttributes.scheme.address,
publisher.getId(),
txParams,
)
observer.next(CreateProgressStep.SettingRoyalties)
await nftAttributes.royaltyAttributes.scheme.setRoyalty(
ddo.shortId(),
nftAttributes.royaltyAttributes.amount,
publisher,
txParams,
)
}
} else {
await didRegistry.registerMintableDID(
this.logger.log('Registering Asset', ddo.id)
await didRegistry.registerDID(
didSeed,
nftAttributes.nftContractAddress,
checksum,
assetAttributes.providers || [this.config.neverminedNodeAddress],
publisher.getId(),
nftAttributesWithoutRoyalties,
serviceEndpoint,
ddoVersion.immutableUrl,
DEFAULT_REGISTRATION_ACTIVITY_ID,
txParams,
)
}

if (nftAttributes.royaltyAttributes != undefined) {
this.logger.log(`Setting up royalties`)

observer.next(CreateProgressStep.SettingRoyaltyScheme)
await didRegistry.setDIDRoyalties(
ddo.shortId(),
nftAttributes.royaltyAttributes.scheme.address,
publisher.getId(),
txParams,
)
observer.next(CreateProgressStep.SettingRoyalties)
await nftAttributes.royaltyAttributes.scheme.setRoyalty(
ddo.shortId(),
nftAttributes.royaltyAttributes.amount,
publisher,
txParams,
)
}
} else {
this.logger.log('Registering Asset', ddo.id)
await didRegistry.registerDID(
didSeed,
checksum,
assetAttributes.providers || [this.config.neverminedNodeAddress],
publisher.getId(),
serviceEndpoint,
ddoVersion.immutableUrl,
DEFAULT_REGISTRATION_ACTIVITY_ID,
txParams,
)
}

this.logger.log('Storing DDO', ddo.id)
Expand Down Expand Up @@ -312,6 +314,11 @@ export abstract class RegistryBaseApi extends Instantiable {
did: string,
policy: DIDResolvePolicy = DIDResolvePolicy.MetadataAPIFirst,
): Promise<DDO> {
// We compose the metadata api url using the SDK config, we don't retrieve any DID information from the DIDRegistry
if (policy === DIDResolvePolicy.NoRegistry) {
return await this.nevermined.services.metadata.retrieveDDO(did, undefined)
}

const { serviceEndpoint, immutableUrl } =
await this.nevermined.keeper.didRegistry.getAttributesByDid(did)

Expand Down

0 comments on commit bfb1fd7

Please sign in to comment.