Skip to content

Commit

Permalink
feat: 🎸 add register did with cdd in Identities namespace
Browse files Browse the repository at this point in the history
 allow a DID to be created with a CDD claim using the new chain
extrinsic. Previously these were unbatchable since the CDD claim needed
the DID as an argument

✅ Closes: DA-498
  • Loading branch information
polymath-eric committed Jan 30, 2023
1 parent 047552b commit 0ff4b24
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 2 deletions.
27 changes: 25 additions & 2 deletions src/api/client/Identities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import {
Identity,
NumberedPortfolio,
registerIdentity,
registerIdentityWithCdd,
} from '~/internal';
import { ProcedureMethod, RegisterIdentityParams } from '~/types';
import { ProcedureMethod, RegisterIdentityParams, RegisterIdentityWithCddParams } from '~/types';
import { asIdentity, createProcedureMethod } from '~/utils/internal';

/**
Expand All @@ -26,6 +27,11 @@ export class Identities {
context
);

this.registerIdentityWithCdd = createProcedureMethod(
{ getProcedureAndArgs: args => [registerIdentityWithCdd, args] },
context
);

this.createPortfolio = createProcedureMethod<
{ name: string },
{ names: string[] },
Expand Down Expand Up @@ -55,7 +61,10 @@ export class Identities {
/**
* Register an Identity
*
* @note must be a CDD provider
* This creates an DID for an Identity, without a CDD claim the Identity will not be fully onboarded. Generally {@link api/client/Identities!registerIdentityWithCdd}
* is preferred, unless having an on chain Identity that will later later complete the CDD process is explicitly desired.
*
* @note this signer must be a CDD provider
* @note this may create {@link api/entities/AuthorizationRequest!AuthorizationRequest | Authorization Requests} which have to be accepted by the `targetAccount`.
* An {@link api/entities/Account!Account} or {@link api/entities/Identity!Identity} can fetch its pending Authorization Requests by calling {@link api/entities/common/namespaces/Authorizations!Authorizations.getReceived | authorizations.getReceived}.
* Also, an Account or Identity can directly fetch the details of an Authorization Request by calling {@link api/entities/common/namespaces/Authorizations!Authorizations.getOne | authorizations.getOne}
Expand All @@ -65,6 +74,20 @@ export class Identities {
*/
public registerIdentity: ProcedureMethod<RegisterIdentityParams, Identity>;

/**
* Register an Identity and create a CDD claim for it. This allows for an Account to receive POLYX and interact with the chain.
*
* Functions like {@link api/client/Identities!registerIdentity | registerIdentity} followed by a CDD claim being added, except in a single transaction.
*
* @note this may create {@link api/entities/AuthorizationRequest!AuthorizationRequest | Authorization Requests} which have to be accepted by the `targetAccount`.
* An {@link api/entities/Account!Account} or {@link api/entities/Identity!Identity} can fetch its pending Authorization Requests by calling {@link api/entities/common/namespaces/Authorizations!Authorizations.getReceived | authorizations.getReceived}.
* Also, an Account or Identity can directly fetch the details of an Authorization Request by calling {@link api/entities/common/namespaces/Authorizations!Authorizations.getOne | authorizations.getOne}
*
* @note required role:
* - Customer Due Diligence Provider
*/
public registerIdentityWithCdd: ProcedureMethod<RegisterIdentityWithCddParams, Identity>;

/**
* Create a new Portfolio under the ownership of the signing Identity
*/
Expand Down
21 changes: 21 additions & 0 deletions src/api/client/__tests__/Identities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,27 @@ describe('Identities Class', () => {
});
});

describe('method: registerIdentityWithCdd', () => {
it('should prepare the procedure with the correct arguments and context, and return the resulting transaction', async () => {
const expiry = new Date();
expiry.setDate(expiry.getDate() + 30);
const args = {
targetAccount: 'someTarget',
expiry,
};

const expectedTransaction = 'someTransaction' as unknown as PolymeshTransaction<Identity>;

when(procedureMockUtils.getPrepareMock())
.calledWith({ args, transformer: undefined }, context, {})
.mockResolvedValue(expectedTransaction);

const tx = await identities.registerIdentityWithCdd(args);

expect(tx).toBe(expectedTransaction);
});
});

