Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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