diff --git a/src/api/client/Identities.ts b/src/api/client/Identities.ts index 0f6c4aaa4b..594d66ba8e 100644 --- a/src/api/client/Identities.ts +++ b/src/api/client/Identities.ts @@ -5,6 +5,7 @@ import { AuthorizationRequest, ChildIdentity, Context, + createChildIdentities, createChildIdentity, createPortfolios, Identity, @@ -17,6 +18,7 @@ import { import { AllowIdentityToCreatePortfoliosParams, AttestPrimaryKeyRotationParams, + CreateChildIdentitiesParams, CreateChildIdentityParams, ProcedureMethod, RegisterIdentityParams, @@ -89,6 +91,13 @@ export class Identities { context ); + this.createChildren = createProcedureMethod( + { + getProcedureAndArgs: args => [createChildIdentities, args], + }, + context + ); + this.allowIdentityToCreatePortfolios = createProcedureMethod( { getProcedureAndArgs: args => [allowIdentityToCreatePortfolios, args] }, context @@ -211,6 +220,19 @@ export class Identities { */ public createChild: ProcedureMethod; + /** + * Create child identities using off chain authorization + * + * @note the list of `key` provided in the params should not be linked to any other account + * + * @throws if + * - the signing account is not a primary key + * - the signing Identity is already a child of some other identity + * - `expiresAt` is not a future date + * - the any `key` in `childKeyAuths` is already linked to an Identity + */ + public createChildren: ProcedureMethod; + /** * Gives permission to the Identity to create Portfolios on behalf of the signing Identity * diff --git a/src/api/client/__tests__/Identities.ts b/src/api/client/__tests__/Identities.ts index c639e66d99..0cb6e074e1 100644 --- a/src/api/client/__tests__/Identities.ts +++ b/src/api/client/__tests__/Identities.ts @@ -11,7 +11,7 @@ import { } from '~/internal'; import { dsMockUtils, entityMockUtils, procedureMockUtils } from '~/testUtils/mocks'; import { Mocked } from '~/testUtils/types'; -import { RotatePrimaryKeyToSecondaryParams } from '~/types'; +import { CreateChildIdentitiesParams, RotatePrimaryKeyToSecondaryParams } from '~/types'; import { tuple } from '~/types/utils'; import * as utilsConversionModule from '~/utils/conversion'; @@ -95,6 +95,32 @@ describe('Identities Class', () => { }); }); + describe('method: createChildren', () => { + it('should prepare the procedure with the correct arguments and context, and return the resulting transaction', async () => { + const args: CreateChildIdentitiesParams = { + childKeyAuths: [ + { + key: 'someKey', + authSignature: '0xsignature', + }, + ], + expiresAt: new Date('2050/01/01'), + }; + + const expectedTransaction = 'someTransaction' as unknown as PolymeshTransaction< + ChildIdentity[] + >; + + when(procedureMockUtils.getPrepareMock()) + .calledWith({ args, transformer: undefined }, context, {}) + .mockResolvedValue(expectedTransaction); + + const tx = await identities.createChildren(args); + + expect(tx).toBe(expectedTransaction); + }); + }); + describe('method: registerIdentity', () => { it('should prepare the procedure with the correct arguments and context, and return the resulting transaction', async () => { const args = { diff --git a/src/api/procedures/__tests__/addSecondaryAccountsWithAuth.ts b/src/api/procedures/__tests__/addSecondaryAccountsWithAuth.ts index cfcf80dd9d..93ab164de5 100644 --- a/src/api/procedures/__tests__/addSecondaryAccountsWithAuth.ts +++ b/src/api/procedures/__tests__/addSecondaryAccountsWithAuth.ts @@ -40,6 +40,7 @@ describe('addSecondaryAccounts procedure', () => { }); beforeEach(() => { + // jest.spyOn() identity = entityMockUtils.getIdentityInstance(); actingAccount = entityMockUtils.getAccountInstance(); @@ -61,7 +62,6 @@ describe('addSecondaryAccounts procedure', () => { assets: null, portfolios: null, transactions: null, - transactionGroups: [], }, }, authSignature: '0xSignature', @@ -94,6 +94,23 @@ describe('addSecondaryAccounts procedure', () => { dsMockUtils.cleanup(); }); + it('should throw an error if the expiry date is not valid', () => { + const proc = procedureMockUtils.getInstance( + mockContext, + { + identity, + actingAccount, + } + ); + + return expect( + prepareAddSecondaryKeysWithAuth.call(proc, { + ...params, + expiresAt: new Date('2020/01/01'), + }) + ).rejects.toThrow('Expiry date must be in the future'); + }); + it('should throw an error if the one or more accounts are already linked to an Identity', () => { const accounts = [ { diff --git a/src/api/procedures/__tests__/createChildIdentities.ts b/src/api/procedures/__tests__/createChildIdentities.ts new file mode 100644 index 0000000000..0da1d8386b --- /dev/null +++ b/src/api/procedures/__tests__/createChildIdentities.ts @@ -0,0 +1,271 @@ +import { Vec } from '@polkadot/types/codec'; +import { Moment } from '@polkadot/types/interfaces/runtime'; +import { PolymeshCommonUtilitiesIdentityCreateChildIdentityWithAuth } from '@polkadot/types/lookup'; +import { ISubmittableResult } from '@polkadot/types/types'; +import BigNumber from 'bignumber.js'; +import { when } from 'jest-when'; + +import { ChildIdentity } from '~/api/entities/Identity/ChildIdentity'; +import { + createChildIdentityResolver, + getAuthorization, + prepareCreateChildIdentities, + prepareStorage, + Storage, +} from '~/api/procedures/createChildIdentities'; +import { Account, Context, Identity } from '~/internal'; +import { dsMockUtils, entityMockUtils, procedureMockUtils } from '~/testUtils/mocks'; +import { Mocked } from '~/testUtils/types'; +import { CreateChildIdentitiesParams, TxTags } from '~/types'; +import * as utilsConversionModule from '~/utils/conversion'; +import * as utilsInternalModule from '~/utils/internal'; + +jest.mock( + '~/api/entities/Identity/ChildIdentity', + require('~/testUtils/mocks/entities').mockChildIdentityModule( + '~/api/entities/Identity/ChildIdentity' + ) +); + +describe('createChildIdentities procedure', () => { + let mockContext: Mocked; + let identity: Identity; + let actingAccount: Account; + let childAccount: Account; + let childKeysWithAuthToCreateChildIdentitiesWithAuthSpy: jest.SpyInstance; + + let rawChildKeyWithAuths: Vec; + let args: CreateChildIdentitiesParams; + let expiresAt: Date; + let rawExpiresAt: Moment; + let dateToMomentSpy: jest.SpyInstance; + + beforeAll(() => { + dsMockUtils.initMocks(); + procedureMockUtils.initMocks(); + entityMockUtils.initMocks(); + + childKeysWithAuthToCreateChildIdentitiesWithAuthSpy = jest.spyOn( + utilsConversionModule, + 'childKeysWithAuthToCreateChildIdentitiesWithAuth' + ); + dateToMomentSpy = jest.spyOn(utilsConversionModule, 'dateToMoment'); + }); + + beforeEach(() => { + entityMockUtils.configureMocks({ + childIdentityOptions: { + getParentDid: null, + }, + }); + + childAccount = entityMockUtils.getAccountInstance({ + address: 'childAddress', + getIdentity: null, + }); + + identity = entityMockUtils.getIdentityInstance(); + actingAccount = entityMockUtils.getAccountInstance(); + + mockContext = dsMockUtils.getContextInstance({ + getIdentity: identity, + }); + + expiresAt = new Date('2050/01/01'); + + args = { + childKeyAuths: [ + { + key: childAccount, + authSignature: '0xsignature', + }, + ], + expiresAt, + }; + + rawExpiresAt = dsMockUtils.createMockMoment(new BigNumber(expiresAt.getTime())); + when(dateToMomentSpy).calledWith(expiresAt, mockContext).mockReturnValue(rawExpiresAt); + + rawChildKeyWithAuths = + 'someKeysWithAuth' as unknown as Vec; + + when(childKeysWithAuthToCreateChildIdentitiesWithAuthSpy) + .calledWith(args.childKeyAuths, mockContext) + .mockReturnValue(rawChildKeyWithAuths); + }); + + afterEach(() => { + entityMockUtils.reset(); + procedureMockUtils.reset(); + dsMockUtils.reset(); + }); + + afterAll(() => { + jest.resetAllMocks(); + procedureMockUtils.cleanup(); + dsMockUtils.cleanup(); + }); + + it('should throw an error if expiry date is not valid', () => { + const proc = procedureMockUtils.getInstance< + CreateChildIdentitiesParams, + ChildIdentity[], + Storage + >(mockContext, { identity, actingAccount }); + + return expect( + prepareCreateChildIdentities.call(proc, { + ...args, + expiresAt: new Date('2020/01/01'), + }) + ).rejects.toThrow('Expiry date must be in the future'); + }); + + it('should throw an error if the signing Identity is already a child Identity', () => { + entityMockUtils.configureMocks({ + childIdentityOptions: { + getParentDid: entityMockUtils.getIdentityInstance({ did: 'someParentDid' }), + }, + }); + + const proc = procedureMockUtils.getInstance< + CreateChildIdentitiesParams, + ChildIdentity[], + Storage + >(mockContext, { identity, actingAccount }); + + return expect(prepareCreateChildIdentities.call(proc, args)).rejects.toThrow( + 'The signing Identity is already a child Identity and cannot create further child identities' + ); + }); + + it('should throw an error if the one or more accounts are already linked to an Identity', () => { + const proc = procedureMockUtils.getInstance< + CreateChildIdentitiesParams, + ChildIdentity[], + Storage + >(mockContext, { identity, actingAccount }); + + return expect( + prepareCreateChildIdentities.call(proc, { + childKeyAuths: [ + { + key: entityMockUtils.getAccountInstance({ + address: 'secondaryAccount', + getIdentity: entityMockUtils.getIdentityInstance({ did: 'someRandomDid' }), + }), + authSignature: '0xsignature', + }, + ], + expiresAt, + }) + ).rejects.toThrow('One or more accounts are already linked to some Identity'); + }); + + it('should add a createChildIdentities transaction to the queue', async () => { + const proc = procedureMockUtils.getInstance< + CreateChildIdentitiesParams, + ChildIdentity[], + Storage + >(mockContext, { identity, actingAccount }); + + const createChildIdentitiesTransaction = dsMockUtils.createTxMock( + 'identity', + 'createChildIdentities' + ); + + const result = await prepareCreateChildIdentities.call(proc, args); + + expect(result).toEqual({ + transaction: createChildIdentitiesTransaction, + resolver: expect.any(Function), + args: [rawChildKeyWithAuths, rawExpiresAt], + }); + }); + + describe('getAuthorization', () => { + it('should return the appropriate roles and permissions', async () => { + let proc = procedureMockUtils.getInstance< + CreateChildIdentitiesParams, + ChildIdentity[], + Storage + >(mockContext, { identity, actingAccount }); + let boundFunc = getAuthorization.bind(proc); + + let result = await boundFunc(); + expect(result).toEqual({ + permissions: { + transactions: [TxTags.identity.CreateChildIdentities], + assets: [], + portfolios: [], + }, + }); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (actingAccount.isEqual as any).mockReturnValue(false); + + proc = procedureMockUtils.getInstance( + dsMockUtils.getContextInstance({ + signingAccountIsEqual: false, + }), + { identity, actingAccount } + ); + + boundFunc = getAuthorization.bind(proc); + + result = await boundFunc(); + expect(result).toEqual({ + signerPermissions: "Child Identities can only be created by an Identity's primary Account", + }); + }); + }); + + describe('prepareStorage', () => { + it('should return the signing Identity', async () => { + const proc = procedureMockUtils.getInstance< + CreateChildIdentitiesParams, + ChildIdentity[], + Storage + >(mockContext); + const boundFunc = prepareStorage.bind(proc); + + const result = await boundFunc(); + + expect(result).toEqual({ + identity: expect.objectContaining({ + did: 'someDid', + }), + actingAccount: expect.objectContaining({ + address: '0xdummy', + }), + }); + }); + }); + + describe('createChildIdentityResolver', () => { + const filterEventRecordsSpy = jest.spyOn(utilsInternalModule, 'filterEventRecords'); + const did = 'someDid'; + const rawIdentityId = dsMockUtils.createMockIdentityId(did); + const childDid = 'someChildDid'; + const rawChildIdentity = dsMockUtils.createMockIdentityId(childDid); + + beforeEach(() => { + filterEventRecordsSpy.mockReturnValue([ + dsMockUtils.createMockIEvent([rawIdentityId, rawChildIdentity]), + ]); + }); + + afterEach(() => { + jest.resetAllMocks(); + filterEventRecordsSpy.mockReset(); + }); + + it('should return the new ChildIdentity', () => { + const fakeContext = {} as Context; + + const result = createChildIdentityResolver(fakeContext)({} as ISubmittableResult); + + expect(result[0].did).toEqual(childDid); + }); + }); +}); diff --git a/src/api/procedures/__tests__/registerIdentity.ts b/src/api/procedures/__tests__/registerIdentity.ts index 5f93d1d820..833ec7078a 100644 --- a/src/api/procedures/__tests__/registerIdentity.ts +++ b/src/api/procedures/__tests__/registerIdentity.ts @@ -69,6 +69,7 @@ describe('registerIdentity procedure', () => { mockContext = dsMockUtils.getContextInstance(); registerIdentityTransaction = dsMockUtils.createTxMock('identity', 'cddRegisterDid'); proc = procedureMockUtils.getInstance(mockContext); + jest.spyOn(utilsInternalModule, 'assertAddressValid').mockImplementation(); }); afterEach(() => { diff --git a/src/api/procedures/addSecondaryAccountsWithAuth.ts b/src/api/procedures/addSecondaryAccountsWithAuth.ts index 5ef306481b..de43b38ed7 100644 --- a/src/api/procedures/addSecondaryAccountsWithAuth.ts +++ b/src/api/procedures/addSecondaryAccountsWithAuth.ts @@ -4,6 +4,7 @@ import { PolymeshError, Procedure } from '~/internal'; import { Account, AddSecondaryAccountsParams, ErrorCode, Identity, TxTags } from '~/types'; import { ExtrinsicParams, ProcedureAuthorization, TransactionSpec } from '~/types/internal'; import { dateToMoment, secondaryAccountWithAuthToSecondaryKeyWithAuth } from '~/utils/conversion'; +import { asAccount } from '~/utils/internal'; /** * @hidden @@ -32,8 +33,15 @@ export async function prepareAddSecondaryKeysWithAuth( const { accounts, expiresAt } = args; + if (expiresAt <= new Date()) { + throw new PolymeshError({ + code: ErrorCode.ValidationError, + message: 'Expiry date must be in the future', + }); + } + const identities = await Promise.all( - accounts.map(({ secondaryAccount: { account } }) => account.getIdentity()) + accounts.map(({ secondaryAccount: { account } }) => asAccount(account, context).getIdentity()) ); if (identities.some(identityValue => identityValue !== null)) { diff --git a/src/api/procedures/createChildIdentities.ts b/src/api/procedures/createChildIdentities.ts new file mode 100644 index 0000000000..446ab99786 --- /dev/null +++ b/src/api/procedures/createChildIdentities.ts @@ -0,0 +1,147 @@ +import { ISubmittableResult } from '@polkadot/types/types'; + +import { ChildIdentity, Context, Identity, PolymeshError, Procedure } from '~/internal'; +import { Account, CreateChildIdentitiesParams, ErrorCode, TxTags } from '~/types'; +import { ExtrinsicParams, ProcedureAuthorization, TransactionSpec } from '~/types/internal'; +import { + childKeysWithAuthToCreateChildIdentitiesWithAuth, + dateToMoment, + identityIdToString, +} from '~/utils/conversion'; +import { asAccount, filterEventRecords } from '~/utils/internal'; + +/** + * @hidden + */ +export interface Storage { + identity: Identity; + actingAccount: Account; +} + +/** + * @hidden + */ +export const createChildIdentityResolver = + (context: Context) => + (receipt: ISubmittableResult): ChildIdentity[] => { + const childDids = filterEventRecords(receipt, 'identity', 'ChildDidCreated'); + + return childDids.map( + ({ data }) => new ChildIdentity({ did: identityIdToString(data[1]) }, context) + ); + }; + +/** + * @hidden + */ +export async function prepareCreateChildIdentities( + this: Procedure, + args: CreateChildIdentitiesParams +): Promise>> { + const { + context: { + polymeshApi: { tx }, + }, + context, + storage: { + identity: { did: signingDid }, + }, + } = this; + + const { childKeyAuths, expiresAt } = args; + + if (expiresAt <= new Date()) { + throw new PolymeshError({ + code: ErrorCode.ValidationError, + message: 'Expiry date must be in the future', + }); + } + + const childIdentity = new ChildIdentity({ did: signingDid }, context); + + const [parentDid, ...identities] = await Promise.all([ + childIdentity.getParentDid(), + ...childKeyAuths.map(({ key }) => asAccount(key, context).getIdentity()), + ]); + + if (parentDid) { + throw new PolymeshError({ + code: ErrorCode.UnmetPrerequisite, + message: + 'The signing Identity is already a child Identity and cannot create further child identities', + data: { + parentDid, + }, + }); + } + + if (identities.some(identityValue => identityValue !== null)) { + throw new PolymeshError({ + code: ErrorCode.UnmetPrerequisite, + message: 'One or more accounts are already linked to some Identity', + }); + } + + const rawExpiry = dateToMoment(expiresAt, context); + + return { + transaction: tx.identity.createChildIdentities, + args: [childKeysWithAuthToCreateChildIdentitiesWithAuth(childKeyAuths, context), rawExpiry], + resolver: createChildIdentityResolver(context), + }; +} + +/** + * @hidden + */ +export async function getAuthorization( + this: Procedure +): Promise { + const { + storage: { identity, actingAccount }, + } = this; + + const { account: primaryAccount } = await identity.getPrimaryAccount(); + + if (!actingAccount.isEqual(primaryAccount)) { + return { + signerPermissions: "Child Identities can only be created by an Identity's primary Account", + }; + } + + return { + permissions: { + transactions: [TxTags.identity.CreateChildIdentities], + assets: [], + portfolios: [], + }, + }; +} + +/** + * @hidden + */ +export async function prepareStorage( + this: Procedure +): Promise { + const { context } = this; + + const [identity, actingAccount] = await Promise.all([ + context.getSigningIdentity(), + context.getActingAccount(), + ]); + + return { + identity, + actingAccount, + }; +} + +/** + * @hidden + */ +export const createChildIdentities = (): Procedure< + CreateChildIdentitiesParams, + ChildIdentity[], + Storage +> => new Procedure(prepareCreateChildIdentities, getAuthorization, prepareStorage); diff --git a/src/api/procedures/types.ts b/src/api/procedures/types.ts index 5944d9b085..e3b131c3cb 100644 --- a/src/api/procedures/types.ts +++ b/src/api/procedures/types.ts @@ -601,13 +601,38 @@ export interface RemoveSecondaryAccountsParams { } export interface AccountWithSignature { - secondaryAccount: PermissionedAccount; + /** + * The secondary Account along with its permissions to be added + * + * @note This account should not be linked to any other Identity + */ + secondaryAccount: Modify< + PermissionedAccount, + { account: string | Account; permissions: PermissionsLike } + >; + /** + * Off-chain authorization signature generated by `secondaryAccount` signing of the target Id authorization + * + * Target Id authorization consists of the target Identity (to which the secondary account will be added), + * off chain authorization nonce of the target Identity and expiry date (same as `expiresAt` value) until which the off chain authorization will be valid. + * Signature has to be generated encoding the target Id authorization value in the specified order. + * + * @note Nonce value can be fetched using {@link api/entities/Identity!Identity.getOffChainAuthorizationNonce | Identity.getOffChainAuthorizationNonce } + * Signature can also be generated using the method {@link api/client/AccountManagement!AccountManagement.generateOffChainAuthSignature | accountManagement.generateOffChainAuthSignature } + */ authSignature: string; } export interface AddSecondaryAccountsParams { - accounts: AccountWithSignature[]; + /** + * Expiry date until which all the off chain authorizations received from each account is valid + */ expiresAt: Date; + + /** + * List of accounts to be added as secondary accounts along with their off chain authorization signatures + */ + accounts: AccountWithSignature[]; } export interface SubsidizeAccountParams { @@ -1628,15 +1653,36 @@ export interface CreateChildIdentityParams { secondaryKey: string | Account; } +export interface ChildKeyWithAuth { + /** + * The key that will become the primary key of the new child Identity + * + * @note This key should not be linked to any other Identity + */ + key: string | Account; + /** + * Off-chain authorization signature generated by `key` signing of the target Id authorization + * + * Target Id authorization consists of the target Identity (which will become the parent of the child Identity), + * off chain authorization nonce of the target Identity and expiry date (same as `expiresAt` value) until which the off chain authorization will be valid. + * Signature has to be generated encoding the target Id authorization value in the specified order. + * + * @note Nonce value can be fetched using {@link api/entities/Identity!Identity.getOffChainAuthorizationNonce | Identity.getOffChainAuthorizationNonce } + * Signature can also be generated using the method {@link api/client/AccountManagement!AccountManagement.generateOffChainAuthSignature | accountManagement.generateOffChainAuthSignature } + */ + authSignature: `0x${string}`; +} + export interface CreateChildIdentitiesParams { /** - * The secondary keys that will become the primary keys of the new child Identities + * Expiry date until which all the off chain authorizations received from each key will be valid */ - secondaryKeys: (string | Account)[]; + expiresAt: Date; + /** - * Expiry date of the signed authorization + * List of child keys along with their off chain authorization signatures */ - expiry: Date; + childKeyAuths: ChildKeyWithAuth[]; } export interface UnlinkChildParams { diff --git a/src/internal.ts b/src/internal.ts index 4128f335fe..a52e05c35b 100644 --- a/src/internal.ts +++ b/src/internal.ts @@ -47,6 +47,7 @@ export { } from '~/api/procedures/modifyAssetTrustedClaimIssuers'; export { registerIdentity } from '~/api/procedures/registerIdentity'; export { createChildIdentity } from '~/api/procedures/createChildIdentity'; +export { createChildIdentities } from '~/api/procedures/createChildIdentities'; export { attestPrimaryKeyRotation } from '~/api/procedures/attestPrimaryKeyRotation'; export { rotatePrimaryKey } from '~/api/procedures/rotatePrimaryKey'; export { addSecondaryAccounts } from '~/api/procedures/addSecondaryAccountsWithAuth'; diff --git a/src/utils/__tests__/conversion.ts b/src/utils/__tests__/conversion.ts index 6f87a9bb3a..c7e5247ea9 100644 --- a/src/utils/__tests__/conversion.ts +++ b/src/utils/__tests__/conversion.ts @@ -10,6 +10,7 @@ import { Moment, Permill, } from '@polkadot/types/interfaces'; +import { H512 } from '@polkadot/types/interfaces/runtime'; import { PalletCorporateActionsCaId, PalletCorporateActionsCaKind, @@ -17,6 +18,7 @@ import { PalletCorporateActionsTargetIdentities, PalletStoPriceTier, PolymeshCommonUtilitiesCheckpointScheduleCheckpoints, + PolymeshCommonUtilitiesIdentityCreateChildIdentityWithAuth, PolymeshCommonUtilitiesIdentitySecondaryKeyWithAuth, PolymeshCommonUtilitiesProtocolFeeProtocolOp, PolymeshPrimitivesAgentAgentGroup, @@ -118,6 +120,7 @@ import { AssetDocument, Authorization, AuthorizationType, + ChildKeyWithAuth, Claim, ClaimType, Condition, @@ -170,7 +173,7 @@ import { import { InstructionStatus, PermissionGroupIdentifier } from '~/types/internal'; import { tuple } from '~/types/utils'; import { DUMMY_ACCOUNT_ID, MAX_BALANCE, MAX_DECIMALS, MAX_TICKER_LENGTH } from '~/utils/constants'; -import * as internalUtils from '~/utils/internal'; +import * as utilsInternalModule from '~/utils/internal'; import { padString } from '~/utils/internal'; import { @@ -199,6 +202,7 @@ import { cddIdToString, cddStatusToBoolean, checkpointToRecordDateSpec, + childKeysWithAuthToCreateChildIdentitiesWithAuth, claimCountStatInputToStatUpdates, claimCountToClaimCountRestrictionValue, claimToMeshClaim, @@ -4480,7 +4484,7 @@ describe('middlewareClaimToClaimData', () => { beforeAll(() => { dsMockUtils.initMocks(); entityMockUtils.initMocks(); - createClaimSpy = jest.spyOn(internalUtils, 'createClaim'); + createClaimSpy = jest.spyOn(utilsInternalModule, 'createClaim'); }); afterEach(() => { @@ -5881,6 +5885,7 @@ describe('secondaryAccountToMeshSecondaryKey', () => { }); it('should convert a SecondaryAccount to a polkadot SecondaryKey', () => { + jest.spyOn(utilsInternalModule, 'assertAddressValid').mockImplementation(); const address = 'someAccount'; const context = dsMockUtils.getContextInstance(); const secondaryAccount = { @@ -5906,7 +5911,7 @@ describe('secondaryAccountToMeshSecondaryKey', () => { when(context.createType) .calledWith('PolymeshPrimitivesSecondaryKey', { - signer: signerValueToSignatory({ type: SignerType.Account, value: address }, context), + key: stringToAccountId(address, context), permissions: permissionsToMeshPermissions(secondaryAccount.permissions, context), }) .mockReturnValue(fakeResult); @@ -10203,6 +10208,7 @@ describe('secondaryAccountWithAuthToSecondaryKeyWithAuth', () => { }); it('should create additional keys', () => { + jest.spyOn(utilsInternalModule, 'assertAddressValid').mockImplementation(); const context = dsMockUtils.getContextInstance(); const accounts = [ @@ -10216,7 +10222,6 @@ describe('secondaryAccountWithAuthToSecondaryKeyWithAuth', () => { assets: null, portfolios: null, transactions: null, - transactionGroups: [], }, }, authSignature: '0xSignature', @@ -10235,3 +10240,52 @@ describe('secondaryAccountWithAuthToSecondaryKeyWithAuth', () => { expect(result).toEqual(fakeResult); }); }); + +describe('childKeysWithAuthToCreateChildIdentitiesWithAuth', () => { + beforeAll(() => { + dsMockUtils.initMocks(); + }); + + afterEach(() => { + dsMockUtils.reset(); + }); + + afterAll(() => { + dsMockUtils.cleanup(); + }); + + it('should create child identities with auth', () => { + const context = dsMockUtils.getContextInstance(); + + const childKey = '5EYCAe5ijAx5xEfZdpCna3grUpY1M9M5vLUH5vpmwV1EnaYR'; + const childKeyAuths: ChildKeyWithAuth[] = [ + { + key: childKey, + authSignature: '0xSignature', + }, + ]; + + const childAccountId = 'childKey' as unknown as AccountId; + + when(context.createType).calledWith('AccountId', childKey).mockReturnValue(childAccountId); + + const h512Signature = '0xSignature' as unknown as H512; + when(context.createType).calledWith('H512', '0xSignature').mockReturnValue(h512Signature); + + const fakeResult = + 'fakeSecondaryKeysWithAuth' as unknown as Vec; + + when(context.createType) + .calledWith('Vec', [ + { + key: childAccountId, + authSignature: h512Signature, + }, + ]) + .mockReturnValue(fakeResult); + + const result = childKeysWithAuthToCreateChildIdentitiesWithAuth(childKeyAuths, context); + + expect(result).toEqual(fakeResult); + }); +}); diff --git a/src/utils/conversion.ts b/src/utils/conversion.ts index 1dabfb1065..bf7d8f523d 100644 --- a/src/utils/conversion.ts +++ b/src/utils/conversion.ts @@ -8,6 +8,7 @@ import { Hash, Permill, } from '@polkadot/types/interfaces'; +import { H512 } from '@polkadot/types/interfaces/runtime'; import { DispatchError, DispatchResult } from '@polkadot/types/interfaces/system'; import { PalletCorporateActionsCaId, @@ -21,6 +22,7 @@ import { PalletStoFundraiserTier, PalletStoPriceTier, PolymeshCommonUtilitiesCheckpointScheduleCheckpoints, + PolymeshCommonUtilitiesIdentityCreateChildIdentityWithAuth, PolymeshCommonUtilitiesIdentitySecondaryKeyWithAuth, PolymeshCommonUtilitiesProtocolFeeProtocolOp, PolymeshPrimitivesAgentAgentGroup, @@ -152,6 +154,7 @@ import { AssetDocument, Authorization, AuthorizationType, + ChildKeyWithAuth, Claim, ClaimCountRestrictionValue, ClaimCountStatInput, @@ -358,6 +361,13 @@ export function stringToU8aFixed(value: string, context: Context): U8aFixed { return context.createType('U8aFixed', value); } +/** + * @hidden + */ +export function stringToH512(value: string, context: Context): H512 { + return context.createType('H512', value); +} + /** * @hidden */ @@ -2821,10 +2831,13 @@ export function secondaryAccountToMeshSecondaryKey( secondaryKey: PermissionedAccount, context: Context ): PolymeshPrimitivesSecondaryKey { - const { account, permissions } = secondaryKey; + const { + account: { address }, + permissions, + } = secondaryKey; return context.createType('PolymeshPrimitivesSecondaryKey', { - signer: signerValueToSignatory(signerToSignerValue(account), context), + key: stringToAccountId(address, context), permissions: permissionsToMeshPermissions(permissions, context), }); } @@ -4991,13 +5004,40 @@ export function secondaryAccountWithAuthToSecondaryKeyWithAuth( accounts: AccountWithSignature[], context: Context ): Vec { - const keyWithAuths = accounts.map(({ secondaryAccount, authSignature }) => ({ - secondaryKey: secondaryAccountToMeshSecondaryKey(secondaryAccount, context), - authSignature: stringToU8aFixed(authSignature, context), - })); + const keyWithAuths = accounts.map(({ secondaryAccount, authSignature }) => { + const { account, permissions } = secondaryAccount; + return { + secondaryKey: secondaryAccountToMeshSecondaryKey( + { + account: asAccount(account, context), + permissions: permissionsLikeToPermissions(permissions, context), + }, + context + ), + authSignature: stringToH512(authSignature, context), + }; + }); return context.createType( 'Vec', keyWithAuths ); } + +/** + * @hidden + */ +export function childKeysWithAuthToCreateChildIdentitiesWithAuth( + childKeyAuths: ChildKeyWithAuth[], + context: Context +): Vec { + const keyWithAuths = childKeyAuths.map(({ key, authSignature }) => ({ + key: stringToAccountId(asAccount(key, context).address, context), + authSignature: stringToH512(authSignature, context), + })); + + return context.createType( + 'Vec', + keyWithAuths + ); +}