Skip to content

Commit

Permalink
feat: add getPendingInstructions to CurrentIdentity
Browse files Browse the repository at this point in the history
Also add `addInstruction` to `Venue`
  • Loading branch information
monitz87 committed Oct 19, 2020
1 parent 5a261ad commit a9062db
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 3 deletions.
35 changes: 34 additions & 1 deletion src/api/entities/CurrentIdentity.ts
@@ -1,4 +1,7 @@
import { Identity, Venue } from '~/api/entities';
import { u64 } from '@polkadot/types';
import { Instruction as MeshInstruction } from 'polymesh-types/types';

import { Identity, Instruction, Venue } from '~/api/entities';
import {
createVenue,
CreateVenueParams,
Expand All @@ -8,6 +11,7 @@ import {
} from '~/api/procedures';
import { TransactionQueue } from '~/base';
import { SecondaryKey, Signer, SubCallback, UnsubCallback } from '~/types';
import { portfolioIdToMeshPortfolioId,stringToIdentityId, u64ToBigNumber } from '~/utils';

/**
* Represents the Identity associated to the current [[Account]]
Expand Down Expand Up @@ -58,4 +62,33 @@ export class CurrentIdentity extends Identity {
public createVenue(args: CreateVenueParams): Promise<TransactionQueue<Venue>> {
return createVenue.prepare(args, this.context);
}

/**
* Retrieve all pending Instructions involving the Current Identity
*/
public async getPendingInstructions(): Promise<Instruction[]> {
const {
context: {
polymeshApi: {
query: { settlement },
},
},
did,
context,
} = this;

const auths = await settlement.userAuths.entries(
portfolioIdToMeshPortfolioId({ did }, context)
);

const instructionIds = auths.map(([key]) => key.args[1] as u64);

const meshInstructions = await settlement.instructionDetails.multi<MeshInstruction>(
instructionIds
);

return meshInstructions
.filter(({ status }) => status.isPending)
.map(({ instruction_id: id }) => new Instruction({ id: u64ToBigNumber(id) }, context));
}
}
46 changes: 45 additions & 1 deletion src/api/entities/Venue/__tests__/index.ts
Expand Up @@ -3,7 +3,9 @@ import BigNumber from 'bignumber.js';
import sinon from 'sinon';

import { Entity, Venue } from '~/api/entities';
import { Context } from '~/base';
import { Instruction } from '~/api/entities/Instruction';
import { addInstruction } from '~/api/procedures/addInstruction';
import { Context, TransactionQueue } from '~/base';
import { dsMockUtils, entityMockUtils } from '~/testUtils/mocks';
import { Mocked } from '~/testUtils/types';
import { VenueType } from '~/types';
Expand Down Expand Up @@ -131,4 +133,46 @@ describe('Venue class', () => {
expect(result[0].id).toEqual(instructionId);
});
});

describe('method: addInstruction', () => {
let prepareAddInstructionStub: sinon.SinonStub;

beforeAll(() => {
prepareAddInstructionStub = sinon.stub(addInstruction, 'prepare');
});

afterAll(() => {
sinon.restore();
});

test('should prepare the procedure and return the resulting transaction queue', async () => {
const legs = [
{
from: 'someDid',
to: 'anotherDid',
amount: new BigNumber(1000),
token: 'SOME_TOKEN',
},
{
from: 'anotherDid',
to: 'aThirdDid',
amount: new BigNumber(100),
token: 'ANOTHER_TOKEN',
},
];

const validFrom = new Date(new Date().getTime() + 24 * 60 * 60 * 1000);
const endBlock = new BigNumber(10000);

const expectedQueue = ('someQueue' as unknown) as TransactionQueue<Instruction>;

prepareAddInstructionStub
.withArgs({ venueId: id, legs, validFrom, endBlock }, context)
.resolves(expectedQueue);

const queue = await venue.addInstruction({ legs, validFrom, endBlock });

expect(queue).toBe(expectedQueue);
});
});
});
16 changes: 15 additions & 1 deletion src/api/entities/Venue/index.ts
Expand Up @@ -2,7 +2,8 @@ import BigNumber from 'bignumber.js';
import P from 'bluebird';

import { Entity, Identity, Instruction } from '~/api/entities';
import { Context } from '~/base';
import { addInstruction, AddInstructionParams } from '~/api/procedures/addInstruction';
import { Context, TransactionQueue } from '~/base';
import { InstructionStatus } from '~/types';
import {
identityIdToString,
Expand Down Expand Up @@ -99,4 +100,17 @@ export class Venue extends Entity<UniqueIdentifiers> {
return status === InstructionStatus.Pending;
});
}

/**
* Creates a settlement instruction in this Venue
*
* @param args.legs - array of token movements (amount, from, to, token)
* @param args.validFrom - date from which the instruction is valid and can be authorized by the participants (optional, instruction will be valid from the start if not supplied)
* @param args.endBlock - block at which the instruction will be executed automatically (optional, the instruction will be executed when all participants have authorized it if not supplied)
*/
public async addInstruction(args: AddInstructionParams): Promise<TransactionQueue<Instruction>> {
const { id, context } = this;

return addInstruction.prepare({ ...args, venueId: id }, context);
}
}
71 changes: 71 additions & 0 deletions src/api/entities/__tests__/CurrentIdentity.ts
@@ -1,10 +1,12 @@
import BigNumber from 'bignumber.js';
import sinon from 'sinon';

