From 5053f00202b36fb389b779e6478de67d8baaece3 Mon Sep 17 00:00:00 2001 From: Prashant Bajpai <34747455+prashantasdeveloper@users.noreply.github.com> Date: Thu, 18 Jul 2024 16:30:53 +0530 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20Add=20procedure=20to=20a?= =?UTF-8?q?dd=20secondary=20accounts=20to=20an=20Identity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also, adds in method to get off chain authorization nonce for an Identity --- src/api/client/AccountManagement.ts | 59 ++++- src/api/client/__tests__/AccountManagement.ts | 64 +++++- src/api/entities/Identity/__tests__/index.ts | 25 +++ src/api/entities/Identity/index.ts | 23 ++ .../__tests__/addSecondaryAccountsWithAuth.ts | 208 ++++++++++++++++++ .../addSecondaryAccountsWithAuth.ts | 106 +++++++++ src/api/procedures/types.ts | 10 + src/internal.ts | 1 + src/testUtils/mocks/entities.ts | 7 + src/utils/__tests__/conversion.ts | 50 +++++ src/utils/conversion.ts | 20 ++ 11 files changed, 571 insertions(+), 2 deletions(-) create mode 100644 src/api/procedures/__tests__/addSecondaryAccountsWithAuth.ts create mode 100644 src/api/procedures/addSecondaryAccountsWithAuth.ts diff --git a/src/api/client/AccountManagement.ts b/src/api/client/AccountManagement.ts index 9510862279..150ae9aeb1 100644 --- a/src/api/client/AccountManagement.ts +++ b/src/api/client/AccountManagement.ts @@ -1,7 +1,10 @@ +import { hexAddPrefix, hexStripPrefix } from '@polkadot/util'; + import { MultiSig } from '~/api/entities/Account/MultiSig'; import { acceptPrimaryKeyRotation, Account, + addSecondaryAccounts, AuthorizationRequest, Context, createMultiSigAccount, @@ -17,7 +20,9 @@ import { import { AcceptPrimaryKeyRotationParams, AccountBalance, + AddSecondaryAccountsParams, CreateMultiSigParams, + Identity, InviteAccountParams, ModifySignerPermissionsParams, NoArgsProcedureMethod, @@ -28,7 +33,14 @@ import { SubsidizeAccountParams, UnsubCallback, } from '~/types'; -import { asAccount, assertAddressValid, createProcedureMethod, getAccount } from '~/utils/internal'; +import { bigNumberToU64, dateToMoment, stringToIdentityId } from '~/utils/conversion'; +import { + asAccount, + asIdentity, + assertAddressValid, + createProcedureMethod, + getAccount, +} from '~/utils/internal'; /** * Handles functionality related to Account Management @@ -52,6 +64,13 @@ export class AccountManagement { }, context ); + + this.addSecondaryAccounts = createProcedureMethod( + { + getProcedureAndArgs: args => [addSecondaryAccounts, { ...args }], + }, + context + ); this.revokePermissions = createProcedureMethod< { secondaryAccounts: (string | Account)[] }, ModifySignerPermissionsParams, @@ -122,6 +141,13 @@ export class AccountManagement { */ public removeSecondaryAccounts: ProcedureMethod; + /** + * Adds a list of secondary Accounts to the signing Identity + * + * @throws if the signing Account is not the primary Account of the Identity + */ + public addSecondaryAccounts: ProcedureMethod; + /** * Revoke all permissions of a list of secondary Accounts associated with the signing Identity * @@ -291,4 +317,35 @@ export class AccountManagement { * unlinked to any identity. */ public acceptPrimaryKey: ProcedureMethod; + + /** + * Generate an offchain authorization signature with a specified signer + * + * @param args.signer Signer to be used to generate the off chain auth signature + * @param args.target DID of the identity to which signer is targeting the authorization + * @param args.expiry date after which the authorization expires + */ + public async generateOffChainAuthSignature(args: { + signer: string | Account; + target: string | Identity; + expiry: Date; + }): Promise<`0x${string}`> { + const { context } = this; + + const { target, signer, expiry } = args; + + const targetIdentity = asIdentity(target, context); + + const offChainAuthNonce = await targetIdentity.getOffChainAuthorizationNonce(); + + const rawTargetDid = stringToIdentityId(targetIdentity.did, context); + const rawNonce = bigNumberToU64(offChainAuthNonce, context); + const rawExpiry = dateToMoment(expiry, context); + + const payloadStrings = [rawTargetDid.toHex(), rawNonce.toHex(true), rawExpiry.toHex(true)]; + + const rawPayload = hexAddPrefix(payloadStrings.map(e => hexStripPrefix(e)).join('')); + + return context.getSignature({ rawPayload, signer }); + } } diff --git a/src/api/client/__tests__/AccountManagement.ts b/src/api/client/__tests__/AccountManagement.ts index 30796b2841..a96edbf1b1 100644 --- a/src/api/client/__tests__/AccountManagement.ts +++ b/src/api/client/__tests__/AccountManagement.ts @@ -5,7 +5,7 @@ import { AccountManagement } from '~/api/client/AccountManagement'; import { Account, MultiSig, PolymeshTransaction, Subsidy } from '~/internal'; import { dsMockUtils, entityMockUtils, procedureMockUtils } from '~/testUtils/mocks'; import { MockContext } from '~/testUtils/mocks/dataSources'; -import { AccountBalance, PermissionType, SubCallback } from '~/types'; +import { AccountBalance, Identity, PermissionType, SubCallback } from '~/types'; import * as utilsConversionModule from '~/utils/conversion'; import * as utilsInternalModule from '~/utils/internal'; @@ -396,4 +396,66 @@ describe('AccountManagement class', () => { expect(tx).toBe(expectedTransaction); }); }); + + describe('method: addSecondaryAccounts', () => { + it('should prepare the procedure with the correct arguments and context, and return the resulting transaction', async () => { + const expectedTransaction = 'someTransaction' as unknown as PolymeshTransaction; + + const args = { + accounts: [ + { + secondaryAccount: { + account: entityMockUtils.getAccountInstance({ + address: 'secondaryAccount', + getIdentity: null, + }), + permissions: { + assets: null, + portfolios: null, + transactions: null, + transactionGroups: [], + }, + }, + authSignature: '0xSignature', + }, + ], + expiresAt: new Date('2050/01/01'), + }; + + when(procedureMockUtils.getPrepareMock()) + .calledWith({ args, transformer: undefined }, context, {}) + .mockResolvedValue(expectedTransaction); + + const tx = await accountManagement.addSecondaryAccounts(args); + + expect(tx).toBe(expectedTransaction); + }); + }); + + describe('generateOffChainAuthSignature', () => { + it('should generate off chain authorization signature for a specific signer targeting an Identity', async () => { + const args = { + signer: entityMockUtils.getAccountInstance({ address: 'signer' }), + target: entityMockUtils.getIdentityInstance({ did: 'someTargetDid' }), + expiry: new Date('2050/01/01'), + }; + + const rawTargetId = dsMockUtils.createMockIdentityId(args.target.did); + rawTargetId.toHex = jest.fn(); + rawTargetId.toHex.mockReturnValue('0x1000000'.padEnd(66, '0')); + + const rawNonce = dsMockUtils.createMockU64(new BigNumber(0)); + const rawMoment = dsMockUtils.createMockMoment(new BigNumber(args.expiry.getTime())); + + jest.spyOn(utilsConversionModule, 'stringToIdentityId').mockReturnValue(rawTargetId); + + jest.spyOn(utilsConversionModule, 'bigNumberToU64').mockReturnValue(rawNonce); + + jest.spyOn(utilsConversionModule, 'dateToMoment').mockReturnValue(rawMoment); + + const result = await accountManagement.generateOffChainAuthSignature(args); + + expect(result).toEqual('0xsignature'); + }); + }); }); diff --git a/src/api/entities/Identity/__tests__/index.ts b/src/api/entities/Identity/__tests__/index.ts index 473abbcacb..30ef1a5217 100644 --- a/src/api/entities/Identity/__tests__/index.ts +++ b/src/api/entities/Identity/__tests__/index.ts @@ -1436,4 +1436,29 @@ describe('Identity class', () => { ]); }); }); + + describe('method: getOffChainAuthorizationNonce', () => { + it('should return the off chain authorization nonce for an Identity', async () => { + const did = 'someDid'; + const rawIdentityId = dsMockUtils.createMockIdentityId(did); + const mockContext = dsMockUtils.getContextInstance(); + + stringToIdentityIdSpy.mockReturnValue(rawIdentityId); + + const nonce = new BigNumber(2); + const rawNonce = dsMockUtils.createMockU64(nonce); + + dsMockUtils + .createQueryMock('identity', 'offChainAuthorizationNonce') + .mockResolvedValue(rawNonce); + + when(u64ToBigNumberSpy).calledWith(rawNonce).mockReturnValue(nonce); + + const identity = new Identity({ did }, mockContext); + + const result = await identity.getOffChainAuthorizationNonce(); + + expect(result).toEqual(nonce); + }); + }); }); diff --git a/src/api/entities/Identity/index.ts b/src/api/entities/Identity/index.ts index e91172d9da..87935a7c9b 100644 --- a/src/api/entities/Identity/index.ts +++ b/src/api/entities/Identity/index.ts @@ -1072,4 +1072,27 @@ export class Identity extends Entity { signers: multiSigs[multiSig], })); } + + /** + * Returns the off chain authorization nonce for this Identity + */ + public async getOffChainAuthorizationNonce(): Promise { + const { + context, + context: { + polymeshApi: { + query: { + identity: { offChainAuthorizationNonce }, + }, + }, + }, + did, + } = this; + + const rawDid = stringToIdentityId(did, context); + + const rawNonce = await offChainAuthorizationNonce(rawDid); + + return u64ToBigNumber(rawNonce); + } } diff --git a/src/api/procedures/__tests__/addSecondaryAccountsWithAuth.ts b/src/api/procedures/__tests__/addSecondaryAccountsWithAuth.ts new file mode 100644 index 0000000000..cfcf80dd9d --- /dev/null +++ b/src/api/procedures/__tests__/addSecondaryAccountsWithAuth.ts @@ -0,0 +1,208 @@ +import { Vec } from '@polkadot/types/codec'; +import { Moment } from '@polkadot/types/interfaces'; +import { PolymeshCommonUtilitiesIdentitySecondaryKeyWithAuth } from '@polkadot/types/lookup'; +import { BigNumber } from 'bignumber.js'; +import { when } from 'jest-when'; + +import { + getAuthorization, + prepareAddSecondaryKeysWithAuth, + prepareStorage, + Storage, +} from '~/api/procedures/addSecondaryAccountsWithAuth'; +import { Context, Identity } from '~/internal'; +import { dsMockUtils, entityMockUtils, procedureMockUtils } from '~/testUtils/mocks'; +import { Mocked } from '~/testUtils/types'; +import { Account, AddSecondaryAccountsParams, TxTags } from '~/types'; +import * as utilsConversionModule from '~/utils/conversion'; + +describe('addSecondaryAccounts procedure', () => { + let mockContext: Mocked; + let identity: Identity; + let actingAccount: Account; + let params: AddSecondaryAccountsParams; + let rawAdditionalKeys: Vec; + let rawExpiry: Moment; + let dateToMomentSpy: jest.SpyInstance; + let secondaryAccountWithAuthToSecondaryKeyWithAuthSpy: jest.SpyInstance; + let expiry: Date; + + beforeAll(() => { + dsMockUtils.initMocks(); + procedureMockUtils.initMocks(); + entityMockUtils.initMocks(); + + secondaryAccountWithAuthToSecondaryKeyWithAuthSpy = jest.spyOn( + utilsConversionModule, + 'secondaryAccountWithAuthToSecondaryKeyWithAuth' + ); + dateToMomentSpy = jest.spyOn(utilsConversionModule, 'dateToMoment'); + }); + + beforeEach(() => { + identity = entityMockUtils.getIdentityInstance(); + actingAccount = entityMockUtils.getAccountInstance(); + + mockContext = dsMockUtils.getContextInstance({ + getIdentity: identity, + }); + + expiry = new Date('2050/01/01'); + + params = { + accounts: [ + { + secondaryAccount: { + account: entityMockUtils.getAccountInstance({ + address: 'secondaryAccount', + getIdentity: null, + }), + permissions: { + assets: null, + portfolios: null, + transactions: null, + transactionGroups: [], + }, + }, + authSignature: '0xSignature', + }, + ], + expiresAt: expiry, + }; + + rawAdditionalKeys = + 'someKeys' as unknown as Vec; + + when(secondaryAccountWithAuthToSecondaryKeyWithAuthSpy) + .calledWith(params.accounts, mockContext) + .mockReturnValue(rawAdditionalKeys); + + rawExpiry = dsMockUtils.createMockMoment(new BigNumber(expiry.getTime())); + + when(dateToMomentSpy).calledWith(expiry, mockContext).mockReturnValue(rawExpiry); + }); + + afterEach(() => { + entityMockUtils.reset(); + procedureMockUtils.reset(); + dsMockUtils.reset(); + }); + + afterAll(() => { + jest.resetAllMocks(); + procedureMockUtils.cleanup(); + dsMockUtils.cleanup(); + }); + + it('should throw an error if the one or more accounts are already linked to an Identity', () => { + const accounts = [ + { + secondaryAccount: { + account: entityMockUtils.getAccountInstance({ + address: 'secondaryAccount', + getIdentity: entityMockUtils.getIdentityInstance({ did: 'someRandomDid' }), + }), + permissions: { + assets: null, + portfolios: null, + transactions: null, + transactionGroups: [], + }, + }, + authSignature: '0xSignature', + }, + ]; + + const proc = procedureMockUtils.getInstance( + mockContext, + { + identity, + actingAccount, + } + ); + + return expect( + prepareAddSecondaryKeysWithAuth.call(proc, { + ...params, + accounts, + }) + ).rejects.toThrow('One or more accounts are already linked to some Identity'); + }); + + it('should add a create addSecondaryKeysWithAuthorization transaction to the queue', async () => { + const txMock = dsMockUtils.createTxMock('identity', 'addSecondaryKeysWithAuthorization'); + const proc = procedureMockUtils.getInstance( + mockContext, + { + identity, + actingAccount, + } + ); + + const result = await prepareAddSecondaryKeysWithAuth.call(proc, params); + + expect(result).toEqual({ + transaction: txMock, + feeMultiplier: new BigNumber(1), + args: [rawAdditionalKeys, rawExpiry], + resolver: identity, + }); + }); + + describe('getAuthorization', () => { + it('should return the appropriate roles and permissions', async () => { + let proc = procedureMockUtils.getInstance( + mockContext, + { + identity, + actingAccount, + } + ); + let boundFunc = getAuthorization.bind(proc); + + let result = await boundFunc(); + expect(result).toEqual({ + permissions: { + transactions: [TxTags.identity.AddSecondaryKeysWithAuthorization], + assets: [], + portfolios: [], + }, + }); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (actingAccount.isEqual as any).mockReturnValue(false); + + proc = procedureMockUtils.getInstance( + dsMockUtils.getContextInstance(), + { identity, actingAccount } + ); + + boundFunc = getAuthorization.bind(proc); + + result = await boundFunc(); + expect(result).toEqual({ + signerPermissions: 'Secondary accounts can only be added by primary key of an Identity', + }); + }); + }); + + describe('prepareStorage', () => { + it('should return the signing Identity', async () => { + const proc = procedureMockUtils.getInstance( + mockContext + ); + const boundFunc = prepareStorage.bind(proc); + + const result = await boundFunc(); + + expect(result).toEqual({ + identity: expect.objectContaining({ + did: 'someDid', + }), + actingAccount: expect.objectContaining({ + address: '0xdummy', + }), + }); + }); + }); +}); diff --git a/src/api/procedures/addSecondaryAccountsWithAuth.ts b/src/api/procedures/addSecondaryAccountsWithAuth.ts new file mode 100644 index 0000000000..5ef306481b --- /dev/null +++ b/src/api/procedures/addSecondaryAccountsWithAuth.ts @@ -0,0 +1,106 @@ +import BigNumber from 'bignumber.js'; + +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'; + +/** + * @hidden + */ +export interface Storage { + identity: Identity; + actingAccount: Account; +} + +/** + * @hidden + */ +export async function prepareAddSecondaryKeysWithAuth( + this: Procedure, + args: AddSecondaryAccountsParams +): Promise< + TransactionSpec> +> { + const { + context: { + polymeshApi: { tx }, + }, + context, + storage: { identity }, + } = this; + + const { accounts, expiresAt } = args; + + const identities = await Promise.all( + accounts.map(({ secondaryAccount: { account } }) => account.getIdentity()) + ); + + 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.addSecondaryKeysWithAuthorization, + feeMultiplier: new BigNumber(accounts.length), + args: [secondaryAccountWithAuthToSecondaryKeyWithAuth(accounts, context), rawExpiry], + resolver: identity, + }; +} + +/** + * @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: 'Secondary accounts can only be added by primary key of an Identity', + }; + } + + return { + permissions: { + transactions: [TxTags.identity.AddSecondaryKeysWithAuthorization], + 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 addSecondaryAccounts = (): Procedure => + new Procedure(prepareAddSecondaryKeysWithAuth, getAuthorization, prepareStorage); diff --git a/src/api/procedures/types.ts b/src/api/procedures/types.ts index e78b6456e8..5944d9b085 100644 --- a/src/api/procedures/types.ts +++ b/src/api/procedures/types.ts @@ -600,6 +600,16 @@ export interface RemoveSecondaryAccountsParams { accounts: Account[]; } +export interface AccountWithSignature { + secondaryAccount: PermissionedAccount; + authSignature: string; +} + +export interface AddSecondaryAccountsParams { + accounts: AccountWithSignature[]; + expiresAt: Date; +} + export interface SubsidizeAccountParams { /** * Account to subsidize diff --git a/src/internal.ts b/src/internal.ts index 34d738a428..4128f335fe 100644 --- a/src/internal.ts +++ b/src/internal.ts @@ -49,6 +49,7 @@ export { registerIdentity } from '~/api/procedures/registerIdentity'; export { createChildIdentity } from '~/api/procedures/createChildIdentity'; export { attestPrimaryKeyRotation } from '~/api/procedures/attestPrimaryKeyRotation'; export { rotatePrimaryKey } from '~/api/procedures/rotatePrimaryKey'; +export { addSecondaryAccounts } from '~/api/procedures/addSecondaryAccountsWithAuth'; export { removeSecondaryAccounts } from '~/api/procedures/removeSecondaryAccounts'; export { modifySignerPermissions, diff --git a/src/testUtils/mocks/entities.ts b/src/testUtils/mocks/entities.ts index 8b7e8c89db..d62944f886 100644 --- a/src/testUtils/mocks/entities.ts +++ b/src/testUtils/mocks/entities.ts @@ -153,6 +153,7 @@ interface IdentityOptions extends EntityOptions { assetPermissionsGet?: EntityGetter; isAssetPreApproved?: EntityGetter; preApprovedAssets?: EntityGetter>; + getOffChainAuthorizationNonce?: EntityGetter; } interface ChildIdentityOptions extends IdentityOptions { @@ -597,6 +598,7 @@ const MockIdentityClass = createMockEntityClass( isCddProvider!: jest.Mock; preApprovedAssets!: jest.Mock; isAssetPreApproved!: jest.Mock; + getOffChainAuthorizationNonce!: jest.Mock; /** * @hidden @@ -636,6 +638,9 @@ const MockIdentityClass = createMockEntityClass( this.isCddProvider = createEntityGetterMock(opts.isCddProvider); this.preApprovedAssets = createEntityGetterMock(opts.preApprovedAssets); this.isAssetPreApproved = createEntityGetterMock(opts.isAssetPreApproved); + this.getOffChainAuthorizationNonce = createEntityGetterMock( + opts.getOffChainAuthorizationNonce + ); } }, () => ({ @@ -674,6 +679,7 @@ const MockIdentityClass = createMockEntityClass( preApprovedAssets: { data: [], next: null, count: new BigNumber(0) }, isAssetPreApproved: false, toHuman: 'someDid', + getOffChainAuthorizationNonce: new BigNumber(0), }), ['Identity'] ); @@ -804,6 +810,7 @@ const MockChildIdentityClass = createMockEntityClass( getParentDid: getIdentityInstance(), preApprovedAssets: { data: [], next: null, count: new BigNumber(0) }, isAssetPreApproved: false, + getOffChainAuthorizationNonce: new BigNumber(0), }), ['ChildIdentity', 'Identity'] ); diff --git a/src/utils/__tests__/conversion.ts b/src/utils/__tests__/conversion.ts index 2430ab056a..6f87a9bb3a 100644 --- a/src/utils/__tests__/conversion.ts +++ b/src/utils/__tests__/conversion.ts @@ -17,6 +17,7 @@ import { PalletCorporateActionsTargetIdentities, PalletStoPriceTier, PolymeshCommonUtilitiesCheckpointScheduleCheckpoints, + PolymeshCommonUtilitiesIdentitySecondaryKeyWithAuth, PolymeshCommonUtilitiesProtocolFeeProtocolOp, PolymeshPrimitivesAgentAgentGroup, PolymeshPrimitivesAssetAssetType, @@ -112,6 +113,7 @@ import { } from '~/testUtils/mocks/dataSources'; import { Mocked } from '~/testUtils/types'; import { + AccountWithSignature, AffirmationStatus, AssetDocument, Authorization, @@ -297,6 +299,7 @@ import { scopeToMeshScope, scopeToMiddlewareScope, secondaryAccountToMeshSecondaryKey, + secondaryAccountWithAuthToSecondaryKeyWithAuth, securityIdentifierToAssetIdentifier, signatoryToSignerValue, signatureToMeshRuntimeMultiSignature, @@ -10185,3 +10188,50 @@ describe('portfolioIdStringToPortfolio', () => { expect(result.number).toBeNaN(); }); }); + +describe('secondaryAccountWithAuthToSecondaryKeyWithAuth', () => { + beforeAll(() => { + dsMockUtils.initMocks(); + }); + + afterEach(() => { + dsMockUtils.reset(); + }); + + afterAll(() => { + dsMockUtils.cleanup(); + }); + + it('should create additional keys', () => { + const context = dsMockUtils.getContextInstance(); + + const accounts = [ + { + secondaryAccount: { + account: entityMockUtils.getAccountInstance({ + address: 'secondaryAccount', + getIdentity: null, + }), + permissions: { + assets: null, + portfolios: null, + transactions: null, + transactionGroups: [], + }, + }, + authSignature: '0xSignature', + }, + ] as unknown as AccountWithSignature[]; + + const fakeResult = + 'fakeSecondaryKeysWithAuth' as unknown as Vec; + + when(context.createType) + .calledWith('Vec', expect.any(Object)) + .mockReturnValue(fakeResult); + + const result = secondaryAccountWithAuthToSecondaryKeyWithAuth(accounts, context); + + expect(result).toEqual(fakeResult); + }); +}); diff --git a/src/utils/conversion.ts b/src/utils/conversion.ts index b9af2540df..1dabfb1065 100644 --- a/src/utils/conversion.ts +++ b/src/utils/conversion.ts @@ -21,6 +21,7 @@ import { PalletStoFundraiserTier, PalletStoPriceTier, PolymeshCommonUtilitiesCheckpointScheduleCheckpoints, + PolymeshCommonUtilitiesIdentitySecondaryKeyWithAuth, PolymeshCommonUtilitiesProtocolFeeProtocolOp, PolymeshPrimitivesAgentAgentGroup, PolymeshPrimitivesAssetAssetType, @@ -146,6 +147,7 @@ import { Moment, } from '~/polkadot/polymesh'; import { + AccountWithSignature, AffirmationStatus, AssetDocument, Authorization, @@ -4981,3 +4983,21 @@ export function receiptDetailsToMeshReceiptDetails( return context.createType('Vec', rawReceiptDetails); } + +/** + * @hidden + */ +export function secondaryAccountWithAuthToSecondaryKeyWithAuth( + accounts: AccountWithSignature[], + context: Context +): Vec { + const keyWithAuths = accounts.map(({ secondaryAccount, authSignature }) => ({ + secondaryKey: secondaryAccountToMeshSecondaryKey(secondaryAccount, context), + authSignature: stringToU8aFixed(authSignature, context), + })); + + return context.createType( + 'Vec', + keyWithAuths + ); +}