diff --git a/packages/sdk-js/src/DidHelpers/deactivateDid.spec.ts b/packages/sdk-js/src/DidHelpers/deactivateDid.spec.ts index 96a7e9a96..c42c395a8 100644 --- a/packages/sdk-js/src/DidHelpers/deactivateDid.spec.ts +++ b/packages/sdk-js/src/DidHelpers/deactivateDid.spec.ts @@ -13,11 +13,11 @@ import { } from '../../../../tests/testUtils/index.js' import { ConfigService } from '../index.js' import { deactivateDid } from './deactivateDid.js' -import { transact } from './transact.js' +import { transactInternal } from './transact.js' jest.mock('./transact.js') -const mockedTransact = jest.mocked(transact) +const mockedTransact = jest.mocked(transactInternal) const mockedApi = ApiMocks.createAugmentedApi() describe('deactivate', () => { @@ -50,8 +50,8 @@ describe('deactivate', () => { }) expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), + expect.objectContaining[0]>>({ + callFactory: expect.any(Function), expectedEvents: expect.arrayContaining([ { section: 'did', @@ -64,7 +64,11 @@ describe('deactivate', () => { signers: [keypair], }) ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + expect( + await mockedTransact.mock.lastCall?.[0] + .callFactory() + .then((f) => f.toHuman()) + ).toMatchObject({ method: { method: 'delete', section: 'did' }, }) }) diff --git a/packages/sdk-js/src/DidHelpers/deactivateDid.ts b/packages/sdk-js/src/DidHelpers/deactivateDid.ts index ac6318d2b..1b684059f 100644 --- a/packages/sdk-js/src/DidHelpers/deactivateDid.ts +++ b/packages/sdk-js/src/DidHelpers/deactivateDid.ts @@ -6,7 +6,7 @@ */ import type { SharedArguments, TransactionHandlers } from './interfaces.js' -import { transact } from './transact.js' +import { transactInternal } from './transact.js' /** * _Permanently_ deactivates the DID, removing all verification methods and services from its document. @@ -18,9 +18,10 @@ import { transact } from './transact.js' */ export function deactivateDid(options: SharedArguments): TransactionHandlers { const { api, didDocument } = options - return transact({ + return transactInternal({ ...options, - call: api.tx.did.delete(didDocument.service?.length ?? 0), + callFactory: async () => + api.tx.did.delete(didDocument.service?.length ?? 0), expectedEvents: [{ section: 'did', method: 'DidDeleted' }], }) } diff --git a/packages/sdk-js/src/DidHelpers/service.spec.ts b/packages/sdk-js/src/DidHelpers/service.spec.ts index 0e5b7f91c..b2d79712a 100644 --- a/packages/sdk-js/src/DidHelpers/service.spec.ts +++ b/packages/sdk-js/src/DidHelpers/service.spec.ts @@ -12,12 +12,12 @@ import { createLocalDemoFullDidFromKeypair, } from '../../../../tests/testUtils/index.js' import { ConfigService } from '../index.js' -import { transact } from './index.js' import { addService, removeService } from './service.js' +import { transactInternal } from './transact.js' jest.mock('./transact.js') -const mockedTransact = jest.mocked(transact) +const mockedTransact = jest.mocked(transactInternal) const mockedApi = ApiMocks.createAugmentedApi() describe.each(['#my_service', 'did:kilt:4abctest#my_service'])( @@ -57,8 +57,10 @@ describe.each(['#my_service', 'did:kilt:4abctest#my_service'])( }) expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), + expect.objectContaining< + Partial[0]> + >({ + callFactory: expect.any(Function), expectedEvents: expect.arrayContaining([ { section: 'did', @@ -71,7 +73,11 @@ describe.each(['#my_service', 'did:kilt:4abctest#my_service'])( signers: [keypair], }) ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + expect( + await mockedTransact.mock.lastCall?.[0] + .callFactory() + .then((f) => f.toHuman()) + ).toMatchObject({ method: { args: { service_endpoint: { @@ -96,8 +102,10 @@ describe.each(['#my_service', 'did:kilt:4abctest#my_service'])( }) expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), + expect.objectContaining< + Partial[0]> + >({ + callFactory: expect.any(Function), expectedEvents: expect.arrayContaining([ { section: 'did', @@ -110,7 +118,11 @@ describe.each(['#my_service', 'did:kilt:4abctest#my_service'])( signers: [keypair], }) ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + expect( + await mockedTransact.mock.lastCall?.[0] + .callFactory() + .then((f) => f.toHuman()) + ).toMatchObject({ method: { args: { service_id: 'my_service', diff --git a/packages/sdk-js/src/DidHelpers/service.ts b/packages/sdk-js/src/DidHelpers/service.ts index b2aee90dd..f6e346a26 100644 --- a/packages/sdk-js/src/DidHelpers/service.ts +++ b/packages/sdk-js/src/DidHelpers/service.ts @@ -8,7 +8,7 @@ import { serviceToChain, urlFragmentToChain } from '@kiltprotocol/did' import type { DidUrl, Service, UriFragment } from '@kiltprotocol/types' import { SharedArguments, TransactionHandlers } from './interfaces.js' -import { transact } from './transact.js' +import { transactInternal } from './transact.js' /** * Adds a service to the DID Document. @@ -23,12 +23,10 @@ export function addService( service: Service } ): TransactionHandlers { - const didServiceUpdateTx = options.api.tx.did.addServiceEndpoint( - serviceToChain(options.service) - ) - return transact({ + return transactInternal({ ...options, - call: didServiceUpdateTx, + callFactory: async () => + options.api.tx.did.addServiceEndpoint(serviceToChain(options.service)), expectedEvents: [{ section: 'did', method: 'DidUpdated' }], }) } @@ -46,12 +44,10 @@ export function removeService( id: DidUrl | UriFragment } ): TransactionHandlers { - const didServiceUpdateTx = options.api.tx.did.removeServiceEndpoint( - urlFragmentToChain(options.id) - ) - return transact({ + return transactInternal({ ...options, - call: didServiceUpdateTx, + callFactory: async () => + options.api.tx.did.removeServiceEndpoint(urlFragmentToChain(options.id)), expectedEvents: [{ section: 'did', method: 'DidUpdated' }], }) } diff --git a/packages/sdk-js/src/DidHelpers/transact.spec.ts b/packages/sdk-js/src/DidHelpers/transact.spec.ts index b04961eb8..378eb1f27 100644 --- a/packages/sdk-js/src/DidHelpers/transact.spec.ts +++ b/packages/sdk-js/src/DidHelpers/transact.spec.ts @@ -5,18 +5,19 @@ * found in the LICENSE file in the root directory of this source tree. */ -import { SubmittableResult } from '@polkadot/api' import { authorizeTx, resolver } from '@kiltprotocol/did' import type { DidDocument, KiltKeyringPair } from '@kiltprotocol/types' import { Crypto } from '@kiltprotocol/utils' -import { ConfigService } from '../index.js' +import { SubmittableResult } from '@polkadot/api' + import { ApiMocks, createLocalDemoFullDidFromKeypair, } from '../../../../tests/testUtils/index.js' -import { transact } from './index.js' -import { TransactionResult } from './interfaces.js' import { makeAttestationCreatedEvents } from '../../../../tests/testUtils/testData.js' +import { ConfigService } from '../index.js' +import { TransactionResult } from './interfaces.js' +import { transact } from './transact.js' jest.mock('@kiltprotocol/did', () => { return { diff --git a/packages/sdk-js/src/DidHelpers/transact.ts b/packages/sdk-js/src/DidHelpers/transact.ts index 40eb7b767..d8d6a8687 100644 --- a/packages/sdk-js/src/DidHelpers/transact.ts +++ b/packages/sdk-js/src/DidHelpers/transact.ts @@ -18,12 +18,12 @@ import type { SharedArguments, TransactionHandlers } from './interfaces.js' * Instructs a transaction (state transition) as this DID (with this DID as the origin). * * @param options Any {@link SharedArguments} and additional parameters. - * @param options.call The transaction / call to execute. + * @param options.callFactory Async callback producing the transaction / call to execute. * @returns A set of {@link TransactionHandlers}. */ -export function transact( +export function transactInternal( options: SharedArguments & { - call: Extrinsic | SubmittableExtrinsic + callFactory: () => Promise expectedEvents?: Array<{ section: string; method: string }> } ): TransactionHandlers { @@ -35,9 +35,16 @@ export function transact( } | undefined = {} ) => { - const { didDocument, signers, submitter, call, api, expectedEvents } = - options + const { + didDocument, + signers, + submitter, + callFactory, + api, + expectedEvents, + } = options const { didNonce, signSubmittable = true } = submitOptions + const call = await callFactory() const didSigners = await signersForDid(didDocument, ...signers) const submitterAccount = ( @@ -75,3 +82,19 @@ export function transact( getSubmittable, } } + +/** + * Instructs a transaction (state transition) as this DID (with this DID as the origin). + * + * @param options Any {@link SharedArguments} and additional parameters. + * @param options.call The transaction / call to execute. + * @returns A set of {@link TransactionHandlers}. + */ +export function transact( + options: SharedArguments & { + call: Extrinsic | SubmittableExtrinsic + expectedEvents?: Array<{ section: string; method: string }> + } +): TransactionHandlers { + return transactInternal({ ...options, callFactory: async () => options.call }) +} diff --git a/packages/sdk-js/src/DidHelpers/verificationMethod.spec.ts b/packages/sdk-js/src/DidHelpers/verificationMethod.spec.ts index 5c5e2c130..9e468aa24 100644 --- a/packages/sdk-js/src/DidHelpers/verificationMethod.spec.ts +++ b/packages/sdk-js/src/DidHelpers/verificationMethod.spec.ts @@ -12,15 +12,15 @@ import { createLocalDemoFullDidFromKeypair, } from '../../../../tests/testUtils/index.js' import { ConfigService } from '../index.js' +import { transactInternal } from './transact.js' import { removeVerificationMethod, setVerificationMethod, } from './verificationMethod.js' -import { transact } from './transact.js' jest.mock('./transact.js') -const mockedTransact = jest.mocked(transact) +const mockedTransact = jest.mocked(transactInternal) const mockedApi = ApiMocks.createAugmentedApi() let didDocument: DidDocument @@ -55,8 +55,8 @@ describe('signing keys', () => { }) expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), + expect.objectContaining[0]>>({ + callFactory: expect.any(Function), expectedEvents: expect.arrayContaining([ { section: 'did', @@ -69,7 +69,11 @@ describe('signing keys', () => { signers: [keypair], }) ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + expect( + await mockedTransact.mock.lastCall?.[0] + .callFactory() + .then((f) => f.toHuman()) + ).toMatchObject({ method: { section: 'did', method: 'setAttestationKey', @@ -90,8 +94,8 @@ describe('signing keys', () => { }) expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), + expect.objectContaining[0]>>({ + callFactory: expect.any(Function), expectedEvents: expect.arrayContaining([ { section: 'did', @@ -104,7 +108,11 @@ describe('signing keys', () => { signers: [keypair], }) ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + expect( + await mockedTransact.mock.lastCall?.[0] + .callFactory() + .then((f) => f.toHuman()) + ).toMatchObject({ method: { section: 'did', method: 'removeAttestationKey', @@ -120,13 +128,13 @@ describe('key agreement keys', () => { api: mockedApi, submitter: keypair, signers: [keypair], - publicKey: { publicKey: keypair.publicKey, type: 'x25519' } as any, + publicKey: { publicKey: keypair.publicKey, type: 'x25519' }, relationship: 'keyAgreement', }) expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), + expect.objectContaining[0]>>({ + callFactory: expect.any(Function), expectedEvents: expect.arrayContaining([ { section: 'did', @@ -139,7 +147,11 @@ describe('key agreement keys', () => { signers: [keypair], }) ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + expect( + await mockedTransact.mock.lastCall?.[0] + .callFactory() + .then((f) => f.toHuman()) + ).toMatchObject({ method: { section: 'utility', method: 'batchAll', @@ -165,13 +177,13 @@ describe('key agreement keys', () => { api: mockedApi, submitter: keypair, signers: [keypair], - publicKey: { publicKey: keypair.publicKey, type: 'x25519' } as any, + publicKey: { publicKey: keypair.publicKey, type: 'x25519' }, relationship: 'keyAgreement', }) expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), + expect.objectContaining[0]>>({ + callFactory: expect.any(Function), expectedEvents: expect.arrayContaining([ { section: 'did', @@ -184,7 +196,11 @@ describe('key agreement keys', () => { signers: [keypair], }) ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + expect( + await mockedTransact.mock.lastCall?.[0] + .callFactory() + .then((f) => f.toHuman()) + ).toMatchObject({ method: { section: 'utility', method: 'batchAll', @@ -217,8 +233,8 @@ describe('key agreement keys', () => { }) expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), + expect.objectContaining[0]>>({ + callFactory: expect.any(Function), expectedEvents: expect.arrayContaining([ { section: 'did', @@ -231,7 +247,11 @@ describe('key agreement keys', () => { signers: [keypair], }) ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + expect( + await mockedTransact.mock.lastCall?.[0] + .callFactory() + .then((f) => f.toHuman()) + ).toMatchObject({ method: { section: 'did', method: 'removeKeyAgreementKey', diff --git a/packages/sdk-js/src/DidHelpers/verificationMethod.ts b/packages/sdk-js/src/DidHelpers/verificationMethod.ts index e394ae15a..252d52a1b 100644 --- a/packages/sdk-js/src/DidHelpers/verificationMethod.ts +++ b/packages/sdk-js/src/DidHelpers/verificationMethod.ts @@ -23,7 +23,7 @@ import type { SharedArguments, TransactionHandlers, } from './interfaces.js' -import { transact } from './transact.js' +import { transactInternal } from './transact.js' /** * Replaces all existing verification methods for the selected `relationship` with `publicKey`. @@ -40,50 +40,47 @@ export function setVerificationMethod( relationship: VerificationRelationship } ): TransactionHandlers { - const { publicKey, relationship, didDocument, api } = options - const pk = convertPublicKey(publicKey) + const callFactory = async (): Promise => { + const { publicKey, relationship, didDocument, api } = options + const pk = convertPublicKey(publicKey) - let didKeyUpdateTx - switch (relationship) { - case 'keyAgreement': { - const txs: SubmittableExtrinsic[] = [] - didDocument.keyAgreement?.forEach((id) => - txs.push(api.tx.did.removeKeyAgreementKey(urlFragmentToChain(id))) - ) - txs.push( - api.tx.did.addKeyAgreementKey( - publicKeyToChain(pk as NewDidEncryptionKey) + switch (relationship) { + case 'keyAgreement': { + const txs: SubmittableExtrinsic[] = [] + didDocument.keyAgreement?.forEach((id) => + txs.push(api.tx.did.removeKeyAgreementKey(urlFragmentToChain(id))) ) - ) - didKeyUpdateTx = api.tx.utility.batchAll(txs) - break - } - case 'authentication': { - didKeyUpdateTx = api.tx.did.setAuthenticationKey( - publicKeyToChain(pk as NewDidVerificationKey) - ) - break - } - case 'capabilityDelegation': { - didKeyUpdateTx = api.tx.did.setDelegationKey( - publicKeyToChain(pk as NewDidVerificationKey) - ) - break - } - case 'assertionMethod': { - didKeyUpdateTx = api.tx.did.setAttestationKey( - publicKeyToChain(pk as NewDidVerificationKey) - ) - break - } - default: { - throw new Error('unsupported relationship') + txs.push( + api.tx.did.addKeyAgreementKey( + publicKeyToChain(pk as NewDidEncryptionKey) + ) + ) + return api.tx.utility.batchAll(txs) + } + case 'authentication': { + return api.tx.did.setAuthenticationKey( + publicKeyToChain(pk as NewDidVerificationKey) + ) + } + case 'capabilityDelegation': { + return api.tx.did.setDelegationKey( + publicKeyToChain(pk as NewDidVerificationKey) + ) + } + case 'assertionMethod': { + return api.tx.did.setAttestationKey( + publicKeyToChain(pk as NewDidVerificationKey) + ) + } + default: { + throw new Error('unsupported relationship') + } } } - return transact({ + return transactInternal({ ...options, - call: didKeyUpdateTx, + callFactory, expectedEvents: [{ section: 'did', method: 'DidUpdated' }], }) } @@ -105,52 +102,49 @@ export function removeVerificationMethod( relationship: Omit } ): TransactionHandlers { - const { relationship, didDocument, api, verificationMethodId } = options - let didKeyUpdateTx - switch (relationship) { - case 'authentication': { - throw new Error('authentication verification methods can not be removed') - } - case 'capabilityDelegation': { - if (didDocument.capabilityDelegation?.includes(verificationMethodId)) { - didKeyUpdateTx = api.tx.did.removeDelegationKey() - } else { + const callFactory = async (): Promise => { + const { relationship, didDocument, api, verificationMethodId } = options + switch (relationship) { + case 'authentication': { throw new Error( - 'the specified capabilityDelegation method does not exist in the DID Document' + 'authentication verification methods can not be removed' ) } - break - } - case 'keyAgreement': { - if (didDocument.keyAgreement?.includes(verificationMethodId)) { - didKeyUpdateTx = api.tx.did.removeKeyAgreementKey( - urlFragmentToChain(verificationMethodId) + case 'capabilityDelegation': { + if (didDocument.capabilityDelegation?.includes(verificationMethodId)) { + return api.tx.did.removeDelegationKey() + } + throw new Error( + 'the specified capabilityDelegation method does not exist in the DID Document' ) - } else { + } + case 'keyAgreement': { + if (didDocument.keyAgreement?.includes(verificationMethodId)) { + return api.tx.did.removeKeyAgreementKey( + urlFragmentToChain(verificationMethodId) + ) + } throw new Error( 'the specified keyAgreement key does not exist in the DID Document' ) } - break - } - case 'assertionMethod': { - if (didDocument.assertionMethod?.includes(verificationMethodId)) { - didKeyUpdateTx = api.tx.did.removeAttestationKey() - } else { + case 'assertionMethod': { + if (didDocument.assertionMethod?.includes(verificationMethodId)) { + return api.tx.did.removeAttestationKey() + } throw new Error( 'the specified assertionMethod does not exist in the DID Document' ) } - break - } - default: { - throw new Error('the specified method relationship is not supported') + default: { + throw new Error('the specified method relationship is not supported') + } } } - return transact({ + return transactInternal({ ...options, - call: didKeyUpdateTx, + callFactory, expectedEvents: [{ section: 'did', method: 'DidUpdated' }], }) } diff --git a/packages/sdk-js/src/DidHelpers/w3names.spec.ts b/packages/sdk-js/src/DidHelpers/w3names.spec.ts index e078b5d4d..55f625e51 100644 --- a/packages/sdk-js/src/DidHelpers/w3names.spec.ts +++ b/packages/sdk-js/src/DidHelpers/w3names.spec.ts @@ -12,11 +12,12 @@ import { createLocalDemoFullDidFromKeypair, } from '../../../../tests/testUtils/index.js' import { ConfigService } from '../index.js' -import { claimWeb3Name, releaseWeb3Name, transact } from './index.js' +import { transactInternal } from './transact.js' +import { claimWeb3Name, releaseWeb3Name } from './w3names.js' jest.mock('./transact.js') -const mockedTransact = jest.mocked(transact) +const mockedTransact = jest.mocked(transactInternal) const mockedApi = ApiMocks.createAugmentedApi() describe('w3n', () => { @@ -50,8 +51,8 @@ describe('w3n', () => { }) expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), + expect.objectContaining[0]>>({ + callFactory: expect.any(Function), expectedEvents: expect.arrayContaining([ { section: 'web3Names', @@ -64,7 +65,11 @@ describe('w3n', () => { signers: [keypair], }) ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + expect( + await mockedTransact.mock.lastCall?.[0] + .callFactory() + .then((f) => f.toHuman()) + ).toMatchObject({ method: { args: { name: 'paul' }, method: 'claim', section: 'web3Names' }, }) }) @@ -78,8 +83,8 @@ describe('w3n', () => { }) expect(mockedTransact).toHaveBeenLastCalledWith( - expect.objectContaining[0]>>({ - call: expect.any(Object), + expect.objectContaining[0]>>({ + callFactory: expect.any(Function), expectedEvents: expect.arrayContaining([ { section: 'web3Names', @@ -92,7 +97,11 @@ describe('w3n', () => { signers: [keypair], }) ) - expect(mockedTransact.mock.lastCall?.[0].call.toHuman()).toMatchObject({ + expect( + await mockedTransact.mock.lastCall?.[0] + .callFactory() + .then((f) => f.toHuman()) + ).toMatchObject({ method: { method: 'releaseByOwner', section: 'web3Names' }, }) }) diff --git a/packages/sdk-js/src/DidHelpers/w3names.ts b/packages/sdk-js/src/DidHelpers/w3names.ts index 0b58afdbd..5feeed056 100644 --- a/packages/sdk-js/src/DidHelpers/w3names.ts +++ b/packages/sdk-js/src/DidHelpers/w3names.ts @@ -6,7 +6,7 @@ */ import type { SharedArguments, TransactionHandlers } from './interfaces.js' -import { transact } from './transact.js' +import { transactInternal } from './transact.js' /** * Adds a w3n nickname to the DID Document. @@ -22,9 +22,9 @@ export function claimWeb3Name( } ): TransactionHandlers { const { api, name } = options - return transact({ + return transactInternal({ ...options, - call: api.tx.web3Names.claim(name), + callFactory: async () => api.tx.web3Names.claim(name), expectedEvents: [{ section: 'web3Names', method: 'Web3NameClaimed' }], }) } @@ -37,9 +37,9 @@ export function claimWeb3Name( */ export function releaseWeb3Name(options: SharedArguments): TransactionHandlers { const { api } = options - return transact({ + return transactInternal({ ...options, - call: api.tx.web3Names.releaseByOwner(), + callFactory: async () => api.tx.web3Names.releaseByOwner(), expectedEvents: [{ section: 'web3Names', method: 'Web3NameReleased' }], }) }