import { CurrentIdentity, Identity, Venue } from '~/api/entities';
import { createVenue, inviteAccount, removeSecondaryKeys } from '~/api/procedures';
import { Context, TransactionQueue } from '~/base';
import { dsMockUtils, entityMockUtils } from '~/testUtils/mocks';
import { SecondaryKey, SubCallback, VenueType } from '~/types';
import { tuple } from '~/types/utils';

describe('CurrentIdentity class', () => {
let context: Context;
Expand Down Expand Up @@ -133,4 +135,73 @@ describe('CurrentIdentity class', () => {
expect(queue).toBe(expectedQueue);
});
});

describe('method: getPendingInstructions', () => {
test('should return all pending instructions in which the identity is involved', async () => {
const id1 = new BigNumber(1);
const id2 = new BigNumber(2);
const id3 = new BigNumber(3);

const did = 'someDid';
const identity = new CurrentIdentity({ did }, context);

const rawPortfolio = dsMockUtils.createMockPortfolioId({
did: dsMockUtils.createMockIdentityId(did),
kind: dsMockUtils.createMockPortfolioKind('Default'),
});
dsMockUtils.createQueryStub('settlement', 'userAuths', {
entries: [
tuple(
[rawPortfolio, dsMockUtils.createMockU64(id1.toNumber())],
dsMockUtils.createMockAuthorizationStatus('Pending')
),
tuple(
[rawPortfolio, dsMockUtils.createMockU64(id2.toNumber())],
dsMockUtils.createMockAuthorizationStatus('Pending')
),
tuple(
[rawPortfolio, dsMockUtils.createMockU64(id3.toNumber())],
dsMockUtils.createMockAuthorizationStatus('Pending')
),
],
});

/* eslint-disable @typescript-eslint/camelcase */
dsMockUtils.createQueryStub('settlement', 'instructionDetails', {
multi: [
dsMockUtils.createMockInstruction({
instruction_id: dsMockUtils.createMockU64(id1.toNumber()),
venue_id: dsMockUtils.createMockU64(),
status: dsMockUtils.createMockInstructionStatus('Pending'),
settlement_type: dsMockUtils.createMockSettlementType('SettleOnAuthorization'),
created_at: dsMockUtils.createMockOption(),
valid_from: dsMockUtils.createMockOption(),
}),
dsMockUtils.createMockInstruction({
instruction_id: dsMockUtils.createMockU64(id2.toNumber()),
venue_id: dsMockUtils.createMockU64(),
status: dsMockUtils.createMockInstructionStatus('Pending'),
settlement_type: dsMockUtils.createMockSettlementType('SettleOnAuthorization'),
created_at: dsMockUtils.createMockOption(),
valid_from: dsMockUtils.createMockOption(),
}),
dsMockUtils.createMockInstruction({
instruction_id: dsMockUtils.createMockU64(id3.toNumber()),
venue_id: dsMockUtils.createMockU64(),
status: dsMockUtils.createMockInstructionStatus('Unknown'),
settlement_type: dsMockUtils.createMockSettlementType('SettleOnAuthorization'),
created_at: dsMockUtils.createMockOption(),
valid_from: dsMockUtils.createMockOption(),
}),
],
});
/* eslint-enable @typescript-eslint/camelcase */

const result = await identity.getPendingInstructions();

expect(result.length).toBe(2);
expect(result[0].id).toEqual(id1);
expect(result[1].id).toEqual(id2);
});
});
});
30 changes: 30 additions & 0 deletions src/testUtils/mocks/dataSources.ts
Expand Up @@ -57,6 +57,7 @@ import {
FundingRoundName,
IdentityId,
IdentityRole,
Instruction,
InstructionStatus,
IssueAssetItem,
LinkedKeyInfo,
Expand Down Expand Up @@ -1936,3 +1937,32 @@ export const createMockAuthorizationStatus = (
): AuthorizationStatus => {
return createMockEnum(authorizationStatus) as AuthorizationStatus;
};

/**
* @hidden
* NOTE: `isEmpty` will be set to true if no value is passed
*/
export const createMockInstruction = (instruction?: {
instruction_id: u64;
venue_id: u64;
status: InstructionStatus;
settlement_type: SettlementType;
created_at: Option<Moment>;
valid_from: Option<Moment>;
}): Instruction => {
const data = instruction || {
instruction_id: createMockU64(),
venue_id: createMockU64(),
status: createMockInstructionStatus(),
settlement_type: createMockSettlementType(),
created_at: createMockOption(),
valid_from: createMockOption(),
};

return createMockCodec(
{
...data,
},
!instruction
) as Instruction;
};

0 comments on commit a9062db

Please sign in to comment.