Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: proper resolutions APIs for AssetDIDs #714

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/asset-did/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"bugs": "https://github.com/KILTprotocol/sdk-js/issues",
"homepage": "https://github.com/KILTprotocol/sdk-js#readme",
"dependencies": {
"@kiltprotocol/did": "workspace:*",
"@kiltprotocol/types": "workspace:*",
"@kiltprotocol/utils": "workspace:*"
}
Expand Down
49 changes: 3 additions & 46 deletions packages/asset-did/src/AssetDid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,52 +5,9 @@
* found in the LICENSE file in the root directory of this source tree.
*/

import type {
AssetDidUri,
Caip19AssetId,
Caip19AssetInstance,
Caip19AssetNamespace,
Caip19AssetReference,
Caip2ChainId,
Caip2ChainNamespace,
Caip2ChainReference,
} from '@kiltprotocol/types'
import { SDKErrors } from '@kiltprotocol/utils'
import type { AssetDidUri } from '@kiltprotocol/types'

// Matches AssetDIDs as per the [AssetDID specification](https://github.com/KILTprotocol/spec-asset-did).
const ASSET_DID_REGEX =
/^did:asset:(?<chainId>(?<chainNamespace>[-a-z0-9]{3,8}):(?<chainReference>[-a-zA-Z0-9]{1,32}))\.(?<assetId>(?<assetNamespace>[-a-z0-9]{3,8}):(?<assetReference>[-a-zA-Z0-9]{1,64})(:(?<assetInstance>[-a-zA-Z0-9]{1,78}))?)$/

type IAssetDidParsingResult = {
uri: AssetDidUri
chainId: Caip2ChainId
chainNamespace: Caip2ChainNamespace
chainReference: Caip2ChainReference
assetId: Caip19AssetId
assetNamespace: Caip19AssetNamespace
assetReference: Caip19AssetReference
assetInstance?: Caip19AssetInstance
}

/**
* Parses an AssetDID uri and returns the information contained within in a structured form.

* @param assetDidUri An AssetDID uri as a string.
* @returns Object containing information extracted from the AssetDID uri.
*/
export function parse(assetDidUri: AssetDidUri): IAssetDidParsingResult {
const matches = ASSET_DID_REGEX.exec(assetDidUri)?.groups
if (!matches) {
throw new SDKErrors.InvalidDidFormatError(assetDidUri)
}

const { chainId, assetId } = matches as Omit<IAssetDidParsingResult, 'uri'>

return {
...(matches as Omit<IAssetDidParsingResult, 'uri'>),
uri: `did:asset:${chainId}.${assetId}`,
}
}
import { resolve } from './Resolver.js'

/**
* Checks that a string (or other input) is a valid AssetDID uri.
Expand All @@ -63,5 +20,5 @@ export function validateUri(input: unknown): void {
throw new TypeError(`Asset DID string expected, got ${typeof input}`)
}

parse(input as AssetDidUri)
resolve(input as AssetDidUri)
}
60 changes: 60 additions & 0 deletions packages/asset-did/src/DidDocumentExporter.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Copyright (c) 2018-2023, BOTLabs GmbH.
*
* This source code is licensed under the BSD 4-Clause "Original" license
* found in the LICENSE file in the root directory of this source tree.
*/

import type { AssetDidDocument } from '@kiltprotocol/types'

import { exportToDidDocument } from './DidDocumentExporter'
import { resolve } from './Resolver'

/**
* @group unit/assetdid
*/

const assetDid =
'did:asset:eip155:1.erc20:0x71C7656EC7ab88b098defB751B7401B5f6d8976F:123'