describe('method: createPortfolio', () => {
it('should prepare the procedure and return the resulting transaction', async () => {
const args = { name: 'someName' };
Expand Down
146 changes: 146 additions & 0 deletions src/api/procedures/__tests__/registerIdentityWithCdd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { AccountId } from '@polkadot/types/interfaces';
import { PolymeshPrimitivesSecondaryKey } from '@polkadot/types/lookup';
import { ISubmittableResult } from '@polkadot/types/types';
import BigNumber from 'bignumber.js';
import { when } from 'jest-when';

import {
createRegisterIdentityWithDidResolver,
prepareRegisterIdentityWithCdd,
} from '~/api/procedures/registerIdentityWithCdd';
import { Context, Identity } from '~/internal';
import { Moment } from '~/polkadot';
import { dsMockUtils, entityMockUtils, procedureMockUtils } from '~/testUtils/mocks';
import { Mocked } from '~/testUtils/types';
import { PermissionedAccount, RegisterIdentityWithCddParams } from '~/types';
import { PolymeshTx } from '~/types/internal';
import * as utilsConversionModule from '~/utils/conversion';
import * as utilsInternalModule from '~/utils/internal';

describe('registerIdentityWithCdd procedure', () => {
let mockContext: Mocked<Context>;
let stringToAccountIdSpy: jest.SpyInstance<AccountId, [string, Context]>;
let dateToMomentSpy: jest.SpyInstance<Moment, [Date, Context]>;
let secondaryAccountToMeshSecondaryKeySpy: jest.SpyInstance<
PolymeshPrimitivesSecondaryKey,
[PermissionedAccount, Context]
>;
let registerIdentityWithCddTransaction: PolymeshTx<unknown[]>;

beforeAll(() => {
entityMockUtils.initMocks();
procedureMockUtils.initMocks();
dsMockUtils.initMocks();
secondaryAccountToMeshSecondaryKeySpy = jest.spyOn(
utilsConversionModule,
'secondaryAccountToMeshSecondaryKey'
);
stringToAccountIdSpy = jest.spyOn(utilsConversionModule, 'stringToAccountId');
dateToMomentSpy = jest.spyOn(utilsConversionModule, 'dateToMoment');
});

beforeEach(() => {
mockContext = dsMockUtils.getContextInstance();
registerIdentityWithCddTransaction = dsMockUtils.createTxMock(
'identity',
'cddRegisterDidWithCdd'
);
});

afterEach(() => {
entityMockUtils.reset();
procedureMockUtils.reset();
dsMockUtils.reset();
});

afterAll(() => {
procedureMockUtils.cleanup();
dsMockUtils.cleanup();
});

it('should return a cddRegisterIdentity transaction spec', async () => {
const targetAccount = 'someAccount';
const secondaryAccounts = [
{
account: entityMockUtils.getAccountInstance({ address: 'someValue' }),
permissions: {
assets: null,
portfolios: null,
transactions: null,
transactionGroups: [],
},
},
];
const args = {
targetAccount,
secondaryAccounts,
};
const expiry = new Date('12/12/2050');
const rawExpiry = dsMockUtils.createMockMoment(new BigNumber(expiry.getTime()));
when(dateToMomentSpy).calledWith(expiry, mockContext).mockReturnValue(rawExpiry);
const rawAccountId = dsMockUtils.createMockAccountId(targetAccount);
const rawSecondaryAccount = dsMockUtils.createMockSecondaryKey({
signer: dsMockUtils.createMockSignatory({
Account: dsMockUtils.createMockAccountId(secondaryAccounts[0].account.address),
}),
permissions: dsMockUtils.createMockPermissions(),
});

const proc = procedureMockUtils.getInstance<RegisterIdentityWithCddParams, Identity>(
mockContext
);

when(stringToAccountIdSpy).calledWith(targetAccount, mockContext).mockReturnValue(rawAccountId);
when(secondaryAccountToMeshSecondaryKeySpy)
.calledWith(secondaryAccounts[0], mockContext)
.mockReturnValue(rawSecondaryAccount);

let result = await prepareRegisterIdentityWithCdd.call(proc, args);

expect(result).toEqual({
transaction: registerIdentityWithCddTransaction,
args: [rawAccountId, [rawSecondaryAccount], null],
resolver: expect.any(Function),
});

result = await prepareRegisterIdentityWithCdd.call(proc, { targetAccount, expiry });

expect(result).toEqual({
transaction: registerIdentityWithCddTransaction,
args: [rawAccountId, [], rawExpiry],
resolver: expect.any(Function),
});
});
});

describe('createRegisterIdentityResolver', () => {
const did = 'someDid';
const rawDid = dsMockUtils.createMockIdentityId(did);
const filterEventRecordsSpy = jest.spyOn(utilsInternalModule, 'filterEventRecords');

beforeAll(() => {
entityMockUtils.initMocks({
identityOptions: {
did,
},
});
});

beforeEach(() => {
filterEventRecordsSpy.mockReturnValue([
dsMockUtils.createMockIEvent([rawDid, 'accountId', 'signedItem']),
]);
});

afterEach(() => {
filterEventRecordsSpy.mockReset();
});

it('should return the new Identity', () => {
const fakeContext = {} as Context;

const result = createRegisterIdentityWithDidResolver(fakeContext)({} as ISubmittableResult);

expect(result.did).toEqual(did);
});
});
72 changes: 72 additions & 0 deletions src/api/procedures/registerIdentityWithCdd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { ISubmittableResult } from '@polkadot/types/types';

import { Context, Identity, Procedure } from '~/internal';
import { RegisterIdentityParams, RegisterIdentityWithCddParams, RoleType, TxTags } from '~/types';
import { ExtrinsicParams, TransactionSpec } from '~/types/internal';
import {
dateToMoment,
identityIdToString,
permissionsLikeToPermissions,
secondaryAccountToMeshSecondaryKey,
signerToString,
stringToAccountId,
} from '~/utils/conversion';
import { filterEventRecords, optionize } from '~/utils/internal';

/**
* @hidden
*/
export const createRegisterIdentityWithDidResolver =
(context: Context) =>
(receipt: ISubmittableResult): Identity => {
const [{ data }] = filterEventRecords(receipt, 'identity', 'DidCreated');
const did = identityIdToString(data[0]);

return new Identity({ did }, context);
};

/**
* @hidden
*/
export async function prepareRegisterIdentityWithCdd(
this: Procedure<RegisterIdentityParams, Identity>,
args: RegisterIdentityWithCddParams
): Promise<TransactionSpec<Identity, ExtrinsicParams<'identity', 'cddRegisterDidWithCdd'>>> {
const {
context: {
polymeshApi: {
tx: { identity },
},
},
context,
} = this;
const { targetAccount, secondaryAccounts = [], expiry } = args;

const rawTargetAccount = stringToAccountId(signerToString(targetAccount), context);
const rawSecondaryKeys = secondaryAccounts.map(({ permissions, ...rest }) =>
secondaryAccountToMeshSecondaryKey(
{ ...rest, permissions: permissionsLikeToPermissions(permissions, context) },
context
)
);
const cddExpiry = optionize(dateToMoment)(expiry, context);

return {
transaction: identity.cddRegisterDidWithCdd,
args: [rawTargetAccount, rawSecondaryKeys, cddExpiry],
resolver: createRegisterIdentityWithDidResolver(context),
};
}

/**
* @hidden
*/
export const registerIdentityWithCdd = (): Procedure<RegisterIdentityWithCddParams, Identity> =>
new Procedure(prepareRegisterIdentityWithCdd, {
roles: [{ type: RoleType.CddProvider }],
permissions: {
assets: [],
portfolios: [],
transactions: [TxTags.identity.CddRegisterDidWithCdd],
},
});
6 changes: 6 additions & 0 deletions src/api/procedures/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,12 @@ export interface RegisterIdentityParams {
secondaryAccounts?: Modify<PermissionedAccount, { permissions: PermissionsLike }>[];
}

export interface RegisterIdentityWithCddParams {
targetAccount: string | Account;
secondaryAccounts?: Modify<PermissionedAccount, { permissions: PermissionsLike }>[];
expiry?: Date;
}

export interface TransferPolyxParams {
/**
* Account that will receive the POLYX
Expand Down
1 change: 1 addition & 0 deletions src/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export {
Params as ModifyAssetTrustedClaimIssuersParams,
} from '~/api/procedures/modifyAssetTrustedClaimIssuers';
export { registerIdentity } from '~/api/procedures/registerIdentity';
export { registerIdentityWithCdd } from '~/api/procedures/registerIdentityWithCdd';
export { removeSecondaryAccounts } from '~/api/procedures/removeSecondaryAccounts';
export {
modifySignerPermissions,
Expand Down

0 comments on commit 0ff4b24

Please sign in to comment.