describe('DidDocumentExporter.exportToDidDocument', () => {
it('should correctly export a JSON DID document', async () => {
const resolution = resolve(assetDid)
expect(
exportToDidDocument(resolution, 'application/json')
).toMatchObject<AssetDidDocument>({
id: assetDid,
chain: {
namespace: 'eip155',
reference: '1',
},
asset: {
namespace: 'erc20',
reference: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F',
identifier: '123',
},
})
})

it('should correctly export a JSON-LD DID document', async () => {
const resolution = resolve(assetDid)
expect(
exportToDidDocument(resolution, 'application/ld+json')
).toMatchObject<AssetDidDocument>({
'@context': [
'https://www.w3.org/ns/did/v1',
'ipfs://QmUAcsTVNfjGoZ3dcuHKikFJZpRiUkXCpbWcfxb1j5qnv4',
],
id: assetDid,
chain: {
namespace: 'eip155',
reference: '1',
},
asset: {
namespace: 'erc20',
reference: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F',
identifier: '123',
},
})
})
})
50 changes: 50 additions & 0 deletions packages/asset-did/src/DidDocumentExporter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright (c) 2018-2023, BOTLabs GmbH.
*
* This source code is licensed under the BSD 4-Clause "Original" license
* found in the LICENSE file in the root directory of this source tree.
*/

import type { AssetDidDocument } from '@kiltprotocol/types'

import { ASSET_DID_CONTEXT_URL, W3C_DID_CONTEXT_URL } from '@kiltprotocol/did'

import { ResolvedAssetDid } from './Resolver.js'

/**
* Export a [[ResolvedAssetDid]] to a W3C-spec conforming DID Document in the format provided.
*
* @param did The [[ResolvedAssetDid]].
* @param mimeType The format for the output DID Document. Accepted values are `application/json` and `application/ld+json`.
* @returns The DID Document formatted according to the mime type provided, or an error if the format specified is not supported.
*/
export function exportToDidDocument(
did: ResolvedAssetDid,
mimeType: 'application/json' | 'application/ld+json'
): AssetDidDocument {
const {
uri,
chainNamespace,
chainReference,
assetNamespace,
assetReference,
assetInstance,
} = did
const didDocument: AssetDidDocument = {
id: uri,
chain: {
namespace: chainNamespace,
reference: chainReference,
},
asset: {
namespace: assetNamespace,
reference: assetReference,
identifier: assetInstance,
},
}
if (mimeType === 'application/ld+json') {
didDocument['@context'] = [W3C_DID_CONTEXT_URL, ASSET_DID_CONTEXT_URL]
}

return didDocument
}
146 changes: 146 additions & 0 deletions packages/asset-did/src/Resolver.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**
* Copyright (c) 2018-2023, BOTLabs GmbH.
*
* This source code is licensed under the BSD 4-Clause "Original" license
* found in the LICENSE file in the root directory of this source tree.
*/

/**
* @group unit/assetdid
*/

import type {
AssetDidUri,
ConformingAssetDidResolutionResult,
} from '@kiltprotocol/types'
import { SDKErrors } from '@kiltprotocol/utils'

import type { ResolvedAssetDid } from './Resolver'
import { resolve, resolveCompliant } from './Resolver'

describe('Resolver.resolve', () => {
it('should correctly resolve a valid AssetDID without an asset identifier', async () => {
const assetDid =
'did:asset:eip155:1.erc20:0x71C7656EC7ab88b098defB751B7401B5f6d8976F'
expect(resolve(assetDid)).toMatchObject<ResolvedAssetDid>({
uri: 'did:asset:eip155:1.erc20:0x71C7656EC7ab88b098defB751B7401B5f6d8976F',
chainId: 'eip155:1',
chainNamespace: 'eip155',
chainReference: '1',
assetId: 'erc20:0x71C7656EC7ab88b098defB751B7401B5f6d8976F',
assetNamespace: 'erc20',
assetReference: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F',
assetInstance: undefined,
})
})
it('should correctly resolve a valid AssetDID with an asset identifier', async () => {
const assetDid =
'did:asset:eip155:1.erc20:0x71C7656EC7ab88b098defB751B7401B5f6d8976F:123'
expect(resolve(assetDid)).toMatchObject<ResolvedAssetDid>({
uri: 'did:asset:eip155:1.erc20:0x71C7656EC7ab88b098defB751B7401B5f6d8976F:123',
chainId: 'eip155:1',
chainNamespace: 'eip155',
chainReference: '1',
assetId: 'erc20:0x71C7656EC7ab88b098defB751B7401B5f6d8976F:123',
assetNamespace: 'erc20',
assetReference: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F',
assetInstance: '123',
})
})
it('should fail to resolve invalid AssetDIDs', async () => {
const assetDids: string[] = [
'did',
'did:',
'did:asset',
'did:asset:',
'did:asset:eip155',
'did:asset:eip155:',
'did:asset:eip155:1',
'did:asset:eip155:1.',
'did:asset:eip155:1.erc20',
'did:asset:eip155:1.erc20:',
'did:asset:eip155:1.erc20:0x71C7656EC7ab88b098defB751B7401B5f6d8976F:',
'did:asset:eip155:1.erc20:0x71C7656EC7ab88b098defB751B7401B5f6d8976F:123:',
]
assetDids.forEach((assetDid) =>
expect(() => resolve(assetDid as AssetDidUri)).toThrowError(
new SDKErrors.InvalidDidFormatError(assetDid)
)
)
})
})

describe('Resolver.resolveCompliant', () => {
it('should correctly resolve a valid AssetDID without an asset identifier', async () => {
const assetDid =
'did:asset:eip155:1.erc20:0x71C7656EC7ab88b098defB751B7401B5f6d8976F'
expect(
resolveCompliant(assetDid)
).toMatchObject<ConformingAssetDidResolutionResult>({
didDocument: {
id: 'did:asset:eip155:1.erc20:0x71C7656EC7ab88b098defB751B7401B5f6d8976F',
chain: {
namespace: 'eip155',
reference: '1',
},
asset: {
namespace: 'erc20',
reference: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F',
identifier: undefined,
},
},
didDocumentMetadata: {},
didResolutionMetadata: {},
})
})
it('should correctly resolve a valid AssetDID with an asset identifier', async () => {
const assetDid =
'did:asset:eip155:1.erc20:0x71C7656EC7ab88b098defB751B7401B5f6d8976F:123'
expect(
resolveCompliant(assetDid)
).toMatchObject<ConformingAssetDidResolutionResult>({
didDocument: {
id: 'did:asset:eip155:1.erc20:0x71C7656EC7ab88b098defB751B7401B5f6d8976F:123',
chain: {
namespace: 'eip155',
reference: '1',
},
asset: {
namespace: 'erc20',
reference: '0x71C7656EC7ab88b098defB751B7401B5f6d8976F',
identifier: '123',
},
},
didDocumentMetadata: {},
didResolutionMetadata: {},
})
})
it('should fail to resolve invalid AssetDIDs', async () => {
const assetDids: string[] = [
'did',
'did:',
'did:asset',
'did:asset:',
'did:asset:eip155',
'did:asset:eip155:',
'did:asset:eip155:1',
'did:asset:eip155:1.',
'did:asset:eip155:1.erc20',
'did:asset:eip155:1.erc20:',
'did:asset:eip155:1.erc20:0x71C7656EC7ab88b098defB751B7401B5f6d8976F:',
'did:asset:eip155:1.erc20:0x71C7656EC7ab88b098defB751B7401B5f6d8976F:123:',
]
assetDids.forEach((assetDid) =>
expect(
resolveCompliant(assetDid as AssetDidUri)
).toMatchObject<ConformingAssetDidResolutionResult>({
didDocument: undefined,
didDocumentMetadata: {},
didResolutionMetadata: {
error: 'invalidDid',
errorMessage: new SDKErrors.InvalidDidFormatError(assetDid).message,
},
})
)
})
})
Loading